{"type":"title","v":1,"title":"","updatedAt":"2026-07-11T00:40:25.141Z","pad":"                                                                                                                                                                              "}
{"type":"session","version":3,"id":"019f4e9e-4d32-7000-adb2-f46e9eaa2af8","timestamp":"2026-07-11T00:40:25.141Z","cwd":"C:\\Users\\decid\\Documents\\projects"}
{"type":"model_change","id":"4b707521","parentId":null,"timestamp":"2026-07-11T00:40:25.239Z","model":"openai-codex/gpt-5.6-sol"}
{"type":"thinking_level_change","id":"52a136ec","parentId":"4b707521","timestamp":"2026-07-11T00:40:25.240Z","thinkingLevel":"medium","configured":null}
{"type":"session_init","id":"840df98a","parentId":"52a136ec","timestamp":"2026-07-11T00:40:25.241Z","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\nand runs them sequentially with auto-decisions using 6 decision principles. Surfaces\ntaste decisions (close approaches, borderline scope, codex disagreements) at a final\napproval gate. One command, fully reviewed plan out.\nUse when asked to \"auto review\", \"autoplan\", \"run all reviews\", \"review this plan\nautomatically\", or \"make the decisions for me\".\nProactively suggest when the user has a plan file and wants to run the full review\ngauntlet without answering 15-30 intermediate questions. (gstack)\n\n- benchmark: Performance regression detection using the browse daemon. Establishes\nbaselines for page load times, Core Web Vitals, and resource sizes.\nCompares before/after on every PR. Tracks performance trends over time.\nUse when: \"performance\", \"benchmark\", \"page speed\", \"lighthouse\", \"web vitals\",\n\"bundle size\", \"load time\". (gstack)\n\n- browse: Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with\nelements, verify page state, diff before/after actions, take annotated screenshots, check\nresponsive layouts, test forms and uploads, handle dialogs, and assert element states.\n~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a\nuser flow, or file a bug with evidence. Use when asked to \"open in browser\", \"test the\nsite\", \"take a screenshot\", or \"dogfood this\". (gstack)\n\n- canary: Post-deploy canary monitoring. Watches the live app for console errors,\nperformance regressions, and page failures using the browse daemon. Takes\nperiodic screenshots, compares against pre-deploy baselines, and alerts\non anomalies. Use when: \"monitor deploy\", \"canary\", \"post-deploy check\",\n\"watch production\", \"verify deploy\". (gstack)\n\n- careful: Safety guardrails for destructive commands. Warns before rm -rf, DROP TABLE,\nforce-push, git reset --hard, kubectl delete, and similar destructive operations.\nUser can override each warning. Use when touching prod, debugging live systems,\nor working in a shared environment. Use when asked to \"be careful\", \"safety mode\",\n\"prod mode\", or \"careful mode\". (gstack)\n\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. Code review: independent diff review via\ncodex review with pass/fail gate. Challenge: adversarial mode that tries to break\nyour code. Consult: ask codex anything with session continuity for follow-ups.\nThe \"200 IQ autistic developer\" second opinion. Use when asked to \"codex review\",\n\"codex challenge\", \"ask codex\", \"second opinion\", or \"consult codex\". (gstack)\n\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- cso: Chief Security Officer mode. Infrastructure-first security audit: secrets archaeology,\ndependency supply chain, CI/CD pipeline security, LLM/AI security, skill supply chain\nscanning, plus OWASP Top 10, STRIDE threat modeling, and active verification.\nTwo modes: daily (zero-noise, 8/10 confidence gate) and comprehensive (monthly deep\nscan, 2/10 bar). Trend tracking across audit runs.\nUse when: \"security audit\", \"threat model\", \"pentest review\", \"OWASP\", \"CSO review\". (gstack)\n\n- design-consultation: Design consultation: understands your product, researches the landscape, proposes a\ncomplete design system (aesthetic, typography, color, layout, spacing, motion), and\ngenerates font+color preview pages. Creates DESIGN.md as your project's design source\nof truth. For existing sites, use /plan-design-review to infer the system instead.\nUse when asked to \"design system\", \"brand guidelines\", or \"create DESIGN.md\".\nProactively suggest when starting a new project's UI with no existing\ndesign system or DESIGN.md. (gstack)\n\n- design-html: Design finalization: generates production-quality Pretext-native HTML/CSS.\nWorks with approved mockups from /design-shotgun, CEO plans from /plan-ceo-review,\ndesign review context from /plan-design-review, or from scratch with a user\ndescription. Text actually reflows, heights are computed, layouts are dynamic.\n30KB overhead, zero deps. Smart API routing: picks the right Pretext patterns\nfor each design type. Use when: \"finalize this design\", \"turn this into HTML\",\n\"build me a page\", \"implement this design\", or after any planning skill.\nProactively suggest when user has approved a design or has a plan ready. (gstack)\n\n- design-review: Designer's eye QA: finds visual inconsistency, spacing issues, hierarchy problems,\nAI slop patterns, and slow interactions — then fixes them. Iteratively fixes issues\nin source code, committing each fix atomically and re-verifying with before/after\nscreenshots. For plan-mode design review (before implementation), use /plan-design-review.\nUse when asked to \"audit the design\", \"visual QA\", \"check if it looks good\", or \"design polish\".\nProactively suggest when the user mentions visual inconsistencies or\nwants to polish the look of a live site. (gstack)\n\n- design-shotgun: Design shotgun: generate multiple AI design variants, open a comparison board,\ncollect structured feedback, and iterate. Standalone design exploration you can\nrun anytime. Use when: \"explore designs\", \"show me options\", \"design variants\",\n\"visual brainstorm\", or \"I don't like how this looks\".\nProactively suggest when the user describes a UI feature but hasn't seen\nwhat it could look like. (gstack)\n\n- devex-review: Live developer experience audit. Uses the browse tool to actually TEST the\ndeveloper experience: navigates docs, tries the getting started flow, times\nTTHW, screenshots error messages, evaluates CLI help text. Produces a DX\nscorecard with evidence. Compares against /plan-devex-review scores if they\nexist (the boomerang: plan said 3 minutes, reality says 8). Use when asked to\n\"test the DX\", \"DX audit\", \"developer experience test\", or \"try the\nonboarding\". Proactively suggest after shipping a developer-facing feature. (gstack)\n\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- document-release: Post-ship documentation update. Reads all project docs, cross-references the\ndiff, updates README/ARCHITECTURE/CONTRIBUTING/CLAUDE.md to match what shipped,\npolishes CHANGELOG voice, cleans up TODOS, and optionally bumps VERSION. Use when\nasked to \"update the docs\", \"sync documentation\", or \"post-ship docs\".\nProactively suggest after a PR is merged or code is shipped. (gstack)\n\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. Blocks Edit and\nWrite outside the allowed path. Use when debugging to prevent accidentally\n\"fixing\" unrelated code, or when you want to scope changes to one module.\nUse when asked to \"freeze\", \"restrict edits\", \"only edit this folder\",\nor \"lock down edits\". (gstack)\n\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: Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with\nelements, verify state, diff before/after, take annotated screenshots, test responsive\nlayouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or\ntest a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)\n\n- gstack-upgrade: Upgrade gstack to the latest version. Detects global vs vendored install,\nruns the upgrade, and shows what's new. Use when asked to \"upgrade gstack\",\n\"update gstack\", or \"get latest version\".\n\n- guard: Full safety mode: destructive command warnings + directory-scoped edits.\nCombines /careful (warns before rm -rf, DROP TABLE, force-push, etc.) with\n/freeze (blocks edits outside a specified directory). Use for maximum safety\nwhen touching prod or debugging live systems. Use when asked to \"guard mode\",\n\"full safety\", \"lock it down\", or \"maximum safety\". (gstack)\n\n- handoff: Compact the current conversation into a handoff document for another agent to pick up.\n- health: Code quality dashboard. Wraps existing project tools (type checker, linter,\ntest runner, dead code detector, shell linter), computes a weighted composite\n0-10 score, and tracks trends over time. Use when: \"health check\",\n\"code quality\", \"how healthy is the codebase\", \"run all checks\",\n\"quality score\". (gstack)\n\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. Four phases: investigate,\nanalyze, hypothesize, implement. Iron Law: no fixes without root cause.\nUse when asked to \"debug this\", \"fix this bug\", \"why is this broken\",\n\"investigate this error\", or \"root cause analysis\".\nProactively invoke this skill (do NOT debug directly) when the user reports\nerrors, 500 errors, stack traces, unexpected behavior, \"it was working\nyesterday\", or is troubleshooting why something stopped working. (gstack)\n\n- land-and-deploy: Land and deploy workflow. Merges the PR, waits for CI and deploy,\nverifies production health via canary checks. Takes over after /ship\ncreates the PR. Use when: \"merge\", \"land\", \"deploy\", \"merge and verify\",\n\"land it\", \"ship it to production\". (gstack)\n\n- learn: Manage project learnings. Review, search, prune, and export what gstack\nhas learned across sessions. Use when asked to \"what have we learned\",\n\"show learnings\", \"prune stale learnings\", or \"export learnings\".\nProactively suggest when the user asks about past patterns or wonders\n\"didn't we fix this before?\"\n\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- 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. Startup mode: six forcing questions that expose\ndemand reality, status quo, desperate specificity, narrowest wedge, observation,\nand future-fit. Builder mode: design thinking brainstorming for side projects,\nhackathons, learning, and open source. Saves a design doc.\nUse when asked to \"brainstorm this\", \"I have an idea\", \"help me think through\nthis\", \"office hours\", or \"is this worth building\".\nProactively invoke this skill (do NOT answer directly) when the user describes\na new product idea, asks whether something is worth building, wants to think\nthrough design decisions for something that doesn't exist yet, or is exploring\na concept before any code is written.\nUse before /plan-ceo-review or /plan-eng-review. (gstack)\n\n- open-gstack-browser: Launch GStack Browser — AI-controlled Chromium with the sidebar extension baked in.\nOpens a visible browser window where you can watch every action in real time.\nThe sidebar shows a live activity feed and chat. Anti-bot stealth built in.\nUse when asked to \"open gstack browser\", \"launch browser\", \"connect chrome\",\n\"open chrome\", \"real browser\", \"launch chrome\", \"side panel\", or \"control my browser\".\n\n- pair-agent: Pair a remote AI agent with your browser. One command generates a setup key and\nprints instructions the other agent can follow to connect. Works with OpenClaw,\nHermes, Codex, Cursor, or any agent that can make HTTP requests. The remote agent\ngets its own tab with scoped access (read+write by default, admin on request).\nUse when asked to \"pair agent\", \"connect agent\", \"share browser\", \"remote browser\",\n\"let another agent use my browser\", or \"give browser access\". (gstack)\n\n- plan-ceo-review: CEO/founder-mode plan review. Rethink the problem, find the 10-star product,\nchallenge premises, expand scope when it creates a better product. Four modes:\nSCOPE EXPANSION (dream big), SELECTIVE EXPANSION (hold scope + cherry-pick\nexpansions), HOLD SCOPE (maximum rigor), SCOPE REDUCTION (strip to essentials).\nUse when asked to \"think bigger\", \"expand scope\", \"strategy review\", \"rethink this\",\nor \"is this ambitious enough\".\nProactively suggest when the user is questioning scope or ambition of a plan,\nor when the plan feels like it could be thinking bigger. (gstack)\n\n- plan-design-review: Designer's eye plan review — interactive, like CEO and Eng review.\nRates each design dimension 0-10, explains what would make it a 10,\nthen fixes the plan to get there. Works in plan mode. For live site\nvisual audits, use /design-review. Use when asked to \"review the design plan\"\nor \"design critique\".\nProactively suggest when the user has a plan with UI/UX components that\nshould be reviewed before implementation. (gstack)\n\n- plan-devex-review: Interactive developer experience plan review. Explores developer personas,\nbenchmarks against competitors, designs magical moments, and traces friction\npoints before scoring. Three modes: DX EXPANSION (competitive advantage),\nDX POLISH (bulletproof every touchpoint), DX TRIAGE (critical gaps only).\nUse when asked to \"DX review\", \"developer experience audit\", \"devex review\",\nor \"API design review\".\nProactively suggest when the user has a plan for developer-facing products\n(APIs, CLIs, SDKs, libraries, platforms, docs). (gstack)\n\n- plan-eng-review: Eng manager-mode plan review. Lock in the execution plan — architecture,\ndata flow, diagrams, edge cases, test coverage, performance. Walks through\nissues interactively with opinionated recommendations. Use when asked to\n\"review the architecture\", \"engineering review\", or \"lock in the plan\".\nProactively suggest when the user has a plan or design doc and is about to\nstart coding — to catch architecture issues before implementation. (gstack)\n\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. Runs QA testing,\nthen iteratively fixes bugs in source code, committing each fix atomically and\nre-verifying. Use when asked to \"qa\", \"QA\", \"test this site\", \"find bugs\",\n\"test and fix\", or \"fix what's broken\".\nProactively suggest when the user says a feature is ready for testing\nor asks \"does this work?\". Three tiers: Quick (critical/high only),\nStandard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores,\nfix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. (gstack)\n\n- qa-only: Report-only QA testing. Systematically tests a web application and produces a\nstructured report with health score, screenshots, and repro steps — but never\nfixes anything. Use when asked to \"just report bugs\", \"qa report only\", or\n\"test but don't fix\". For the full test-fix-verify loop, use /qa instead.\nProactively suggest when the user wants a bug report without any code changes. (gstack)\n\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. Analyzes commit history, work patterns,\nand code quality metrics with persistent history and trend tracking.\nTeam-aware: breaks down per-person contributions with praise and growth areas.\nUse when asked to \"weekly retro\", \"what did we ship\", or \"engineering retrospective\".\nProactively suggest at the end of a work week or sprint. (gstack)\n\n- review: Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust\nboundary violations, conditional side effects, and other structural issues. Use when\nasked to \"review this PR\", \"code review\", \"pre-landing review\", or \"check my diff\".\nProactively suggest when the user is about to merge or land code changes. (gstack)\n\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- 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.\nOpens an interactive picker UI where you select which cookie domains to import.\nUse before QA testing authenticated pages. Use when asked to \"import cookies\",\n\"login to the site\", or \"authenticate the browser\". (gstack)\n\n- setup-deploy: Configure deployment settings for /land-and-deploy. Detects your deploy\nplatform (Fly.io, Render, Vercel, Netlify, Heroku, GitHub Actions, custom),\nproduction URL, health check endpoints, and deploy status commands. Writes\nthe configuration to CLAUDE.md so all future deploys are automatic.\nUse when: \"setup deploy\", \"configure deployment\", \"set up land-and-deploy\",\n\"how do I deploy with gstack\", \"add deploy config\".\n\n- ship: Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION,\nupdate CHANGELOG, commit, push, create PR. Use when asked to \"ship\", \"deploy\",\n\"push to main\", \"create a PR\", \"merge and push\", or \"get it deployed\".\nProactively invoke this skill (do NOT push/PR directly) when the user says code\nis ready, asks about deploying, wants to push code up, or asks to create a PR. (gstack)\n\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- 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- 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\nagain. Use when you want to widen edit scope without ending the session.\nUse when asked to \"unfreeze\", \"unlock edits\", \"remove freeze\", or\n\"allow all edits\". (gstack)\n\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: **Concurrency RCA 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\nRoot-cause the intermittent PTY freeze reported since spt-core v0.30.3/v0.30.4, without implementing a fix.\n# Constraints\nRead-only in C:/Users/decid/Documents/projects/spt-core because another agent owns it. Do not run formatters, linters, builds, tests, restart/stop the live daemon, or edit files. Every claim needs exact code/log evidence. Live daemon has non-default SPT_INJECT_VERIFY_ECHO enabled; treat as a confounder. Focus on freeze root, not W6 poison-log loudness.\n# Contract\nReturn exact paths/line ranges and falsifiable causal chains. Compare v0.30.2 through v0.30.6 when relevant. Evidence logs: %LOCALAPPDATA%/spt-core/logs/daemon.stderr.log and .1.\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 `FreezeCodePath`. Currently visible peers:\n- `Main` — main (main, running)\n- `FreezeLogTimeline` — Production Log Forensics (sub, running)\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-10, and the current working directory is 'C:/Users/decid/Documents/projects'.\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\nC:/Users/decid/Documents/projects/spt-core PTY broker/pump, seamless swap, resume reattach, inject settle/echo verification code and relevant history/diffs. Non-goal: edits or builds.\n# Change\nMap all blocking/serialized operations on active PTY data paths added or changed in v0.30.3-v0.30.6. Test leads: bounded broker writes stacking, resume machinery, inject echo re-drive. Identify 3-5 falsifiable hypotheses and code evidence that supports or kills each.\n# Acceptance\nReport the strongest pinned root candidate or state why code alone cannot pin it, with exact file/line and commit/tag evidence.","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":"7b58cd4d","parentId":"840df98a","timestamp":"2026-07-11T00:40:25.243Z","message":{"role":"user","content":[{"type":"text","text":"Complete the assignment below, thoroughly:\n\n# Target\nC:/Users/decid/Documents/projects/spt-core PTY broker/pump, seamless swap, resume reattach, inject settle/echo verification code and relevant history/diffs. Non-goal: edits or builds.\n# Change\nMap all blocking/serialized operations on active PTY data paths added or changed in v0.30.3-v0.30.6. Test leads: bounded broker writes stacking, resume machinery, inject echo re-drive. Identify 3-5 falsifiable hypotheses and code evidence that supports or kills each.\n# Acceptance\nReport the strongest pinned root candidate or state why code alone cannot pin it, with exact file/line and commit/tag evidence."}],"attribution":"agent","timestamp":1783730425242}}
{"type":"message","id":"9e92bdbd","parentId":"7b58cd4d","timestamp":"2026-07-11T00:40:31.839Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Locating repo structure with glob searches**\n\n<!-- -->"},{"type":"toolCall","id":"call_5ZLUc98B0VgTx5PcaQoq2xBJ|fc_08496348794f7e09016a519100680c8198ac0ca3a45917bdc6","name":"glob","arguments":{"i":"Map PTY source files","path":"spt-core/**/*.{rs,ts,js,py,go}","hidden":true,"gitignore":true,"limit":500}},{"type":"toolCall","id":"call_bBEsGls5xhcQNp62KS9PDJy3|fc_08496348794f7e09016a519100681c81988c6372acea34f79e","name":"grep","arguments":{"i":"Find PTY pump symbols","pattern":"PTY|pty|broker|pump|seamless|reattach","path":"spt-core","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_9QVfB71IoVab0jux3OfxCsQ9|fc_08496348794f7e09016a519100682481989766a299ea67f848","name":"grep","arguments":{"i":"Find inject verification","pattern":"INJECT_VERIFY_ECHO|verify.echo|echo verification|settle|redrive|re-drive","path":"spt-core","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_TSLdO8gma203mM6ULqmwXZYQ|fc_08496348794f7e09016a5191006828819886b97057717410e8","name":"glob","arguments":{"i":"Locate repository history","path":"spt-core/.git/refs/**/*; spt-core/.git/packed-refs; spt-core/.git/logs/**/*","hidden":true,"gitignore":false,"limit":500}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":17080,"output":279,"cacheRead":0,"cacheWrite":0,"totalTokens":17359,"reasoningTokens":31,"cost":{"input":0.0854,"output":0.00837,"cacheRead":0,"cacheWrite":0,"total":0.09377}},"stopReason":"toolUse","timestamp":1783730425296,"responseId":"resp_08496348794f7e09016a5190fa3bfc81988c4bff7e9691e222","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a5190fb5b988198b8abb94d90a3d57f","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZEAtdjy8bqV4ittrfv83ZSEKO_zB1newXfG_qgRCNcvGQBtDaxoHrs6Cvg_IJDgerJDZHvVnFQniyiIXDsAnjJMkLsCzmyOnajFCZ0vDREsLjY-1d1nEtxAYoTuZ9zFpQpxVQVrvAok934ex5AqUAl2VvqeSs3zKyi6cPmb4lQrIXCa8IL1WXU-KvFQg05euo8h0oumUiO1rkol9YNtX6CPuRk6LFJA6buFeDKrODuSJtRZrHCv04Kbkuc4oGUngcOgVKeKN4dDClz6l1ogn4-6Qvh6ct7J9V8pGLO1sCoCuxMYjHRJYfuB_VmZ7FFAP53p_zQ6-UO2yi9PQG8rvoMlxOdavSzndcrEq7q-HAP-Uh0cqk509nZ1lUAqgrp6okTdmuYCET5evGTW17-6rMJ9dvrQBo8CV2tKh5Ymk1PYo8qPqCpfHDTskXGF4bvSjryXbTCA6nGJ5Q3TPRvm2fcamoHYtv7eKnRxcsBrECweX89-h1A6-FLA3t8kbigfFwNXAfFRQHbYyL3VJWhANxIB8jsxspeJ_Z2EQUnPf78X6eep1ybb-snREIiPbz_Ue_HyUAdGN5gKXrxhFM2dRHVxh77JlnZUYZ7x1MmX2gM6TcxvBeoUj2SiqDXEMtAQeQA3XDedbsy1vujt80ycheao8y3RAJGb47CYJwA_JKceGsiSI1JhzLCtG0hx6DURXsM118hkTdGKcFVWezFTCzpNmIkTeutpN8640WvdIBSed7X5aEFUK6_4yrTwFOGV3zxLjwzv3xwdXJUiRm839U5n3S3g-c295SfAnqorF211AMAZbGI00qb9iISNIuRtPgCo7CAwxhFtVocGqmR9QsMnbv4jb8n4vzqaLxRhC6ehfupTHC702GLdU6HHRLLxoqQ6WHhw1TlSsGcUf0pSKn094E5_LG085s8o6PJor569YwIMQYNCnqicTHzecVRkKysqqXbvk1Yr2Za5ehijkaBVvv0hW27c83IYPd4RaeyczkYOmFgb1VmyGuslHOJXPww9GXqFKw4eTZW_sA1oOe-M3LhyMY_txYG4EDJxYqcIJLowCRfXkSRB9asKmaeteJEdv6axPmrWFNeoiIXpyk-baBjyYcWb6dA6bM0gmpWb930=","summary":[{"type":"summary_text","text":"**Locating repo structure with glob searches**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a519100680c8198ac0ca3a45917bdc6","type":"function_call","status":"completed","arguments":"{\"i\":\"Map PTY source files\",\"path\":\"spt-core/**/*.{rs,ts,js,py,go}\",\"hidden\":true,\"gitignore\":true,\"limit\":500}","call_id":"call_5ZLUc98B0VgTx5PcaQoq2xBJ","name":"glob"},{"id":"fc_08496348794f7e09016a519100681c81988c6372acea34f79e","type":"function_call","status":"completed","arguments":"{\"i\":\"Find PTY pump symbols\",\"pattern\":\"PTY|pty|broker|pump|seamless|reattach\",\"path\":\"spt-core\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_bBEsGls5xhcQNp62KS9PDJy3","name":"grep"},{"id":"fc_08496348794f7e09016a519100682481989766a299ea67f848","type":"function_call","status":"completed","arguments":"{\"i\":\"Find inject verification\",\"pattern\":\"INJECT_VERIFY_ECHO|verify.echo|echo verification|settle|redrive|re-drive\",\"path\":\"spt-core\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_9QVfB71IoVab0jux3OfxCsQ9","name":"grep"},{"id":"fc_08496348794f7e09016a5191006828819886b97057717410e8","type":"function_call","status":"completed","arguments":"{\"i\":\"Locate repository history\",\"path\":\"spt-core/.git/refs/**/*; spt-core/.git/packed-refs; spt-core/.git/logs/**/*\",\"hidden\":true,\"gitignore\":false,\"limit\":500}","call_id":"call_TSLdO8gma203mM6ULqmwXZYQ","name":"glob"}]},"duration":6518.882900000084,"ttft":1431.154600000009,"contextSnapshot":{"promptTokens":17080,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5ZLUc98B0VgTx5PcaQoq2xBJ|fc_08496348794f7e09016a519100680c8198ac0ca3a45917bdc6","toolName":"glob","startedAt":"2026-07-11T00:40:31.840Z","args":{"path":"spt-core/**/*.{rs,ts,js,py,go}"},"intent":"Map PTY source files"},"id":"8e8fbc65","parentId":"9e92bdbd","timestamp":"2026-07-11T00:40:31.840Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bBEsGls5xhcQNp62KS9PDJy3|fc_08496348794f7e09016a519100681c81988c6372acea34f79e","toolName":"grep","startedAt":"2026-07-11T00:40:31.840Z","args":{"path":"spt-core"},"intent":"Find PTY pump symbols"},"id":"5269af80","parentId":"8e8fbc65","timestamp":"2026-07-11T00:40:31.840Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_9QVfB71IoVab0jux3OfxCsQ9|fc_08496348794f7e09016a519100682481989766a299ea67f848","toolName":"grep","startedAt":"2026-07-11T00:40:31.841Z","args":{"path":"spt-core"},"intent":"Find inject verification"},"id":"9f35e539","parentId":"5269af80","timestamp":"2026-07-11T00:40:31.841Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_TSLdO8gma203mM6ULqmwXZYQ|fc_08496348794f7e09016a5191006828819886b97057717410e8","toolName":"glob","startedAt":"2026-07-11T00:40:31.841Z","args":{"path":"spt-core/.git/refs/**/*; spt-core/.git/packed-refs; spt-core/.git/logs/**/*"},"intent":"Locate repository history"},"id":"c696ce13","parentId":"9f35e539","timestamp":"2026-07-11T00:40:31.841Z"}
{"type":"message","id":"7f4f9eb4","parentId":"c696ce13","timestamp":"2026-07-11T00:40:31.880Z","message":{"role":"toolResult","toolCallId":"call_TSLdO8gma203mM6ULqmwXZYQ|fc_08496348794f7e09016a5191006828819886b97057717410e8","toolName":"glob","content":[{"type":"text","text":"# spt-core/.git/\npacked-refs\n## logs/\nHEAD\n### refs/\n#### heads/\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\npump-w2-design\npump-w1-fix\nmain\nmusl-w2\nmusl-w1\ngate-inject-rearm\nlifecycle-truth\nworker-truth\nremote-truth\nf030-psyche-ephemeral\n##### build/\nmsg-identity-w4-delivery\nmsg-identity-w2-f034\nmsg-identity-w5-f038\nmsg-identity-w3-f032\nmsg-identity-w1-f036\n##### triage/\nupdate-wedge-rca-trace\nmsg-identity-milestone\nupdate-wedge-rca-capture\nmusl-tier\n##### release/\nv0.30.4\n0.29.1\nv0.25.0\n##### test/\nuwedge-r4-stale-guard-swap\n##### release-merge/\nuwedge-r4\n##### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n##### spec/\nbrain-handoff-pilot\n##### docs/\nreorg-plans-process\n##### rca/\npump-w1-fastfail\n##### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n#### remotes/origin/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\ngateway-liveness\npump-w2-design\nlifecycle-truth\nworker-truth\nremote-truth\nf030-psyche-ephemeral\nf028-runtruth\n##### build/\nmsg-identity-w4-delivery\nmsg-identity-w3-f032\nmsg-identity-w2-f034\nmsg-identity-w1-f036\n##### triage/\nupdate-wedge-rca-trace\nmsg-identity-milestone\nmusl-tier\n##### release/\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.2\nv0.30.1\n0.29.1\nv0.25.0\n##### test/\nuwedge-r4-stale-guard-swap\n##### release-merge/\nuwedge-r4\n##### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n##### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n##### spec/\nbrain-handoff-pilot\n##### docs/\nreorg-plans-process\n##### rca/\npump-w1-fastfail\n## refs/\n### heads/\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\npump-w2-design\npump-w1-fix\nmain\nmusl-w2\nmusl-w1\ngate-inject-rearm\nlifecycle-truth\nworker-truth\nremote-truth\nf030-psyche-ephemeral\n#### build/\nmsg-identity-w4-delivery\nmsg-identity-w2-f034\nmsg-identity-w5-f038\nmsg-identity-w3-f032\nmsg-identity-w1-f036\n#### triage/\nupdate-wedge-rca-trace\nmsg-identity-milestone\nupdate-wedge-rca-capture\nmusl-tier\n#### release/\nv0.30.4\n0.29.1\nv0.25.0\n#### test/\nuwedge-r4-stale-guard-swap\n#### release-merge/\nuwedge-r4\n#### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n#### spec/\nbrain-handoff-pilot\n#### docs/\nreorg-plans-process\n#### rca/\npump-w1-fastfail\n#### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n### remotes/origin/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\ngateway-liveness\npump-w2-design\nlifecycle-truth\nworker-truth\nremote-truth\nf030-psyche-ephemeral\nf028-runtruth\n#### build/\nmsg-identity-w4-delivery\nmsg-identity-w3-f032\nmsg-identity-w2-f034\nmsg-identity-w1-f036\n#### triage/\nupdate-wedge-rca-trace\nmsg-identity-milestone\nmusl-tier\n#### release/\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.2\nv0.30.1\n0.29.1\nv0.25.0\n#### test/\nuwedge-r4-stale-guard-swap\n#### release-merge/\nuwedge-r4\n#### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\nchangelog-0220-wording\n#### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n#### spec/\nbrain-handoff-pilot\n#### docs/\nreorg-plans-process\n#### rca/\npump-w1-fastfail\n### tags/\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\n\n[200 results limit reached. Use limit=400 for more]"}],"details":{"scopePath":"spt-core/.git/refs/**/*, spt-core/.git/packed-refs, spt-core/.git/logs/**/*","fileCount":200,"files":["spt-core/.git/logs/HEAD","spt-core/.git/refs/heads/build/","spt-core/.git/refs/heads/build/msg-identity-w4-delivery","spt-core/.git/logs/refs/heads/build/msg-identity-w4-delivery","spt-core/.git/refs/heads/build/msg-identity-w2-f034","spt-core/.git/logs/refs/heads/build/msg-identity-w2-f034","spt-core/.git/refs/remotes/origin/","spt-core/.git/logs/refs/remotes/origin/main","spt-core/.git/refs/remotes/origin/main","spt-core/.git/refs/remotes/origin/build/","spt-core/.git/logs/refs/remotes/origin/build/msg-identity-w4-delivery","spt-core/.git/refs/remotes/origin/build/msg-identity-w4-delivery","spt-core/.git/logs/refs/heads/build/msg-identity-w5-f038","spt-core/.git/refs/heads/build/msg-identity-w5-f038","spt-core/.git/logs/refs/heads/build/","spt-core/.git/logs/refs/remotes/origin/build/","spt-core/.git/logs/refs/remotes/origin/build/msg-identity-w3-f032","spt-core/.git/refs/remotes/origin/build/msg-identity-w3-f032","spt-core/.git/logs/refs/heads/build/msg-identity-w3-f032","spt-core/.git/refs/heads/build/msg-identity-w3-f032","spt-core/.git/logs/refs/remotes/origin/build/msg-identity-w2-f034","spt-core/.git/refs/remotes/origin/build/msg-identity-w2-f034","spt-core/.git/logs/refs/remotes/origin/build/msg-identity-w1-f036","spt-core/.git/refs/remotes/origin/build/msg-identity-w1-f036","spt-core/.git/logs/refs/heads/build/msg-identity-w1-f036","spt-core/.git/refs/heads/build/msg-identity-w1-f036","spt-core/.git/logs/refs/heads/","spt-core/.git/refs/heads/","spt-core/.git/refs/remotes/origin/triage/","spt-core/.git/logs/refs/remotes/origin/triage/update-wedge-rca-trace","spt-core/.git/refs/remotes/origin/triage/update-wedge-rca-trace","spt-core/.git/logs/refs/remotes/origin/triage/","spt-core/.git/refs/heads/triage/","spt-core/.git/refs/heads/triage/update-wedge-rca-trace","spt-core/.git/logs/refs/heads/triage/update-wedge-rca-trace","spt-core/.git/logs/refs/remotes/origin/triage/msg-identity-milestone","spt-core/.git/refs/remotes/origin/triage/msg-identity-milestone","spt-core/.git/logs/refs/heads/triage/msg-identity-milestone","spt-core/.git/refs/heads/triage/msg-identity-milestone","spt-core/.git/logs/refs/heads/triage/","spt-core/.git/refs/heads/release/","spt-core/.git/logs/refs/heads/release/","spt-core/.git/packed-refs","spt-core/.git/refs/tags/","spt-core/.git/refs/tags/v0.30.6","spt-core/.git/refs/remotes/origin/release/","spt-core/.git/logs/refs/remotes/origin/release/v0.30.6","spt-core/.git/refs/remotes/origin/release/v0.30.6","spt-core/.git/refs/remotes/origin/test/","spt-core/.git/logs/refs/remotes/origin/test/uwedge-r4-stale-guard-swap","spt-core/.git/refs/remotes/origin/test/uwedge-r4-stale-guard-swap","spt-core/.git/refs/heads/test/","spt-core/.git/logs/refs/heads/test/uwedge-r4-stale-guard-swap","spt-core/.git/refs/heads/test/uwedge-r4-stale-guard-swap","spt-core/.git/logs/refs/remotes/origin/","spt-core/.git/logs/refs/remotes/origin/test/","spt-core/.git/logs/refs/heads/test/","spt-core/.git/logs/refs/remotes/origin/release/","spt-core/.git/refs/remotes/origin/release-merge/","spt-core/.git/logs/refs/remotes/origin/release-merge/uwedge-r4","spt-core/.git/logs/refs/remotes/origin/release-merge/","spt-core/.git/refs/remotes/origin/release-merge/uwedge-r4","spt-core/.git/refs/heads/release-merge/","spt-core/.git/refs/heads/release-merge/uwedge-r4","spt-core/.git/logs/refs/heads/release-merge/uwedge-r4","spt-core/.git/logs/refs/heads/release-merge/","spt-core/.git/refs/remotes/origin/fix/","spt-core/.git/logs/refs/remotes/origin/fix/update-wedge-bounded-conn-write","spt-core/.git/refs/remotes/origin/fix/update-wedge-bounded-conn-write","spt-core/.git/refs/heads/fix/","spt-core/.git/refs/heads/fix/update-wedge-bounded-conn-write","spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","spt-core/.git/logs/refs/remotes/origin/fix/","spt-core/.git/logs/refs/heads/fix/","spt-core/.git/logs/refs/heads/triage/update-wedge-rca-capture","spt-core/.git/refs/heads/triage/update-wedge-rca-capture","spt-core/.git/refs/tags/v0.30.5","spt-core/.git/refs/remotes/origin/release/v0.30.5","spt-core/.git/logs/refs/remotes/origin/release/v0.30.5","spt-core/.git/refs/heads/uwedge2-resume-viewer","spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","spt-core/.git/refs/tags/v0.30.4","spt-core/.git/logs/refs/remotes/origin/release/v0.30.4","spt-core/.git/refs/remotes/origin/release/v0.30.4","spt-core/.git/refs/heads/release/v0.30.4","spt-core/.git/logs/refs/heads/release/v0.30.4","spt-core/.git/refs/remotes/origin/uwedge2-resume-viewer","spt-core/.git/logs/refs/remotes/origin/uwedge2-resume-viewer","spt-core/.git/refs/tags/v0.30.3","spt-core/.git/refs/remotes/origin/update-wedge-drain-drive","spt-core/.git/logs/refs/remotes/origin/update-wedge-drain-drive","spt-core/.git/refs/heads/update-wedge-drain-drive","spt-core/.git/logs/refs/heads/update-wedge-drain-drive","spt-core/.git/refs/remotes/origin/f-035-window-drain-honor","spt-core/.git/logs/refs/remotes/origin/f-035-window-drain-honor","spt-core/.git/refs/heads/f-035-window-drain-honor","spt-core/.git/logs/refs/heads/f-035-window-drain-honor","spt-core/.git/logs/refs/","spt-core/.git/logs/refs/remotes/origin/gateway-liveness","spt-core/.git/refs/remotes/origin/gateway-liveness","spt-core/.git/refs/tags/v0.30.2","spt-core/.git/refs/remotes/origin/feat/","spt-core/.git/logs/refs/remotes/origin/feat/","spt-core/.git/logs/refs/remotes/origin/release/v0.30.2","spt-core/.git/refs/remotes/origin/release/v0.30.2","spt-core/.git/logs/refs/remotes/origin/pump-w2-design","spt-core/.git/refs/remotes/origin/pump-w2-design","spt-core/.git/logs/refs/heads/pump-w2-design","spt-core/.git/refs/heads/pump-w2-design","spt-core/.git/refs/remotes/origin/spec/","spt-core/.git/logs/refs/remotes/origin/spec/brain-handoff-pilot","spt-core/.git/refs/remotes/origin/spec/brain-handoff-pilot","spt-core/.git/refs/heads/spec/","spt-core/.git/logs/refs/heads/spec/brain-handoff-pilot","spt-core/.git/refs/heads/spec/brain-handoff-pilot","spt-core/.git/refs/tags/v0.30.1","spt-core/.git/refs/remotes/origin/release/v0.30.1","spt-core/.git/logs/refs/remotes/origin/release/v0.30.1","spt-core/.git/logs/refs/heads/pump-w1-fix","spt-core/.git/refs/heads/pump-w1-fix","spt-core/.git/logs/refs/remotes/origin/spec/","spt-core/.git/logs/refs/heads/main","spt-core/.git/refs/heads/main","spt-core/.git/logs/refs/heads/spec/","spt-core/.git/refs/remotes/origin/docs/","spt-core/.git/logs/refs/remotes/origin/docs/reorg-plans-process","spt-core/.git/refs/remotes/origin/docs/reorg-plans-process","spt-core/.git/refs/heads/docs/","spt-core/.git/refs/heads/docs/reorg-plans-process","spt-core/.git/logs/refs/heads/docs/reorg-plans-process","spt-core/.git/logs/refs/remotes/origin/docs/","spt-core/.git/logs/refs/heads/docs/","spt-core/.git/refs/heads/rca/","spt-core/.git/logs/refs/heads/rca/pump-w1-fastfail","spt-core/.git/refs/heads/rca/pump-w1-fastfail","spt-core/.git/refs/remotes/origin/rca/","spt-core/.git/logs/refs/remotes/origin/rca/pump-w1-fastfail","spt-core/.git/refs/remotes/origin/rca/pump-w1-fastfail","spt-core/.git/refs/tags/v0.30.0","spt-core/.git/refs/remotes/origin/feat/musl-w3-release-artifact","spt-core/.git/logs/refs/remotes/origin/feat/musl-w3-release-artifact","spt-core/.git/refs/heads/feat/","spt-core/.git/logs/refs/heads/feat/musl-w3-release-artifact","spt-core/.git/refs/heads/feat/musl-w3-release-artifact","spt-core/.git/logs/refs/remotes/origin/rca/","spt-core/.git/logs/refs/heads/rca/","spt-core/.git/logs/refs/heads/feat/","spt-core/.git/logs/refs/heads/musl-w2","spt-core/.git/refs/heads/musl-w2","spt-core/.git/logs/refs/heads/musl-w1","spt-core/.git/refs/heads/musl-w1","spt-core/.git/refs/remotes/origin/feat/musl-w1-platform-registry","spt-core/.git/logs/refs/remotes/origin/feat/musl-w1-platform-registry","spt-core/.git/logs/refs/heads/feat/musl-w1-platform-registry","spt-core/.git/refs/heads/feat/musl-w1-platform-registry","spt-core/.git/logs/refs/remotes/origin/triage/musl-tier","spt-core/.git/refs/remotes/origin/triage/musl-tier","spt-core/.git/logs/refs/heads/triage/musl-tier","spt-core/.git/refs/heads/triage/musl-tier","spt-core/.git/refs/tags/v0.29.1","spt-core/.git/refs/remotes/origin/release/0.29.1","spt-core/.git/logs/refs/remotes/origin/release/0.29.1","spt-core/.git/refs/heads/release/0.29.1","spt-core/.git/logs/refs/heads/release/0.29.1","spt-core/.git/logs/refs/remotes/origin/fix/inject-settle-rearm","spt-core/.git/refs/remotes/origin/fix/inject-settle-rearm","spt-core/.git/logs/refs/heads/gate-inject-rearm","spt-core/.git/refs/heads/gate-inject-rearm","spt-core/.git/logs/refs/heads/fix/inject-settle-rearm","spt-core/.git/refs/heads/fix/inject-settle-rearm","spt-core/.git/logs/refs/remotes/origin/lifecycle-truth","spt-core/.git/refs/remotes/origin/lifecycle-truth","spt-core/.git/logs/refs/heads/lifecycle-truth","spt-core/.git/refs/heads/lifecycle-truth","spt-core/.git/refs/tags/v0.29.0","spt-core/.git/refs/tags/v0.28.0","spt-core/.git/refs/tags/v0.27.0","spt-core/.git/logs/refs/remotes/origin/worker-truth","spt-core/.git/refs/remotes/origin/worker-truth","spt-core/.git/logs/refs/heads/worker-truth","spt-core/.git/refs/heads/worker-truth","spt-core/.git/refs/tags/v0.26.0","spt-core/.git/logs/refs/remotes/origin/remote-truth","spt-core/.git/refs/remotes/origin/remote-truth","spt-core/.git/logs/refs/heads/remote-truth","spt-core/.git/refs/heads/remote-truth","spt-core/.git/logs/refs/heads/f030-psyche-ephemeral","spt-core/.git/refs/heads/f030-psyche-ephemeral","spt-core/.git/refs/tags/v0.25.0","spt-core/.git/refs/remotes/origin/release/v0.25.0","spt-core/.git/logs/refs/remotes/origin/release/v0.25.0","spt-core/.git/refs/heads/release/v0.25.0","spt-core/.git/logs/refs/heads/release/v0.25.0","spt-core/.git/logs/refs/remotes/origin/f030-psyche-ephemeral","spt-core/.git/refs/remotes/origin/f030-psyche-ephemeral","spt-core/.git/refs/tags/v0.24.0","spt-core/.git/refs/remotes/origin/f028-runtruth","spt-core/.git/logs/refs/remotes/origin/f028-runtruth","spt-core/.git/refs/tags/v0.23.0","spt-core/.git/refs/remotes/origin/fix/changelog-0220-wording"],"truncated":true,"resultLimitReached":200,"cwd":"C:\\Users\\decid\\Documents\\projects","meta":{"limits":{"resultLimit":{"reached":200,"suggestion":400}}}},"isError":false,"timestamp":1783730431880}}
{"type":"message","id":"eb858de7","parentId":"7f4f9eb4","timestamp":"2026-07-11T00:40:31.901Z","message":{"role":"toolResult","toolCallId":"call_5ZLUc98B0VgTx5PcaQoq2xBJ|fc_08496348794f7e09016a519100680c8198ac0ca3a45917bdc6","toolName":"glob","content":[{"type":"text","text":"# spt-core/crates/\n## spt-live/src/\ningest.rs\npulse.rs\necho.rs\nturn.rs\ntest_support.rs\n## spt/\n### src/\nroster.rs\ncli.rs\nrc.rs\nwansend.rs\n#### api/\nstartup.rs\nreporting.rs\nauth.rs\nmod.rs\nworker.rs\ndelivery.rs\n#### picker/\nmod.rs\ndata.rs\nview.rs\nmodel.rs\n### tests/\nlisten_seed_retry_e2e.rs\ntrial_drain_drive_e2e.rs\nresume_no_control_steal_e2e.rs\nlist_json_liveness_parity_e2e.rs\nactive_only_never_relay_e2e.rs\nn1_pairing.rs\nattach_wedge_e2e.rs\nworker_visibility_e2e.rs\nworker_lifecycle_e2e.rs\ntwohost_cli.rs\ntranslate_proof.rs\npsyche_download_e2e.rs\nnotify_shell_e2e.rs\nmulti_subnet_bringup_e2e.rs\nlive_adapt_translation_swap_e2e.rs\ndummy_harness_e2e.rs\nbrain_survive.rs\n## spt-store/src/\nspool.rs\npsyche_custody.rs\npeeraddrs.rs\nlib.rs\nworker_reap.rs\nworker_seq.rs\ninfo.rs\nsessions.rs\nxfer.rs\nvisibility.rs\ntimefmt.rs\nsyncmerge.rs\nsubnet.rs\nshellinfo.rs\nseed.rs\nrotation.rs\nroster.rs\nrename.rs\nregistry.rs\nrecent_home.rs\nproject.rs\nproc.rs\nperch.rs\nnotif.rs\nnodeid.rs\nliveness.rs\nhostlabel.rs\nhome.rs\nhistory.rs\ngrants.rs\ngitrun.rs\nepoch.rs\ndb.rs\ncontextstore.rs\ncontextmark.rs\nbranchstore.rs\nattachment.rs\natomic.rs\naccess.rs\n## spt-daemon/\n### tests/\ninject_control_wedge.rs\npumpdeadline.rs\npump.rs\nnet_worker_starve.rs\nfalse_promote.rs\nbrain_decouple.rs\nbrain_resume_conn_deadlock.rs\npropagate.rs\ntwo_origin_spanning.rs\nwake_single_flight.rs\nendpoint_survival.rs\nxfer.rs\nwanmsg.rs\ntwohost.rs\nreplicate.rs\nsync.rs\nnotifsync.rs\nnetstream.rs\nnetbroker.rs\ninput_ack_deadlock.rs\nidempotent.rs\ndispatch.rs\ndaemon_e2e.rs\nbroker.rs\nattach.rs\naccess.rs\n### src/\ninject.rs\nlib.rs\ndaemon.rs\nconfig.rs\nsync.rs\nrelay.rs\npropagate.rs\nnethost.rs\nmsg.rs\nlivehost.rs\nconn.rs\nbroker.rs\nbrainproc.rs\nbrain.rs\nrelease.rs\napplyhost.rs\nstderrlog.rs\nlifecycle.rs\ndigest.rs\npairhost.rs\nxfer.rs\nshellwake.rs\nshellchan.rs\nseedmap.rs\nresting.rs\nresthost.rs\nregistryhost.rs\nnotif.rs\nlinkhost.rs\neffect.rs\nattach.rs\nadapter_update.rs\n#### pump/\nmod.rs\n#### bin/\nxlate_choreo_fixture.rs\n## spt-runtime/src/\nresolve.rs\nruntime.rs\nlib.rs\nmanifest.rs\nregistry.rs\nprofile.rs\n## spt-msg/\n### src/\ndeliver.rs\nready.rs\nwire.rs\nring.rs\nlistener.rs\nlib.rs\nemit.rs\n### tests/\nkiller_quickstart.rs\n## xtask/src/\nmain.rs\n## spt-net/src/\nnet.rs\nlib.rs\n### net/\nregistry.rs\nxfer.rs\nwanmsg.rs\nupdate.rs\nsync.rs\nshelllink.rs\nserveprobe.rs\nrest.rs\nreplicate.rs\npairing.rs\nnotif.rs\nndjson.rs\nmesh.rs\nendpoint.rs\nattach.rs\n#### pairing/\nntp.rs\nwire.rs\ntranscript.rs\ntotp.rs\nspake.rs\nrendezvous.rs\nratelimit.rs\nmeet.rs\n#### mesh/\nseedproof.rs\n## spt-term/\n### src/\nscreen.rs\nwinprog.rs\nsurface.rs\nstream.rs\nreader.rs\npty.rs\nprojection.rs\nlib.rs\ndigest.rs\n### tests/\nwinspawn.rs\nsurface.rs\nstream.rs\ninject.rs\ndsr.rs\n## spt-proto/src/\nversion.rs\npayload.rs\nlib.rs\nidentity.rs\nid.rs\nevent.rs\nenvelope.rs\nendpoint.rs\nchunk.rs\naddr.rs\n\n[200 results limit reached. Use limit=400 for more]"}],"details":{"scopePath":"spt-core","fileCount":200,"files":["spt-core/crates/spt-live/src/ingest.rs","spt-core/crates/spt/src/roster.rs","spt-core/crates/spt-store/src/spool.rs","spt-core/crates/spt/src/cli.rs","spt-core/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/crates/spt-daemon/src/inject.rs","spt-core/crates/spt/src/api/startup.rs","spt-core/crates/spt/src/api/reporting.rs","spt-core/crates/spt/src/api/auth.rs","spt-core/crates/spt-live/src/pulse.rs","spt-core/crates/spt/src/picker/mod.rs","spt-core/crates/spt-daemon/src/lib.rs","spt-core/crates/spt-daemon/src/daemon.rs","spt-core/crates/spt-daemon/src/config.rs","spt-core/crates/spt/tests/listen_seed_retry_e2e.rs","spt-core/crates/spt-runtime/src/resolve.rs","spt-core/crates/spt/src/api/mod.rs","spt-core/crates/spt-store/src/psyche_custody.rs","spt-core/crates/spt-runtime/src/runtime.rs","spt-core/crates/spt-runtime/src/lib.rs","spt-core/crates/spt-runtime/src/manifest.rs","spt-core/crates/spt/tests/trial_drain_drive_e2e.rs","spt-core/crates/spt/tests/resume_no_control_steal_e2e.rs","spt-core/crates/spt/tests/list_json_liveness_parity_e2e.rs","spt-core/crates/spt/tests/active_only_never_relay_e2e.rs","spt-core/crates/spt-store/src/peeraddrs.rs","spt-core/crates/spt-msg/src/deliver.rs","spt-core/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/crates/spt-daemon/tests/pump.rs","spt-core/crates/spt-daemon/tests/net_worker_starve.rs","spt-core/crates/spt-daemon/tests/false_promote.rs","spt-core/crates/spt-daemon/tests/brain_decouple.rs","spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/crates/spt-daemon/src/sync.rs","spt-core/crates/spt-daemon/src/relay.rs","spt-core/crates/spt-daemon/src/pump/mod.rs","spt-core/crates/spt-daemon/src/propagate.rs","spt-core/crates/spt-daemon/src/nethost.rs","spt-core/crates/spt-daemon/src/msg.rs","spt-core/crates/spt-daemon/src/livehost.rs","spt-core/crates/spt-daemon/src/conn.rs","spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/brainproc.rs","spt-core/crates/spt-daemon/src/brain.rs","spt-core/crates/xtask/src/main.rs","spt-core/crates/spt-daemon/tests/propagate.rs","spt-core/crates/spt-daemon/src/release.rs","spt-core/crates/spt-daemon/src/applyhost.rs","spt-core/crates/spt/tests/n1_pairing.rs","spt-core/crates/spt/tests/attach_wedge_e2e.rs","spt-core/crates/spt/src/rc.rs","spt-core/crates/spt/src/api/worker.rs","spt-core/crates/spt/src/api/delivery.rs","spt-core/crates/spt-msg/src/ready.rs","spt-core/crates/spt-live/src/echo.rs","spt-core/crates/spt-daemon/tests/two_origin_spanning.rs","spt-core/crates/spt-daemon/tests/wake_single_flight.rs","spt-core/crates/spt-daemon/tests/endpoint_survival.rs","spt-core/crates/spt-daemon/src/stderrlog.rs","spt-core/crates/spt-daemon/src/lifecycle.rs","spt-core/crates/spt-daemon/src/digest.rs","spt-core/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","spt-core/crates/spt-net/src/net/pairing/ntp.rs","spt-core/crates/spt-daemon/src/pairhost.rs","spt-core/crates/spt/tests/worker_visibility_e2e.rs","spt-core/crates/spt/src/picker/data.rs","spt-core/crates/spt/tests/worker_lifecycle_e2e.rs","spt-core/crates/spt-store/src/lib.rs","spt-core/crates/spt-store/src/worker_reap.rs","spt-core/crates/spt-store/src/worker_seq.rs","spt-core/crates/spt-store/src/info.rs","spt-core/crates/spt-runtime/src/registry.rs","spt-core/crates/spt/tests/twohost_cli.rs","spt-core/crates/spt/tests/translate_proof.rs","spt-core/crates/spt/tests/psyche_download_e2e.rs","spt-core/crates/spt/tests/notify_shell_e2e.rs","spt-core/crates/spt/tests/multi_subnet_bringup_e2e.rs","spt-core/crates/spt/tests/live_adapt_translation_swap_e2e.rs","spt-core/crates/spt/tests/dummy_harness_e2e.rs","spt-core/crates/spt/tests/brain_survive.rs","spt-core/crates/spt/src/wansend.rs","spt-core/crates/spt/src/picker/view.rs","spt-core/crates/spt/src/picker/model.rs","spt-core/crates/spt-term/src/screen.rs","spt-core/crates/spt-store/src/sessions.rs","spt-core/crates/spt-net/src/net/registry.rs","spt-core/crates/spt-daemon/tests/xfer.rs","spt-core/crates/spt-daemon/tests/wanmsg.rs","spt-core/crates/spt-daemon/tests/twohost.rs","spt-core/crates/spt-daemon/tests/replicate.rs","spt-core/crates/spt-daemon/tests/sync.rs","spt-core/crates/spt-daemon/tests/notifsync.rs","spt-core/crates/spt-daemon/tests/netstream.rs","spt-core/crates/spt-daemon/tests/netbroker.rs","spt-core/crates/spt-daemon/tests/input_ack_deadlock.rs","spt-core/crates/spt-daemon/tests/idempotent.rs","spt-core/crates/spt-daemon/tests/dispatch.rs","spt-core/crates/spt-daemon/tests/daemon_e2e.rs","spt-core/crates/spt-daemon/tests/broker.rs","spt-core/crates/spt-daemon/tests/attach.rs","spt-core/crates/spt-daemon/tests/access.rs","spt-core/crates/spt-daemon/src/xfer.rs","spt-core/crates/spt-daemon/src/shellwake.rs","spt-core/crates/spt-daemon/src/shellchan.rs","spt-core/crates/spt-daemon/src/seedmap.rs","spt-core/crates/spt-daemon/src/resting.rs","spt-core/crates/spt-daemon/src/resthost.rs","spt-core/crates/spt-daemon/src/registryhost.rs","spt-core/crates/spt-daemon/src/notif.rs","spt-core/crates/spt-daemon/src/linkhost.rs","spt-core/crates/spt-daemon/src/effect.rs","spt-core/crates/spt-daemon/src/attach.rs","spt-core/crates/spt-daemon/src/adapter_update.rs","spt-core/crates/spt-term/tests/winspawn.rs","spt-core/crates/spt-term/tests/surface.rs","spt-core/crates/spt-term/tests/stream.rs","spt-core/crates/spt-term/tests/inject.rs","spt-core/crates/spt-term/tests/dsr.rs","spt-core/crates/spt-term/src/winprog.rs","spt-core/crates/spt-term/src/surface.rs","spt-core/crates/spt-term/src/stream.rs","spt-core/crates/spt-term/src/reader.rs","spt-core/crates/spt-term/src/pty.rs","spt-core/crates/spt-term/src/projection.rs","spt-core/crates/spt-term/src/lib.rs","spt-core/crates/spt-term/src/digest.rs","spt-core/crates/spt-store/src/xfer.rs","spt-core/crates/spt-store/src/visibility.rs","spt-core/crates/spt-store/src/timefmt.rs","spt-core/crates/spt-store/src/syncmerge.rs","spt-core/crates/spt-store/src/subnet.rs","spt-core/crates/spt-store/src/shellinfo.rs","spt-core/crates/spt-store/src/seed.rs","spt-core/crates/spt-store/src/rotation.rs","spt-core/crates/spt-store/src/roster.rs","spt-core/crates/spt-store/src/rename.rs","spt-core/crates/spt-store/src/registry.rs","spt-core/crates/spt-store/src/recent_home.rs","spt-core/crates/spt-store/src/project.rs","spt-core/crates/spt-store/src/proc.rs","spt-core/crates/spt-store/src/perch.rs","spt-core/crates/spt-store/src/notif.rs","spt-core/crates/spt-store/src/nodeid.rs","spt-core/crates/spt-store/src/liveness.rs","spt-core/crates/spt-store/src/hostlabel.rs","spt-core/crates/spt-store/src/home.rs","spt-core/crates/spt-store/src/history.rs","spt-core/crates/spt-store/src/grants.rs","spt-core/crates/spt-store/src/gitrun.rs","spt-core/crates/spt-store/src/epoch.rs","spt-core/crates/spt-store/src/db.rs","spt-core/crates/spt-store/src/contextstore.rs","spt-core/crates/spt-store/src/contextmark.rs","spt-core/crates/spt-store/src/branchstore.rs","spt-core/crates/spt-store/src/attachment.rs","spt-core/crates/spt-store/src/atomic.rs","spt-core/crates/spt-store/src/access.rs","spt-core/crates/spt-runtime/src/profile.rs","spt-core/crates/spt-proto/src/version.rs","spt-core/crates/spt-proto/src/payload.rs","spt-core/crates/spt-proto/src/lib.rs","spt-core/crates/spt-proto/src/identity.rs","spt-core/crates/spt-proto/src/id.rs","spt-core/crates/spt-proto/src/event.rs","spt-core/crates/spt-proto/src/envelope.rs","spt-core/crates/spt-proto/src/endpoint.rs","spt-core/crates/spt-proto/src/chunk.rs","spt-core/crates/spt-proto/src/addr.rs","spt-core/crates/spt-net/src/net.rs","spt-core/crates/spt-net/src/net/xfer.rs","spt-core/crates/spt-net/src/net/wanmsg.rs","spt-core/crates/spt-net/src/net/update.rs","spt-core/crates/spt-net/src/net/sync.rs","spt-core/crates/spt-net/src/net/shelllink.rs","spt-core/crates/spt-net/src/net/serveprobe.rs","spt-core/crates/spt-net/src/net/rest.rs","spt-core/crates/spt-net/src/net/replicate.rs","spt-core/crates/spt-net/src/net/pairing.rs","spt-core/crates/spt-net/src/net/pairing/wire.rs","spt-core/crates/spt-net/src/net/pairing/transcript.rs","spt-core/crates/spt-net/src/net/pairing/totp.rs","spt-core/crates/spt-net/src/net/pairing/spake.rs","spt-core/crates/spt-net/src/net/pairing/rendezvous.rs","spt-core/crates/spt-net/src/net/pairing/ratelimit.rs","spt-core/crates/spt-net/src/net/pairing/meet.rs","spt-core/crates/spt-net/src/net/notif.rs","spt-core/crates/spt-net/src/net/ndjson.rs","spt-core/crates/spt-net/src/net/mesh.rs","spt-core/crates/spt-net/src/net/mesh/seedproof.rs","spt-core/crates/spt-net/src/net/endpoint.rs","spt-core/crates/spt-net/src/net/attach.rs","spt-core/crates/spt-net/src/lib.rs","spt-core/crates/spt-msg/tests/killer_quickstart.rs","spt-core/crates/spt-msg/src/wire.rs","spt-core/crates/spt-msg/src/ring.rs","spt-core/crates/spt-msg/src/listener.rs","spt-core/crates/spt-msg/src/lib.rs","spt-core/crates/spt-msg/src/emit.rs","spt-core/crates/spt-live/src/turn.rs","spt-core/crates/spt-live/src/test_support.rs"],"truncated":true,"resultLimitReached":200,"cwd":"C:\\Users\\decid\\Documents\\projects","meta":{"limits":{"resultLimit":{"reached":200,"suggestion":400}}}},"isError":false,"timestamp":1783730431900}}
{"type":"message","id":"fe624f0e","parentId":"eb858de7","timestamp":"2026-07-11T00:40:33.322Z","message":{"role":"toolResult","toolCallId":"call_bBEsGls5xhcQNp62KS9PDJy3|fc_08496348794f7e09016a519100681c81988c6372acea34f79e","toolName":"grep","content":[{"type":"text","text":"# spt-core/\n## CHANGELOG.md\n 16|\n*17|> **Applying this update needs a daemon restart.** Unlike the recent seamless\n 18|> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n 19|> --apply` lands it via a full daemon restart rather than a live swap — your\n 20|> sessions restart as the daemon comes back. This is a one-time cost to install\n...\n 46|  fix lives in the incoming version, updating *to* this release is what makes your next\n*47|  in-place update seamless.)\n 48|\n 49|## [0.30.4] - 2026-07-09\n 50|\n...\n 60|  session's controller, so your open sessions and `spt rc` keep working across the\n*61|  brain swap. (Follow-up to the previous release's seamless-swap fix.)\n 62|\n 63|## [0.30.3] - 2026-07-09\n 64|\n...\n 72|  completing the swap. The incoming version now drives the running daemon to clear a\n*73|  hard-stopped prior session during the handoff, so the update completes seamlessly with\n 74|  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\n 75|  incoming version's logic, updating *to* this release is what makes your next in-place\n*76|  update seamless.)\n 77|\n 78|- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\n 79|  \"never wakes an idle target\") could still be delivered to a target the instant it went\n...\n 112|  would stall the whole peer-sync round, so `spt subnet status` listed the subnet's\n*113|  members but showed an empty live-node list and warned that the peer pump had\n 114|  stalled. An unresponsive peer is now dropped and retried on its own instead of\n 115|  holding up the round, so the node keeps converging and peers reappear.\n 116|\n...\n 158|- **Hosted sessions are re-launched after a daemon restart.** Previously-online sessions come back automatically once the daemon restarts (including as part of an update) instead of being left offline. The restart still interrupts them — this restores the session, it does not preserve its in-flight work.\n*159|- **An attached terminal no longer freezes behind a stalled session.** If a session's underlying process hangs or is suspended, `spt rc` keeps updating and a stuck connection is dropped automatically so you never lose control; reattaching and taking control (`--take`) keep working. `spt daemon status` reports when such a drop happened.\n 160|- **A long, multi-line message typed into a live agent's terminal arrives complete.** When a message is delivered by typing it into a live session, it is no longer truncated at the front. (Messages drained through the polling channel were never affected.)\n 161|- **`spt endpoint digest --json` no longer repeats rows.** Activity replayed across a session checkpoint is collapsed to a single entry.\n 162|- **Waking an agent starts exactly one session.** Two wake requests arriving at once no longer launch it twice.\n...\n 272|- **Message delivery and scheduled wake-ups now survive a context reset.** After a context reset, an idle agent could stop receiving messages and scheduled wake-ups until its session was restarted; the session boundary now re-stamps its readiness in the correct order across the reset, so messages and wakes right after a reset are delivered instead of dropped.\n*273|- **A `--force-native` send that cannot be delivered now says why.** The failure message distinguishes its cause — empty message, no daemon running, endpoint active mid-turn, no working translation binary, or not a controllable endpoint — instead of a single identical message for every case.\n 274|\n 275|## [0.23.0] - 2026-07-03\n 276|\n...\n 326|- **`spt endpoint list --show-all`.** Suspended (resting) endpoints are now hidden by default to cut clutter; `--show-all` reveals them. Each machine's total discloses how many were hidden (nothing silently vanishes), and a corrupt record always shows regardless.\n*327|- **`spt daemon status` now reports the running daemon's version.** It shows the version the running daemon was built from beside the installed version and flags a mismatch — so you can tell when an update is on disk but the daemon still needs a restart to fully load it. The `--json` output gains matching `broker_image` / `broker_stale` fields.\n 328|\n 329|### Changed\n 330|\n...\n 349|\n*350|- **`spt send` with no explicit sender now stamps `cli@<node>`.** A delivered message never shows a blank sender — a message sent without a from-identity is attributed to the originating node instead of arriving empty.\n 351|- **Clearer delivery diagnostics.** Spool and idle-drain failures now emit loud, one-shot log lines, and operator notes on message-delivery timing are documented.\n 352|\n 353|### Fixed\n...\n 386|\n*387|- **Attaching to a running terminal session no longer corrupts the scrollback.** Cold-attaching to a full-screen TUI (e.g. Claude Code) used to replay the raw output ring into a fresh terminal — flipping the alternate screen on and off mid-stream and spilling TUI frames into history. The broker now keeps an authoritative screen model and synthesizes a clean repaint of the current screen on a cold attach, so you get the live frame, not a corrupt transcript. (A resume from a known point still re-fetches ...\n 388|- **`spt rc` to an already-running endpoint no longer prints garbled escape codes on Windows 10 / raw PowerShell.** The garbling was the same raw-ring replay problem as above (not a client terminal-mode issue — `spt endpoint run --attach` rendered fine in the same terminal); the clean-repaint-on-attach fix resolves it. VT output is also enabled defensively on legacy Windows consoles.\n*389|- **`spt rc` to a live local endpoint no longer times out with `brain IPC read deadline elapsed` after a self-update.** A single panic while the broker's effect journal was locked could poison it and brick every subsequent attach; the journal now recovers from a poisoned lock, and the loopback attach path fails fast with a real error instead of an opaque 10-second deadline.\n 390|- **`spt send` across nodes no longer reports `SENT(WAN)` when nothing was delivered.** A cross-node send was a fire-and-forget local buffer write — a refused or no-perch delivery was silently dropped while the sender printed success. The receiver now writes the delivery outcome back and the sender waits for it, so a real failure prints an honest line; the dial also tries the last-known direct address first (mirroring the gossip path) instead of forcing a cold discovery on every send.\n 391|- **The remote endpoint count no longer drifts as endpoints are added and purged.** A remote viewer's `--nodes` count counted non-routable ghost rows in the denominator and never evicted purged endpoints, producing wrong ratios like `0/2` or `1/3`. The count now uses a routable-only denominator, and rows left offline past a grace window are evicted from the gossiped snapshot instead of leaking forever.\n*392|- **A locally-controlled endpoint no longer shows as \"ready to control\" on other machines.** Remote viewers only learned about a *remote* controller; a locally-driven endpoint gossiped as uncontrolled. The broker now advertises whether *anyone* (local or remote) is driving an endpoint, so remote viewers see it as controlled.\n 393|- **`spt adapter add` no longer swallows install errors.** Errors from the install-as-first-update step were discarded; the failing step's output is now surfaced, and the composite post-install step runs at install time.\n*394|- **`spt adapter update` no longer fails to re-register after fetching.** The update derived the install directory from the `[update]` repo name rather than the adapter's registered source directory — so after a repo rename it wrote to a fresh empty directory and then failed re-register with `os error 2`. Update now targets the registered source directory and tolerates a changed update repo.\n 395|- **`spt endpoint digest` now works for endpoints whose transcript lives under a relocated profile.** A profile that relocates the harness transcript tree (via a runtime env like `CLAUDE_CONFIG_DIR`) produced `NO_DIGEST` because the on-demand extractor ran without that environment. The digest path now carries the profile's transcript-location environment to the extractor, which locates and reads the transcript itself.\n 396|\n 397|## [0.18.0] - 2026-06-30\n...\n 441|- **`[message-idle-translation-binary]` takes a `command`.** The idle-delivery translation binary can be declared with a `command` (program token plus args, with adapter-static substitution) instead of the bare `path`, so it can be invoked as a subcommand of a consolidated adapter binary. The spawn and stdin/stdout protocol are unchanged.\n*442|- **Empty-scope creation flow.** Running `spt endpoint run` (or bare `spt`) on a node with no endpoints at all now opens directly on the adapter-creation screen instead of an empty picker.\n 443|\n 444|### Changed\n 445|\n...\n 541|\n*542|- **Idle message delivery for spt-hosted endpoints now uses a translation binary.** An adapter can declare a `[message-idle-translation-binary]`; spt-core brings it up alongside the endpoint, where it polls the relay for incoming messages and optionally emits keypresses, delays, and text injection that spt-core applies to the endpoint's terminal. Idle delivery now flows through the relay poll for every endpoint, instead of spt-hosted endpoints falling back to direct PTY injection.\n 543|- **Windows paste in `spt rc`.** Ctrl+V and right-click now paste the local clipboard into the attached agent as a single bracketed paste.\n 544|- **Windows special keys in `spt rc`.** Arrow keys, Home/End, Delete, function keys and other special keys are translated to terminal sequences and reach the agent — previously only plain characters got through.\n 545|- **`spt rc` forwards mouse scroll** to the agent's terminal.\n...\n 549|- **Pasting or typing into an spt-hosted endpoint no longer freezes the daemon.** A large paste or input burst could wedge the daemon's input path so every new attach died; input now runs on a dedicated per-session writer and never blocks the daemon (this also covers the effect-journal stall on interactive input).\n*550|- **Session resume actually resumes now.** Agent endpoints track session history and offer explicit session resume via `spt endpoint run` (`--resume <session>` or *Resume from history*); the adapter manifest can now declare a `[session.resume]` command so the relaunch reattaches the prior session instead of always starting a new one. The run picker now shows each row's working directory and local time.\n 551|- **`spt rc --view` viewers survive a high-output terminal.** A viewer no longer dies when the output backlog rolls over or it is briefly evicted — it snaps forward or skips to live instead of failing — and a slow controller can no longer starve a concurrent viewer.\n 552|- **`spt rc` no longer races a just-started agent.** Attach now waits for the endpoint to come online, instead of failing when you attach immediately after `spt endpoint run`.\n 553|- **The daemon no longer flashes or respawns a console window on Windows.**\n...\n 600|- **`spt send` now delivers to an agent whose terminal is hosted by the daemon.** When the target has no `spt api listen` relay (the daemon holds its terminal directly), `spt send` previously queued the message silently. It now injects the message into the agent's session and reports \"Sent\" only once delivery is confirmed — otherwise it queues it (\"Queued\") as before, and never reports a false \"Sent\".\n*601|- **`spt endpoint run` now fills in `[env]` values.** Placeholders such as `{id}` in an adapter's `[env]` entries (for example `SPT_ENDPOINT_ID`) were never substituted, so an agent launched without explicit flags came up with an empty endpoint id and never registered. The values are now substituted and set on the launched process.\n 602|- **A daemon-hosted terminal now reports a clear message when the daemon is stopped,** instead of failing with a raw \"failed to fill whole buffer\" that looked like a crash.\n 603|- **Clearer error when an adapter's manifest has not been extracted yet.** Using such an adapter now reports an actionable message (and logs it as skipped) instead of a raw \"os error 2\" and a silent drop.\n 604|- **A removed endpoint no longer lingers with a stale status** — it is now shown as offline.\n...\n 620|\n*621|- **The picker now loads each agent's project history** — it previously always showed empty.\n 622|- **A self-owned agent listed under both Local and Subnet no longer shows conflicting status.** The live local status is now authoritative for both listings.\n 623|\n 624|## [0.9.1] - 2026-06-17\n...\n 712|\n*713|- **`spt adapter digest-proof` now fills the same substitution keys the live extractor does.** A proof run previously supplied an empty key map, so any extractor template using `{session_id}` (the shape in the published examples) failed instead of producing a sample. It now populates `{id}` and `{session_id}` to match runtime, with an optional `--session` to pin a specific value.\n 714|\n 715|## [0.7.1] - 2026-06-15\n 716|\n## CI-SELFHOST-PLAN.md\n 64|      runner + target).\n*65|- [ ] Decide whether the netstream/netbroker UDP-binding tests need a firewall\n 66|      allowance on gravity (loopback-only since BindScope::Loopback — no).\n## CONTEXT.md\n 2|\n*3|**Platform scope:** Windows + Linux for v1. macOS is out (no test machine available) but kept structurally easy — `portable-pty` and Iroh both support it, so macOS is a later test/CI-budget decision, not a re-architecture.\n 4|\n 5|**Legacy migration:** it should be possible — ideally *automatic* — for a user to migrate an existing `claude_skill_owl` (modern SPT) install to spt-core (identity, agents, tracked Psyche context). Exact mechanism deferred to design; the commitment is that migration is a first-class supported path, not a manual rebuild.\n 6|\n...\n 29|**spt-daemon** (per-machine supervisor):\n*30|The single always-on, one-per-machine logical supervisor. Owns the PTYs for all hosted sessions, the node's network identity + WAN endpoint, the subnet registry, all spools, **all poll-listener logic, and all Psyche/pulse loops** — everything is consolidated here (no separate poll-listener or Psyche-wrapper processes; listeners already touch sessions directly under capsule/idle, and Psyche wrappers already invoke harness binaries directly, so they belong in the one supervisor). Collapses what the siste...\n 31|\n*32|Internally the logical daemon is split into two implementation layers for seamless self-update (see Self-update):\n*33|- **broker** (stable \"kernel\") — holds *only* the un-transferable, must-not-die resources: PTY master fds, the spawned harness child processes, and listening network sockets. Minimal, dumb, versioned local IPC. Almost never updates.\n*34|- **daemon brain** (\"userspace\") — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk state and re-attaches to the broker's held handles.\n 35|\n*36|Logical addressing is unchanged — still one per-machine `spt-daemon`; the broker is an internal layer, not separately addressable. There is exactly **one broker per machine** (per `SPT_HOME`) — *not* one per endpoint: a single broker holds every hosted endpoint's resources, and it is present whenever the daemon runs, even with zero endpoints online (the bare-daemon case). It is therefore the always-present per-machine layer, which is why the single-daemon lock + liveness anchor belong to it.\n 37|\n 38|**in-session relay**:\n*39|A thin, stateless `spt.exe` task that exists only in **harness-hosted** sessions (where the agent harness is the parent process and spt cannot reach into its process tree — today's Monitor model). It streams the daemon brain's events into the session's stdout. All *stateful* listener logic lives in the daemon; the relay is a dumb pipe, freely killable and respawnable. **spt-hosted** sessions need no separate harness-owned relay — the daemon owns the PTY and consumes the same poll feed itself. Idle de...\n 40|\n 41|### Deliverable shape\n 42|\n...\n 106|**composite update — `[update.post]`** (ratified 2026-06-25, v0.16.0 update-arc grill; ADR-0029):\n*107|An optional **avenue-agnostic** post-step `{ command, self_verifies }` spt-core runs **after** the primary avenue resolves, in the same `spt adapter update` — so one lever pulls the adapter `.spt` (`gh_release`) **and** runs a delegated reconcile (e.g. an adapter's `claude plugin update` cross-platform binary). It **runs unconditionally** (even when the adapter version did not change — the post-step's own idempotent check decides), receiving a one-line **stdin JSON** seam (`adapter_applied`, `adapter...\n 108|\n 109|**resident adapter binary**: an adapter-owned process spt-core keeps alive for an endpoint's lifetime (today the `[message-idle-translation-binary]`), as opposed to **ephemeral** adapter binaries — the Psyche loop (daemon-hosted, ADR-0004), the `[digest]` extractor, `[session.*]` runners, hooks — which spawn on demand and pick up an update on their next invocation. Only resident binaries are stopped/restarted on a live update; ephemerals self-heal.\n 110|_Avoid_: calling the Psyche loop or an on-demand extractor a \"resident\" binary; \"restart the endpoint\" for what is a per-binary cycle.\n...\n 116|\n*117|Governing principle: **SPT is not a harness.** Model choice, billing shape, harness-internal env, and harness-internal context are entirely the adapter's concern, expressed inside the adapter's own command templates. spt-core owns only the template *mechanism* (substitution keys), the substitution *values* it is responsible for, and the surrounding lifecycle. Env for the *endpoint binary itself* is auto-handled by spt-core/broker; env for the *agent running inside* that binary is the adapter's config (e....\n 118|\n*119|**spawn-session seam** — launch a new agent session on this node. Manifest provides: a command template; `cwd`/project; a `headless` flag (optional, default false — for the GUI's resume-of-compatible-adapters); a `resume` flag (optional); and the `commune` + `signoff` file directories relative to `cwd` (so the daemon knows where to watch). Substitution keys spt-core can supply: `{id}` and, optionally, a spt-core-generated valid session UUID (e.g. injected as `--session-id {uuid}`) so an adapter can s...\n 120|- **id resolution:** `id` is optional. With no id, spt-core reproduces today's no-id `/spt:live` behavior — run the lone live agent if that's all the project has; show a picker with proposed default IDs if the project has none; let the user choose if there are several.\n 121|\n 122|**post-spawn seam** — the just-launched binary calls an spt-core command on boot (via the adapter's SessionStart-equivalent hook) to bind itself. Needed because the harness's own session id usually isn't known until after the binary runs. Payload: the harness `session_id` (when binary-generated rather than spt-core-injected); the `parent_pid` (the stable session-binding anchor — see KNOWN-HAZARDS 2.1); an endpoint identity/type confirmation; optionally a local HTTP port the binary listens on (for HTT...\n...\n 135|\n*136|**activity/idle detection** — **not** PTY-quiescence (insufficient: e.g. CC's AskUserQuestion stalls the PTY while holding stdin and needing nuanced input) and **not** a manifest-declared idle signal. Instead, the adapter calls spt-core activity/idle commands at the right moments (from its hooks); those commands manage activity/idle **sentinels inside the session perch**. The idle state lives in the perch, owned by spt-core via the commands API exposed to adapter devs.\n 137|\n 138|**inject-input seam** — message delivery into a running session. Configurable per activity-state (activity / idle / both); multiple methods, any combination:\n*139|- PTY injection (with or without key/submit sequences) — spt-hosted topology;\n 140|- adapter hooks calling spt-core poll commands;\n 141|- an in-adapter-session child relay (à la CC's Monitor tool);\n 142|- adapter manifest requesting HTTP POST delivery to the endpoint binary on a local port (shared via the post-spawn seam).\n...\n 148|- **delivery window** (*when*) — **default** (both windows; delivers in whichever fires first) · **idle-only** (held for the idle window; delivered immediately if already idle) · **active-only** (active window only — the receiver's hook-poll; never wakes an idle agent). *active-only* is the renamed legacy **deferred** (the `deferred=1` spool column + `api poll --include-deferred` are its internal/adapter-facing names). Mutually exclusive.\n*149|- **channel restriction** (*through what*) — **unrestricted** (any configured inject method) · **prefer-native** (the translation binary if one is running, else fall back to the standard methods) · **force-native** (the translation binary and nothing else — no fallback, no spool-to-another-method). Mutually exclusive; composes with the window. *\"Native\"* = the `[message-idle-translation-binary]` PTY channel.\n 150|- **persistence** (*how long it waits*) — **durable** (default; spooled until delivered or TTL) · **ephemeral** (dropped if it cannot deliver in its accepted window — at the moment the window opens with no live carrier, or at TTL, whichever is first). Ephemeral is the **only** path permitted to drop silently (the REQ-HAZARD-IDLE-SILENT-NONDELIVERY carve-out); every non-ephemeral path spools and reports non-delivery. <!-- v0.15.0 PARTIAL (W3): ephemeral evaporation covers the spt-hosted-binary no-car...\n 151|Window restricts *when* delivery is accepted, channel restricts *which method* carries it, persistence restricts *how long* it waits — they compose freely (e.g. `force-native` + `active-only` = the binary injects during the active window, never idle; `force-native` + `ephemeral` = binary-or-nothing).\n 152|\n...\n 167|- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.\n*168|- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.\n 169|- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs...\n 170|- **Config knobs** (pulse period, echo-commune window, route-guard window, daily refresh) are spt-core **global settings** with optional **per-endpoint override** — never per-adapter.\n 171|- **Event-block vocabulary and file-drop filenames are fixed spt-core constants** (documented for adapter authors), not manifest-configurable. <!-- [doc->REQ-RESUME-CONTEXT-PULL] --> This includes the **checkpoint sentinel `!!checkpoint!!`** — the agent-checkpoint trigger an adapter embeds in a commune/signoff drop body (one bare token = checkpoint with default wake; a `!!checkpoint!! <text> !!checkpoint!!` pair makes the inter-marker text a custom wake directive). It is spt-core control metadata: spt-...\n...\n 193|- **`api history-log`** — Path B: ingest normalized records into spt-core's native history store.\n*194|- **`api presence`** — adapter reports user interaction → updates the presence datum `(last_active_node, last_active_endpoint, ts)`. In the spt-hosted topology, presence is **also** updated by the broker *detecting* (sensing, not watching/logging) user input on a held PTY — privacy-preserving (it notes that input occurred, records no content).\n*195|- **`api emit --type <sensory_type> <payload>`** — a broker-launched **Shell** binary pushes a sensory payload to its owner agent (owner known from `api bind`; REST-only, never spooled). See the Shell model.\n 196|\n 197|**Not `api` commands — file-drop flow:** `commune` and `signoff` are deprecated as commands (modern SPT) in favor of file drops. The agent/adapter writes `<id>-commune.md` / `<id>-signoff.md`; the daemon watches the manifest-declared commune/signoff dirs (the spawn-session seam fields), ingests, and deletes (drop files are daemon-owned single-writer — KNOWN-HAZARDS 6.4). These stay off the `api` surface and the agent surface alike.\n 198|\n...\n 217|\n*218|**spt-hosted (terminal wrapper / GUI launcher; the daemon launches the binary into a broker PTY):**\n*219|1. The frontend/CLI launches the agent: the daemon runs the **spawn-session** command template into a broker-held PTY.\n*220|2. The binary boots and fires **`api bind`** (or skips it under the strict UUID-injection + stable-pid commitment). **No catalyst/seed file** — the daemon is the launcher, already holds a direct channel (it spawned the process and owns the PTY), so a file round-trip would only add drive churn for no benefit.\n*221|3. The daemon delivers events; method is **manifest-configurable per activity-state** — direct PTY injection, or a relay even here (some adapters prefer a relay over PTY injection for idle delivery), or HTTP. During *activity*, delivery still defaults to the non-disruptive hook-injection path, not raw PTY writes.\n 222|4. Psyche is daemon-spawned, same as above.\n 223|\n 224|So the old `$LIVE start` splits by topology: harness-hosted = SessionStart writes an adapter-agnostic seed → `$SPT listen <id>` consumes seed (by `parent_pid`) + resolves adapter/profile (ADR-0021) + binds + relays — legacy parity (`$LIVE start <id>` → `$SPT listen <id>`, no mandatory `--adapter`); spt-hosted = daemon spawn-session + `api bind` (direct, no file). The asymmetry is the file-bridge-only-when-no-direct-channel principle.\n## Cargo.lock\n 2925|[[package]]\n*2926|name = \"portable-pty\"\n 2927|version = \"0.8.1\"\n 2928|source = \"registry+https://github.com/rust-lang/crates.io-index\"\n 2929|checksum = \"806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be\"\n...\n 4086|dependencies = [\n*4087| \"portable-pty\",\n 4088| \"serde\",\n 4089| \"serde_json\",\n 4090| \"spt-msg\",\n## Cargo.toml\n 6|# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).\n*7|# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.\n*8|# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).\n 9|# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.\n 10|[workspace]\n 11|resolver = \"2\"\n## DAEMON-LIFECYCLE-COMPLETION-PLAN.md\n 27|- `brainproc::run_brain` drives **no** lifecycle — `brainproc.rs:175-182`:\n*28|  *\"Today the supervised daemon brain hosts no PTY sessions ... so this is a\n 29|  no-op now and forward-correct when daemon-hosted sessions land (the\n 30|  live-agent adapter).\"* `run_pulse_loop` has **no production caller**.\n 31|- `spt api listen` still runs the **interim in-process** Psyche + pulse —\n...\n 74|- **Both topologies daemon-spawn the Psyche.** harness-hosted (`listen` relay)\n*75|  and spt-hosted (`bind`, no relay, direct PTY) both get the brain-hosted Psyche,\n 76|  keyed on the endpoint being a LiveAgent — *independent of message delivery*\n 77|  (CONTEXT:168, :177).\n 78|- **Liveness is daemon-authoritative** (CONTEXT:194) — the brain marks/holds the\n 79|  Psyche perch `online`; no relay-process / pid coupling. Brain restart re-hosts\n*80|  the lifecycle (the survival the broker/brain split, ADR-0018, already buys).\n 81|- **No behavior change for the Self session** — `api boundary`, `api state`,\n 82|  commune/signoff file-drops, the digest two-origin merge all keep working; only\n 83|  *where the loop runs* changes.\n## DAEMON-LIFECYCLE-PLAN.md\n 4|> Origin: live rollout 2026-06-08 — a manual `spt daemon run` on kitsubito\n*5|> fought the systemd `spt-daemon` user service over the broker socket\n 6|> (auto-restart-fail loop). User: \"`start`/`stop` should manage the service\n 7|> when there is one; `run` must behave the same on every platform.\"\n 8|\n## DIGEST-MILESTONE-PLAN.md\n 36|**Task 0 (first commit of execution):** set the three `required_stages` above in\n*37|`traceable-reqs.toml`. The tree is green now because they are empty; activation +\n 38|first evidence land together so no commit is ever left activated-but-unevidenced.\n 39|\n 40|## 3. Design (settled — see ADR-0019, do not re-litigate)\n...\n 114|- [ ] `[digest]` section validated at register (bad extractor / bad knob caught at `adapter add`, not spawn).\n*115|- [ ] `spt adapter digest-proof` runs against a **real CC session JSONL** sample and surfaces a deliberately-broken extractor's drops (no silent empty).\n*116|- [ ] Spanning E2E: digest bridges a `/clear`, boundary marker present, window not reset-to-empty.\n 117|- [ ] Two-origin E2E: a psyche/echo/owl injection appears interleaved by ts.\n 118|- [ ] Full suite: workspace 0-fail, `clippy -D`, `--no-default-features`, xtask check, `traceable-reqs check` EXIT=0.\n 119|- [ ] Docs: MANIFEST `[digest]`, harness integration checklist, CHANGELOG supersession note (NOT a v0.5.0 retro-edit), CONTEXT + ADR-0019 consistent.\n## F-019-TRANSLATION-BINARY-INSTALL-DIR-RESOLVE-DESIGN.md\n 8|\n*9|**The `[message-idle-translation-binary]` never ran.** The adapter ships a **bare relative** path `path = \"cc-spt-idle-translate\"` (no `.exe`). spt-core passes it **verbatim** to the broker, which spawns it as `Command::new(path)` (broker.rs:1677) — resolved against the **daemon's cwd/PATH**, NOT the adapter install dir. It isn't on PATH → spawn fails → `build_translation` returns `None` → the session **FAILS CLOSED to raw inject** (broker.rs:1671-1677), which types the message + Enter raw (the s...\n 10|\n 11|**Proof:** (1) ground-truth instrumentation showed the inject worker DOES enqueue `{key:enter}`→`0x0d` etc. when a binary runs; (2) 0 resident translation processes for live endpoints; (3) perri set the manifest path to the ABSOLUTE `_github` exe → fresh endpoint → the binary ran and the message **submitted**.\n 12|\n...\n 20|\n*21|- `install_dir` is available at `launch_harness_brokered_in` (harnesshost.rs:185-196, the `install_dir` arg) — it's already recorded on the broker session (W3a/ADR-0025).\n*22|- Cleanest seam: in `launch_harness_brokered_in`, after `prepare_harness_spawn` returns `PreparedSpawn`, map `translation_binary` through `spt_runtime::runtime::resolve_program_in_dir(&path, install_dir)` when `install_dir` is `Some` (mirror the bare-fallback for `None`). Then the SpawnReq carries the resolved absolute path → `Command::new` succeeds → the real binary runs.\n 23|  - (Alternative: pass `install_dir` into `prepare_harness_spawn` and resolve there, co-located with the other program resolution. Builder's choice — keep it co-located with how the session program is resolved if cleaner.)\n 24|\n 25|No adapter change needed: the shipped `path = \"cc-spt-idle-translate\"` then resolves to `<install_dir>/cc-spt-idle-translate.exe`.\n## M0-PLAN.md\n 22|wire-version, typed+binary payloads (proto); atomic-write, spool, perch layout, registry\n*23|persistence, info.json (store). Out (later milestones): daemon/broker (M2/M3), networking\n*24|& pairing (M4), live/Psyche (M2), terminal/PTY (M3), cross-node registry *resolution* (M4 —\n 25|M0 builds local persistence only).\n 26|\n 27|## Sequencing rationale\n## M1-PLAN.md\n 28|**loopback only**); harness manifest + Psyche/live lifecycle + `api` surface (M2);\n*29|PTY/terminal + the consolidated daemon (M3). **Interim-model note:** M1 ready\n 30|agents run a standalone per-agent TCP listener; M3's `spt-daemon` consolidates all\n 31|listeners into one supervisor (CONTEXT \"spt-daemon\"). M1 builds the messaging\n 32|mechanism; M3 owns where it runs.\n...\n 94|**Stay `[]` (later milestones):** all R-NET/R-PAIR (M4), R-DAEMON/R-API/R-SEAM/R-START\n*95|(M2), R-TERM (M3), R-UPD (M3), R-REACH (M4), the live/Psyche + broker hazards\n*96|(CONPTY-DSR, RESTART-IDEMPOTENT, GRACE/ECHO/SIGNOFF, DROP-FILE-SINGLE-WRITER M2/M3).\n 97|\n 98|## Workspace change\n 99|\n## M11-PLAN.md\n 167|  reliable-ordered QUIC stream pair on the **D4 net-stream substrate** (`NetHost::open_stream`,\n*168|  broker-owned, survives brain restart, ADR-0004 §B) — a NEW held-stream lifecycle, distinct\n 169|  from the one-shot request/reply `serve_shell_link`. W3 anchor = the NetHost **loopback**\n 170|  conn (C2 same-node mock; cross-node real-Iroh is the W5 rung, R2). **Open** at shell-online\n*171|  (`shellhost::bind_shell_by_token`, manifest-enabled only); broker holds the `stream_id` in\n 172|  shell state **online-generation stamped** — *the* crash-proof no-stale-stream leg (R1):\n 173|  a stale stream_id from a prior online-gen is NEVER reused after relink even if\n 174|  `close_shell`'s drop never ran (brain crash mid-close). **Close** at `close_shell`\n## M12-CANDIDATES.md\n 32|\n*33|3. **spt-hosted HARNESS bringup + local user PTY attach (CLI). — CONFIRMED MISSING, TOP PRIORITY.**\n 34|   Investigation 2026-06-14 (file evidence):\n*35|   - Daemon-side PTY spawn EXISTS but shells-only: `crates/spt-daemon/src/brain.rs:spawn_session_pid()`,\n*36|     `crates/spt-daemon/src/shellhost.rs:launch_shell_brokered_in()`; CLI only `spt shell spawn`.\n*37|   - NO user CLI to launch a HARNESS endpoint into a broker PTY (harnesses still\n 38|     harness-hosted: adapter launches binary → binds via `spt api listen`).\n 39|   - Local user terminal attach = DESIGN-ONLY: cross-node remote attach exists\n 40|     (`crates/spt-daemon/src/attach.rs`, M4-D5b), but no local `spt attach <id>`.\n...\n 44|     spawn-session seam for harnesses.\n*45|   - **`spt rc`** — connect to an spt-hosted PTY; works **cross-node** (local attach is the\n 46|     same command, subset). Already M12-slated per operator. Wraps the existing cross-node\n 47|     attach machinery (`crates/spt-daemon/src/attach.rs`) into a user CLI.\n 48|   - spt-claude-code's `cc`/`cc <id>` = thin wrapper over `spt endpoint run` defaulted to\n...\n 50|   **GATING: full-fat M12 is now a PREREQUISITE for spt-claude-code** — operator ruled v1\n*51|   acceptance = legacy parity AND cross-subnet/PTY proof, so local attach + the picker are\n 52|   MANDATORY v1. todlando builds full-fat M12 after this grill, BEFORE perri starts.\n 53|\n 54|4. **Cross-adapter fallback targets `<adapter>:<profile>`.** Node-wide cross-adapter\n...\n 60|- **Psyche cross-machine sync = automatic** (M4-D6c, P2P over Iroh; `crates/spt-daemon/src/sync.rs`,\n*61|  `pump/sync.rs`). tracked/ replicates in-subnet by default; gh-repo `psyche-sync-setup`\n 62|  RETIRED to optional hub-mode. → spt-claude-code `/spt:setup` #8 reduces to a rare opt-in.\n 63|- **`spt endpoint fork <src> <new_id>` exists** (cross-subnet copy, same-node). Forking IS a\n 64|  first-class CLI offering. (`cli.rs:221`)\n## M12-ENDPOINT-RUN-PICKER.md\n 8|> Relationship: `spt endpoint run` = the rich bringup picker. `spt rc` = connect to an\n*9|> spt-hosted PTY (cross-node attach, M12-slated). spt-claude-code's `cc`/`cc <id>` =\n 10|> thin wrapper over `spt endpoint run` defaulted to the `claude-spt` adapter.\n 11|\n 12|## Layer 1 — kind\n...\n 85|- Keybinds: ←→ category · ↑↓ item · enter confirm · esc back · / filter · n new. **Phone\n*86|  caveat:** keybinds risk accidental input on a future smartphone PTY app → design the\n 87|  interaction model so a tap/non-keybind mode can layer on later (don't hard-couple to keys).\n 88|- Type-to-filter for large sets: YES (`/`). Keybind legend pinned at bottom: YES.\n 89|- \"View now\" = read-only attach (watch, no input); attached sessions need an explicit detach\n*90|  keybind. Empty category → \"no endpoints — press n to create\".\n 91|- `cc`/`cc <id>` wrapper: bare `cc` → picker scoped to claude-spt; `cc <id>` → if exists,\n 92|  attach/start directly (skip picker); else create-new with id pre-filled. Non-interactive\n 93|  flags enable `cc-<id>` shortcut scripts.\n## M12-PLAN.md\n 4|> (full picker spec). **M12 is a GATING PREREQUISITE for the downstream `spt-claude-code`\n*5|> project** — operator ruled v1 acceptance = legacy parity AND cross-subnet/PTY proof, so\n*6|> local PTY attach + the bringup picker are mandatory before perri starts. Build full-fat.\n 7|>\n*8|> Value prop framing: `spt rc` (connect to an spt-hosted PTY, cross-node) is a core M12 value.\n 9|>\n 10|> **Traceability:** add a `REQ-*` to `traceable-reqs.toml` for each item BEFORE building it\n 11|> (rule 3); activate stages incrementally as evidence lands, `int` at the final wave (rule 5).\n...\n 14|## What already EXISTS (don't rebuild — verified 2026-06-14)\n*15|- Daemon PTY spawn (`brain.rs:spawn_session_pid`, `shellhost.rs:launch_shell_brokered_in`) —\n 16|  shells-only today.\n 17|- spawn-session seam (`runtime.rs:spawn_session`) — implemented, shell-exercised.\n 18|- Cross-node remote attach (`attach.rs`, M4-D5b) — daemon-internal, no user CLI.\n...\n 22|## Wave 1 — spt-hosted HARNESS bringup + attach (the gating foundation)\n*23|Goal: a user can launch a harness endpoint into a broker PTY and attach a local terminal.\n 24|- **T1.1** `spt endpoint run` non-interactive core: wire the spawn-session seam for HARNESS\n*25|  endpoints (not just shells) → daemon spawns the harness command template into a broker PTY,\n 26|  registers the perch, returns id. Flags: `--adapter <a[:profile]>` `--id <id>` `--create`\n 27|  `--resume <session>` `--attach|--start|--view`. (Foundation for `cc-<id>` shortcuts.)\n*28|- **T1.2** Local user PTY attach CLI: `spt rc <id>` (and the local case) — attach a terminal\n*29|  to a broker-held PTY; works cross-node (reuse `attach.rs`), local is the subset. Read-only\n*30|  `--view`. Clean detach. Mind KNOWN-HAZARDS (PTY ownership, no termination on detach).\n 31|- REQs: REQ-HOST-RUN-*, REQ-RC-* (add to manifest). Stages doc+impl+unit this wave.\n 32|\n 33|## Wave 2 — `spt endpoint run` interactive picker (ratatui)\n...\n 74|  elevation on Win+Linux; whoami alias. Full suite green; `traceable-reqs check` EXIT=0;\n*75|  cross-subnet/PTY proof demonstrated (this is the spt-claude-code v1 acceptance dependency).\n 76|- Then unblock perri to begin `spt-claude-code` (see `../spt-claude-code/SCOPE.md`).\n## M12-W1-PLAN.md\n 3|> JIT build plan (todlando, 2026-06-14). Parent: `M12-PLAN.md` W1. Design-locked with doyle\n*4|> (one-path `rc` invariant + `[session.self]`→broker-PTY mirror approved). This doc is the\n 5|> survivable build spec — coding off this is mechanical.\n 6|\n 7|## Goal (gate condition for W1)\n*8|A user launches a HARNESS endpoint into a broker-held PTY and attaches a local terminal;\n 9|the same `spt rc` path attaches cross-node. Reverses today's harness-hosted-only launch.\n 10|\n 11|## REQs (registered, `traceable-reqs.toml`)\n*12|- **REQ-HOST-RUN-1** — `spt endpoint run` spawns `[session.self]` into a broker PTY for\n*13|  kind=harness (mirrors `shellhost::launch_shell_brokered_in`), registers the perch, returns id.\n*14|- **REQ-RC-1** — `spt rc <id>` attaches a local terminal to a broker-held PTY via the\n*15|  cross-node attach pump; local = degenerate single-node case (ONE path). `--view` read-only,\n 16|  clean detach (no session termination).\n 17|- Activation order (rule 2, memory [[traceable-per-wave-activation]]): each commit sets\n 18|  `required_stages` to ONLY the evidence in that commit, cumulative. doc+impl+unit land this\n...\n 21|## Verified seams (file:line)\n*22|- **Broker spawn**: `spt-daemon/src/brain.rs:380` `spawn_session_pid(SpawnReq) -> (u64, Option<u32>)`.\n 23|  `SpawnReq{program,args,rows,cols,endpoint}` (`msg.rs:124`), reply `Spawned{session_id,pid}`.\n*24|- **Mirror target**: `spt-daemon/src/shellhost.rs:210` `launch_shell_brokered_in(...)` —\n 25|  fills opaque template (`runtime::fill_template`+`tokenize`), `Brain::cold_start` → `spawn_session_pid`,\n 26|  records pid. New harness path mirrors this but: (a) template = `[session.self].command`,\n*27|  (b) endpoint label = the harness endpoint id (NOT shell_session_label), (c) PTY size = real\n 28|  terminal dims (a harness IS a terminal — unlike shells' 24x512 anti-wrap), (d) registration\n 29|  per the harness perch contract (fill from trace), (e) NO link-token (harness binds via `api`).\n*30|- **Attach pump**: `spt-daemon/src/attach.rs` — `serve_attach(brain,stream_id,endpoint,origin_node,net_from_seq)`\n 31|  -> `AttachServeOutcome{Refused,Exited(Option<i32>),Detached}`; `request_attach(brain,conn_id,session_id,from_seq,open_op)->stream_id`;\n 32|  `send_attach_input(brain,stream_id,bytes,wire_op)`. Cross-node rides QUIC (`spt-net/src/net/attach.rs`\n 33|  `AttachRecord::{Request,Input,Output,Exit}`). Local = single-node degenerate case (loopback peer).\n...\n 50|  No new daemon registration code — `spt endpoint run` only needs to (1) spawn `[session.self]`\n*51|  into the broker PTY labeled `<id>`, (2) ensure the harness binds to `<id>` (the env/id question\n*52|  below). Broker-held PTY ⇒ harness survives `rc` detach (hazard satisfied, by construction).\n*53|- **cwd — REQ-HOST-RUN-2 (doyle-tracked, post-W1 / pre-M12-gate).** W1 ships broker-inherited\n 54|  cwd (= daemon cwd) as a bringup-PROOF SHORTCUT only. NOT optional for M12: the consumer (Claude\n 55|  Code) is project-scoped — wrong cwd ⇒ wrong `.claude`/session-history/digest source; `cc <id>`\n 56|  at a project root must land CC in that project. Real cwd DOES need the additive `SpawnReq.cwd`\n*57|  wire change (broker spawns the PTY; cwd must reach `portable-pty` CommandBuilder). N-1-safe,\n 58|  done deliberately. Registered now (stages=[]); must land before the M12 gate.\n*59|- **Follow-up flags (doyle, NOT W1 blockers):** (1) PTY *resize* events must flow through the\n*60|  attach pump for an interactive harness — W1 sets initial real dims only; live resize = later W.\n 61|  (2) Detach keybind (T1.2.5): explicit, cannot collide with harness passthrough input (legacy\n 62|  capsule = ctrl-b prefix); documented. Folded into REQ-RC-1.\n 63|- **RESOLVED — VERDICT (A): argv `{id}` template, NO SpawnReq wire change.** Harness learns its\n...\n 66|  `fill_template` (`runtime.rs:93`) fills `{id}`/`{session_id}`/`{adapter_name}` (`SUBSTITUTION_KEYS`\n*67|  `runtime.rs:36`) before tokenize. Broker spawn `broker.rs:531 dispatch_spawn` →\n*68|  `pty.rs:95 PtySession::spawn_program(program,args,size)` → portable-pty CommandBuilder INHERITS\n*69|  broker env (no env param). `[env]` doc confirms \"spt-hosted inherits from the broker.\" So the\n 70|  daemon fn just substitutes `{id}` + the standard keys; harness self-binds to `<id>` post-spawn.\n 71|  Session id: fresh create = mint one; `--resume <session>` = use that.\n 72|\n...\n 76|   `--resume <session>`, `--attach|--start|--view` (mutually-exclusive terminal action).\n*77|2. New daemon fn (shellhost sibling, e.g. `harnesshost::launch_harness_brokered_in` or extend\n## M12-W1.5-PLAN.md\n*1|# M12 Wave 1.5 — broker loopback-conn + local `spt rc` attach\n 2|\n 3|> JIT plan (todlando, 2026-06-14). doyle-scoped after W1 PASS. Goal: LOCAL `spt rc <id>`\n*4|> attach rides the BYTE-IDENTICAL `serve_attach` pump as cross-node — because self-dial is\n 5|> impossible (iroh: \"Connecting to ourself is not supported\", pinned by\n 6|> `loopback_self_dial_is_refused_local_uses_fallback_transport`). doyle ruling = **B1**: an\n*7|> in-process broker loopback connection, NOT B2 local-direct (which would reintroduce a\n*8|> lesser-tested local branch). Sensitive broker-layer work (KNOWN-HAZARDS 6.7 zone).\n 9|\n 10|## The invariant (doyle, binding)\n*11|ONE `serve_attach` pump. Transport is swappable UNDER the stream. Cross-node = QUIC conn;\n*12|local = broker loopback conn. Both surface as broker net-stream events (`NetStreamData` /\n*13|`net_stream_subscribe` / `net_stream_send`) so `serve_attach` (target) + the `rc::pump`\n*14|(operator) are unchanged. The W1.5 int test MUST assert both transports hit the same pump.\n 15|\n 16|## What already exists (W1)\n*17|- Operator pump: `spt/src/rc.rs` `run_attach` + `pump` (transport-invariant — reads\n 18|  `NetStreamData`, writes via `send_attach_input`). Currently codes the now-REFUSED self-dial\n 19|  (`brain.net_dial(net_status().addr)`) — **this is the one line W1.5 swaps** to a loopback-dial.\n 20|- Target serve: `spt-daemon/src/attach.rs serve_attach`, auto-dispatched at\n...\n 25|## B1 design (the loopback conn)\n*26|A broker-internal connection with NO iroh handshake whose two bidi stream ends are wired\n 27|in-process, so a stream opened by the operator side delivers its bytes as `NetStreamData` to\n 28|a subscriber on the \"remote\" side (which the dispatch loop serves via `serve_attach`), and\n 29|vice-versa. Local `spt rc`: loopback-dial → `conn_id` → `request_attach(conn_id, session_id)`\n*30|→ subscribe → pump — identical to cross-node from `request_attach` up.\n 31|\n 32|### Investigation TODOs (read before coding — NOT yet done)\n*33|1. **`spt-daemon/src/broker.rs`**: the conn table + stream table + how `dispatch_*` routes an\n 34|   inbound stream's first frame to `StreamFamily::Attach`→`serve_attach`. How a QUIC stream's\n 35|   bytes become `NetStreamData` events (the accept/serve plumbing). Find the seam to inject a\n 36|   local pair.\n...\n 41|   stream whose sends loop back to a peer stream id the dispatch loop picks up as inbound.\n*42|   Likely an in-process channel pair inside the broker, surfaced through the SAME\n 43|   `NetStreamData`/`net_stream_send`/`net_stream_subscribe` IPC the QUIC path uses.\n 44|4. **Brain-side**: a `brain.net_dial_loopback(op) -> conn_id` (or reuse `net_dial` with a\n*45|   sentinel local addr the broker recognizes). Decide: new frame KIND vs sentinel addr.\n 46|5. **Origin for the gate**: `serve_attach`'s `origin_node` is the handshake-proven id. A\n 47|   loopback conn has no handshake — use THIS node's own id as origin (access_check is\n 48|   default-open for self; confirm). Don't fabricate a foreign origin.\n...\n 50|## Tasks\n*51|- **T1.5.1** Broker loopback conn: `Conn::Loopback` + in-process stream cross-wiring; inbound\n*52|  loopback streams dispatch to `serve_attach` exactly like QUIC. (broker.rs + nethost.rs)\n 53|- **T1.5.2** `brain.net_dial_loopback` (or sentinel) → `conn_id`; minimal Brain API.\n 54|- **T1.5.3** Swap `rc.rs` conn-acquisition: local `run_attach` loopback-dials instead of\n*55|  `net_dial(own_addr)`. (One seam; pump unchanged.)\n*56|- **T1.5.4** Int test `local_attach_via_loopback_conn_rides_the_same_pump`: spawn a session,\n 57|  loopback-dial, request_attach, drive input, render echo, detach→session outlives. PLUS the\n*58|  **one-pump assertion**: both the cross-node int and this local int exercise the same\n*59|  `serve_attach`+pump path (assert by construction / shared helper, per doyle).\n 60|- **T1.5.5** Activate REQ-RC-1 local int evidence (int already active from W1 cross-node face;\n 61|  add the `[int->REQ-RC-1]` tag on the local test). `traceable-reqs check` EXIT=0.\n 62|\n\n## .config/\n### nextest.toml\n 5|#   Phase A  `-E 'not ( <HEAVY> )'`  — the light pool, full parallelism, fast.\n*6|#   Phase B  `-E '<HEAVY>'`          — the heavy real-broker/PTY/daemon-tree class,\n 7|#                                      run AFTER Phase A so the box is QUIET, and\n 8|#                                      serialized by the test-group below.\n 9|# <HEAVY> is the SAME filter string in both CI phases AND in the test-group override\n...\n 14|[profile.default]\n*15|# A hung test (e.g. a blocking PTY/socket read that never completes) must FAIL FAST,\n 16|# never wedge the CI job up to its wall-clock timeout. Warn once a test exceeds 60s,\n 17|# then SIGKILL + fail it after 4 such periods (4 minutes). No legitimate test in this\n 18|# workspace runs that long; this is purely the no-infinite-wedge backstop.\n...\n 20|\n*21|# Concurrency cap for the heavy real-broker / PTY / daemon-tree integration tests.\n 22|#\n 23|# WHY: nextest fans a phase into ONE parallel pool sized to the runner's core count.\n*24|# The \"heavy\" tests each spin a REAL broker, forkpty a REAL PTY child, and/or spawn a\n 25|# REAL `spt daemon`/brain process tree. Two failure modes were observed on the shared\n 26|# self-hosted runners:\n*27|#   1. heavy-vs-heavy contention: ~16 real brokers launching at once contend for the\n*28|#      scheduler / PTY allocation / socket setup, producing wait-deadline / PTY-spawn\n 29|#      / child-reap TIMING flakes whose victim rotates run to run (spawn_env on Linux\n*30|#      forkpty, g4 on Windows, controller_writer, attach_survives, …).\n 31|#   2. heavy-starved-by-light: even with the heavy class serialized, the ~1200 LIGHT\n 32|#      tests still ran at full parallelism in the SAME pool and saturated the box, so\n 33|#      a heavy test's helper thread lost the CPU race (a_journaled_input_wedge\n...\n 38|# can't bite either, even on the idle box. max-threads = 1 is contention-free and\n*39|# deterministic (a cap > 1 still lets two real brokers race = lucky, not reliable).\n 40|# Group membership == the <HEAVY> phase filter. (Driver-pacing in inject_control_wedge\n 41|# additionally removes that gate's self-inflicted CPU storm — a correctness nicety on\n 42|# top of the structural split.)\n 43|[test-groups]\n*44|heavy-broker-pty = { max-threads = 1 }\n 45|\n*46|# <HEAVY> — the heavy real-broker/PTY/daemon-tree class, by package+binary. Swept by\n*47|# concrete evidence: a binary is HEAVY iff some test in it spawns a real PTY child\n 48|# (a `SpawnReq`/`spawn_program` with a `cat`/`findstr`/`yes`/`sleep`/`ping`/FLOOD\n 49|# program) OR a real `spt daemon`/brain process tree (`supervise_brain`, CARGO_BIN_EXE\n*50|# daemon run). Pure in-process loopback-QUIC wire tests (net_broker with NO PTY child:\n*51|# netbroker, netstream, mesh, sync, replicate, presence, …) are LIGHT and stay in\n*52|# Phase A. All spt-term test binaries forkpty a real PTY child, so the whole package\n 53|# is heavy. Keep this string IDENTICAL to both -E filters in ci.yml.\n 54|[[profile.default.overrides]]\n*55|filter = '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|livehost_bootgate_e2e|n1_pairing|oneliner_e2e|resume_te...\n*56|test-group = 'heavy-broker-pty'\n 57|\n*58|# FLAKE-LEDGER #14 (W-3, todlando 2026-07-06): the rc real-broker UNIT tests inside the\n 59|# `spt` BINARY escape the integration-binary HEAVY filter above — they are `kind(bin)`\n\n[Showing lines 1-522 of 633 (50.0KB limit). Use :523 to continue. Some lines truncated to 512 chars]"}],"details":{"scopePath":"spt-core","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":164,"fileCount":20,"files":["spt-core/.config/nextest.toml","spt-core/.github/workflows/ci.yml","spt-core/.github/workflows/release.yml","spt-core/CHANGELOG.md","spt-core/CI-SELFHOST-PLAN.md","spt-core/CONTEXT.md","spt-core/Cargo.lock","spt-core/Cargo.toml","spt-core/DAEMON-LIFECYCLE-COMPLETION-PLAN.md","spt-core/DAEMON-LIFECYCLE-PLAN.md","spt-core/DIGEST-MILESTONE-PLAN.md","spt-core/F-019-TRANSLATION-BINARY-INSTALL-DIR-RESOLVE-DESIGN.md","spt-core/M0-PLAN.md","spt-core/M1-PLAN.md","spt-core/M11-PLAN.md","spt-core/M12-CANDIDATES.md","spt-core/M12-ENDPOINT-RUN-PICKER.md","spt-core/M12-PLAN.md","spt-core/M12-W1-PLAN.md","spt-core/M12-W1.5-PLAN.md"],"fileMatches":[{"path":"spt-core/.config/nextest.toml","count":20},{"path":"spt-core/.github/workflows/ci.yml","count":20},{"path":"spt-core/.github/workflows/release.yml","count":2},{"path":"spt-core/CHANGELOG.md","count":20},{"path":"spt-core/CI-SELFHOST-PLAN.md","count":1},{"path":"spt-core/CONTEXT.md","count":20},{"path":"spt-core/Cargo.lock","count":2},{"path":"spt-core/Cargo.toml","count":2},{"path":"spt-core/DAEMON-LIFECYCLE-COMPLETION-PLAN.md","count":3},{"path":"spt-core/DAEMON-LIFECYCLE-PLAN.md","count":1},{"path":"spt-core/DIGEST-MILESTONE-PLAN.md","count":3},{"path":"spt-core/F-019-TRANSLATION-BINARY-INSTALL-DIR-RESOLVE-DESIGN.md","count":3},{"path":"spt-core/M0-PLAN.md","count":2},{"path":"spt-core/M1-PLAN.md","count":3},{"path":"spt-core/M11-PLAN.md","count":2},{"path":"spt-core/M12-CANDIDATES.md","count":7},{"path":"spt-core/M12-ENDPOINT-RUN-PICKER.md","count":3},{"path":"spt-core/M12-PLAN.md","count":10},{"path":"spt-core/M12-W1-PLAN.md","count":20},{"path":"spt-core/M12-W1.5-PLAN.md","count":20}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# spt-core/\n## CHANGELOG.md\n  16│\n *17│> **Applying this update needs a daemon restart.** Unlike the recent seamless\n  18│> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n  19│> --apply` lands it via a full daemon restart rather than a live swap — your\n  20│> sessions restart as the daemon comes back. This is a one-time cost to install\n    │...\n  46│  fix lives in the incoming version, updating *to* this release is what makes your next\n *47│  in-place update seamless.)\n  48│\n  49│## [0.30.4] - 2026-07-09\n  50│\n    │...\n  60│  session's controller, so your open sessions and `spt rc` keep working across the\n *61│  brain swap. (Follow-up to the previous release's seamless-swap fix.)\n  62│\n  63│## [0.30.3] - 2026-07-09\n  64│\n    │...\n  72│  completing the swap. The incoming version now drives the running daemon to clear a\n *73│  hard-stopped prior session during the handoff, so the update completes seamlessly with\n  74│  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\n  75│  incoming version's logic, updating *to* this release is what makes your next in-place\n *76│  update seamless.)\n  77│\n  78│- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\n  79│  \"never wakes an idle target\") could still be delivered to a target the instant it went\n    │...\n 112│  would stall the whole peer-sync round, so `spt subnet status` listed the subnet's\n*113│  members but showed an empty live-node list and warned that the peer pump had\n 114│  stalled. An unresponsive peer is now dropped and retried on its own instead of\n 115│  holding up the round, so the node keeps converging and peers reappear.\n 116│\n    │...\n 158│- **Hosted sessions are re-launched after a daemon restart.** Previously-online sessions come back automatically once the daemon restarts (including as part of an update) instead of being left offline. The restart still interrupts them — this restores the session, it does not preserve its in-flight work.\n*159│- **An attached terminal no longer freezes behind a stalled session.** If a session's underlying process hangs or is suspended, `spt rc` keeps updating and a stuck connection is dropped automatically so you never lose control; reattaching and taking control (`--take`) keep working. `spt daemon status` reports when such a drop happened.\n 160│- **A long, multi-line message typed into a live agent's terminal arrives complete.** When a message is delivered by typing it into a live session, it is no longer truncated at the front. (Messages drained through the polling channel were never affected.)\n 161│- **`spt endpoint digest --json` no longer repeats rows.** Activity replayed across a session checkpoint is collapsed to a single entry.\n 162│- **Waking an agent starts exactly one session.** Two wake requests arriving at once no longer launch it twice.\n    │...\n 272│- **Message delivery and scheduled wake-ups now survive a context reset.** After a context reset, an idle agent could stop receiving messages and scheduled wake-ups until its session was restarted; the session boundary now re-stamps its readiness in the correct order across the reset, so messages and wakes right after a reset are delivered instead of dropped.\n*273│- **A `--force-native` send that cannot be delivered now says why.** The failure message distinguishes its cause — empty message, no daemon running, endpoint active mid-turn, no working translation binary, or not a controllable endpoint — instead of a single identical message for every case.\n 274│\n 275│## [0.23.0] - 2026-07-03\n 276│\n    │...\n 326│- **`spt endpoint list --show-all`.** Suspended (resting) endpoints are now hidden by default to cut clutter; `--show-all` reveals them. Each machine's total discloses how many were hidden (nothing silently vanishes), and a corrupt record always shows regardless.\n*327│- **`spt daemon status` now reports the running daemon's version.** It shows the version the running daemon was built from beside the installed version and flags a mismatch — so you can tell when an update is on disk but the daemon still needs a restart to fully load it. The `--json` output gains matching `broker_image` / `broker_stale` fields.\n 328│\n 329│### Changed\n 330│\n    │...\n 349│\n*350│- **`spt send` with no explicit sender now stamps `cli@<node>`.** A delivered message never shows a blank sender — a message sent without a from-identity is attributed to the originating node instead of arriving empty.\n 351│- **Clearer delivery diagnostics.** Spool and idle-drain failures now emit loud, one-shot log lines, and operator notes on message-delivery timing are documented.\n 352│\n 353│### Fixed\n    │...\n 386│\n*387│- **Attaching to a running terminal session no longer corrupts the scrollback.** Cold-attaching to a full-screen TUI (e.g. Claude Code) used to replay the raw output ring into a fresh terminal — flipping the alternate screen on and off mid-stream and spilling TUI frames into history. The broker now keeps an authoritative screen model and synthesizes a clean repaint of the current screen on a cold attach, so you get the live frame, not a corrupt transcript. (A resume from a known point still re-fetches ...\n 388│- **`spt rc` to an already-running endpoint no longer prints garbled escape codes on Windows 10 / raw PowerShell.** The garbling was the same raw-ring replay problem as above (not a client terminal-mode issue — `spt endpoint run --attach` rendered fine in the same terminal); the clean-repaint-on-attach fix resolves it. VT output is also enabled defensively on legacy Windows consoles.\n*389│- **`spt rc` to a live local endpoint no longer times out with `brain IPC read deadline elapsed` after a self-update.** A single panic while the broker's effect journal was locked could poison it and brick every subsequent attach; the journal now recovers from a poisoned lock, and the loopback attach path fails fast with a real error instead of an opaque 10-second deadline.\n 390│- **`spt send` across nodes no longer reports `SENT(WAN)` when nothing was delivered.** A cross-node send was a fire-and-forget local buffer write — a refused or no-perch delivery was silently dropped while the sender printed success. The receiver now writes the delivery outcome back and the sender waits for it, so a real failure prints an honest line; the dial also tries the last-known direct address first (mirroring the gossip path) instead of forcing a cold discovery on every send.\n 391│- **The remote endpoint count no longer drifts as endpoints are added and purged.** A remote viewer's `--nodes` count counted non-routable ghost rows in the denominator and never evicted purged endpoints, producing wrong ratios like `0/2` or `1/3`. The count now uses a routable-only denominator, and rows left offline past a grace window are evicted from the gossiped snapshot instead of leaking forever.\n*392│- **A locally-controlled endpoint no longer shows as \"ready to control\" on other machines.** Remote viewers only learned about a *remote* controller; a locally-driven endpoint gossiped as uncontrolled. The broker now advertises whether *anyone* (local or remote) is driving an endpoint, so remote viewers see it as controlled.\n 393│- **`spt adapter add` no longer swallows install errors.** Errors from the install-as-first-update step were discarded; the failing step's output is now surfaced, and the composite post-install step runs at install time.\n*394│- **`spt adapter update` no longer fails to re-register after fetching.** The update derived the install directory from the `[update]` repo name rather than the adapter's registered source directory — so after a repo rename it wrote to a fresh empty directory and then failed re-register with `os error 2`. Update now targets the registered source directory and tolerates a changed update repo.\n 395│- **`spt endpoint digest` now works for endpoints whose transcript lives under a relocated profile.** A profile that relocates the harness transcript tree (via a runtime env like `CLAUDE_CONFIG_DIR`) produced `NO_DIGEST` because the on-demand extractor ran without that environment. The digest path now carries the profile's transcript-location environment to the extractor, which locates and reads the transcript itself.\n 396│\n 397│## [0.18.0] - 2026-06-30\n    │...\n 441│- **`[message-idle-translation-binary]` takes a `command`.** The idle-delivery translation binary can be declared with a `command` (program token plus args, with adapter-static substitution) instead of the bare `path`, so it can be invoked as a subcommand of a consolidated adapter binary. The spawn and stdin/stdout protocol are unchanged.\n*442│- **Empty-scope creation flow.** Running `spt endpoint run` (or bare `spt`) on a node with no endpoints at all now opens directly on the adapter-creation screen instead of an empty picker.\n 443│\n 444│### Changed\n 445│\n    │...\n 541│\n*542│- **Idle message delivery for spt-hosted endpoints now uses a translation binary.** An adapter can declare a `[message-idle-translation-binary]`; spt-core brings it up alongside the endpoint, where it polls the relay for incoming messages and optionally emits keypresses, delays, and text injection that spt-core applies to the endpoint's terminal. Idle delivery now flows through the relay poll for every endpoint, instead of spt-hosted endpoints falling back to direct PTY injection.\n 543│- **Windows paste in `spt rc`.** Ctrl+V and right-click now paste the local clipboard into the attached agent as a single bracketed paste.\n 544│- **Windows special keys in `spt rc`.** Arrow keys, Home/End, Delete, function keys and other special keys are translated to terminal sequences and reach the agent — previously only plain characters got through.\n 545│- **`spt rc` forwards mouse scroll** to the agent's terminal.\n    │...\n 549│- **Pasting or typing into an spt-hosted endpoint no longer freezes the daemon.** A large paste or input burst could wedge the daemon's input path so every new attach died; input now runs on a dedicated per-session writer and never blocks the daemon (this also covers the effect-journal stall on interactive input).\n*550│- **Session resume actually resumes now.** Agent endpoints track session history and offer explicit session resume via `spt endpoint run` (`--resume <session>` or *Resume from history*); the adapter manifest can now declare a `[session.resume]` command so the relaunch reattaches the prior session instead of always starting a new one. The run picker now shows each row's working directory and local time.\n 551│- **`spt rc --view` viewers survive a high-output terminal.** A viewer no longer dies when the output backlog rolls over or it is briefly evicted — it snaps forward or skips to live instead of failing — and a slow controller can no longer starve a concurrent viewer.\n 552│- **`spt rc` no longer races a just-started agent.** Attach now waits for the endpoint to come online, instead of failing when you attach immediately after `spt endpoint run`.\n 553│- **The daemon no longer flashes or respawns a console window on Windows.**\n    │...\n 600│- **`spt send` now delivers to an agent whose terminal is hosted by the daemon.** When the target has no `spt api listen` relay (the daemon holds its terminal directly), `spt send` previously queued the message silently. It now injects the message into the agent's session and reports \"Sent\" only once delivery is confirmed — otherwise it queues it (\"Queued\") as before, and never reports a false \"Sent\".\n*601│- **`spt endpoint run` now fills in `[env]` values.** Placeholders such as `{id}` in an adapter's `[env]` entries (for example `SPT_ENDPOINT_ID`) were never substituted, so an agent launched without explicit flags came up with an empty endpoint id and never registered. The values are now substituted and set on the launched process.\n 602│- **A daemon-hosted terminal now reports a clear message when the daemon is stopped,** instead of failing with a raw \"failed to fill whole buffer\" that looked like a crash.\n 603│- **Clearer error when an adapter's manifest has not been extracted yet.** Using such an adapter now reports an actionable message (and logs it as skipped) instead of a raw \"os error 2\" and a silent drop.\n 604│- **A removed endpoint no longer lingers with a stale status** — it is now shown as offline.\n    │...\n 620│\n*621│- **The picker now loads each agent's project history** — it previously always showed empty.\n 622│- **A self-owned agent listed under both Local and Subnet no longer shows conflicting status.** The live local status is now authoritative for both listings.\n 623│\n 624│## [0.9.1] - 2026-06-17\n    │...\n 712│\n*713│- **`spt adapter digest-proof` now fills the same substitution keys the live extractor does.** A proof run previously supplied an empty key map, so any extractor template using `{session_id}` (the shape in the published examples) failed instead of producing a sample. It now populates `{id}` and `{session_id}` to match runtime, with an optional `--session` to pin a specific value.\n 714│\n 715│## [0.7.1] - 2026-06-15\n 716│\n## CI-SELFHOST-PLAN.md\n 64│      runner + target).\n*65│- [ ] Decide whether the netstream/netbroker UDP-binding tests need a firewall\n 66│      allowance on gravity (loopback-only since BindScope::Loopback — no).\n## CONTEXT.md\n   2│\n  *3│**Platform scope:** Windows + Linux for v1. macOS is out (no test machine available) but kept structurally easy — `portable-pty` and Iroh both support it, so macOS is a later test/CI-budget decision, not a re-architecture.\n   4│\n   5│**Legacy migration:** it should be possible — ideally *automatic* — for a user to migrate an existing `claude_skill_owl` (modern SPT) install to spt-core (identity, agents, tracked Psyche context). Exact mechanism deferred to design; the commitment is that migration is a first-class supported path, not a manual rebuild.\n   6│\n    │...\n  29│**spt-daemon** (per-machine supervisor):\n *30│The single always-on, one-per-machine logical supervisor. Owns the PTYs for all hosted sessions, the node's network identity + WAN endpoint, the subnet registry, all spools, **all poll-listener logic, and all Psyche/pulse loops** — everything is consolidated here (no separate poll-listener or Psyche-wrapper processes; listeners already touch sessions directly under capsule/idle, and Psyche wrappers already invoke harness binaries directly, so they belong in the one supervisor). Collapses what the siste...\n  31│\n *32│Internally the logical daemon is split into two implementation layers for seamless self-update (see Self-update):\n *33│- **broker** (stable \"kernel\") — holds *only* the un-transferable, must-not-die resources: PTY master fds, the spawned harness child processes, and listening network sockets. Minimal, dumb, versioned local IPC. Almost never updates.\n *34│- **daemon brain** (\"userspace\") — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk state and re-attaches to the broker's held handles.\n  35│\n *36│Logical addressing is unchanged — still one per-machine `spt-daemon`; the broker is an internal layer, not separately addressable. There is exactly **one broker per machine** (per `SPT_HOME`) — *not* one per endpoint: a single broker holds every hosted endpoint's resources, and it is present whenever the daemon runs, even with zero endpoints online (the bare-daemon case). It is therefore the always-present per-machine layer, which is why the single-daemon lock + liveness anchor belong to it.\n  37│\n  38│**in-session relay**:\n *39│A thin, stateless `spt.exe` task that exists only in **harness-hosted** sessions (where the agent harness is the parent process and spt cannot reach into its process tree — today's Monitor model). It streams the daemon brain's events into the session's stdout. All *stateful* listener logic lives in the daemon; the relay is a dumb pipe, freely killable and respawnable. **spt-hosted** sessions need no separate harness-owned relay — the daemon owns the PTY and consumes the same poll feed itself. Idle de...\n  40│\n  41│### Deliverable shape\n  42│\n    │...\n 106│**composite update — `[update.post]`** (ratified 2026-06-25, v0.16.0 update-arc grill; ADR-0029):\n*107│An optional **avenue-agnostic** post-step `{ command, self_verifies }` spt-core runs **after** the primary avenue resolves, in the same `spt adapter update` — so one lever pulls the adapter `.spt` (`gh_release`) **and** runs a delegated reconcile (e.g. an adapter's `claude plugin update` cross-platform binary). It **runs unconditionally** (even when the adapter version did not change — the post-step's own idempotent check decides), receiving a one-line **stdin JSON** seam (`adapter_applied`, `adapter...\n 108│\n 109│**resident adapter binary**: an adapter-owned process spt-core keeps alive for an endpoint's lifetime (today the `[message-idle-translation-binary]`), as opposed to **ephemeral** adapter binaries — the Psyche loop (daemon-hosted, ADR-0004), the `[digest]` extractor, `[session.*]` runners, hooks — which spawn on demand and pick up an update on their next invocation. Only resident binaries are stopped/restarted on a live update; ephemerals self-heal.\n 110│_Avoid_: calling the Psyche loop or an on-demand extractor a \"resident\" binary; \"restart the endpoint\" for what is a per-binary cycle.\n    │...\n 116│\n*117│Governing principle: **SPT is not a harness.** Model choice, billing shape, harness-internal env, and harness-internal context are entirely the adapter's concern, expressed inside the adapter's own command templates. spt-core owns only the template *mechanism* (substitution keys), the substitution *values* it is responsible for, and the surrounding lifecycle. Env for the *endpoint binary itself* is auto-handled by spt-core/broker; env for the *agent running inside* that binary is the adapter's config (e....\n 118│\n*119│**spawn-session seam** — launch a new agent session on this node. Manifest provides: a command template; `cwd`/project; a `headless` flag (optional, default false — for the GUI's resume-of-compatible-adapters); a `resume` flag (optional); and the `commune` + `signoff` file directories relative to `cwd` (so the daemon knows where to watch). Substitution keys spt-core can supply: `{id}` and, optionally, a spt-core-generated valid session UUID (e.g. injected as `--session-id {uuid}`) so an adapter can s...\n 120│- **id resolution:** `id` is optional. With no id, spt-core reproduces today's no-id `/spt:live` behavior — run the lone live agent if that's all the project has; show a picker with proposed default IDs if the project has none; let the user choose if there are several.\n 121│\n 122│**post-spawn seam** — the just-launched binary calls an spt-core command on boot (via the adapter's SessionStart-equivalent hook) to bind itself. Needed because the harness's own session id usually isn't known until after the binary runs. Payload: the harness `session_id` (when binary-generated rather than spt-core-injected); the `parent_pid` (the stable session-binding anchor — see KNOWN-HAZARDS 2.1); an endpoint identity/type confirmation; optionally a local HTTP port the binary listens on (for HTT...\n    │...\n 135│\n*136│**activity/idle detection** — **not** PTY-quiescence (insufficient: e.g. CC's AskUserQuestion stalls the PTY while holding stdin and needing nuanced input) and **not** a manifest-declared idle signal. Instead, the adapter calls spt-core activity/idle commands at the right moments (from its hooks); those commands manage activity/idle **sentinels inside the session perch**. The idle state lives in the perch, owned by spt-core via the commands API exposed to adapter devs.\n 137│\n 138│**inject-input seam** — message delivery into a running session. Configurable per activity-state (activity / idle / both); multiple methods, any combination:\n*139│- PTY injection (with or without key/submit sequences) — spt-hosted topology;\n 140│- adapter hooks calling spt-core poll commands;\n 141│- an in-adapter-session child relay (à la CC's Monitor tool);\n 142│- adapter manifest requesting HTTP POST delivery to the endpoint binary on a local port (shared via the post-spawn seam).\n    │...\n 148│- **delivery window** (*when*) — **default** (both windows; delivers in whichever fires first) · **idle-only** (held for the idle window; delivered immediately if already idle) · **active-only** (active window only — the receiver's hook-poll; never wakes an idle agent). *active-only* is the renamed legacy **deferred** (the `deferred=1` spool column + `api poll --include-deferred` are its internal/adapter-facing names). Mutually exclusive.\n*149│- **channel restriction** (*through what*) — **unrestricted** (any configured inject method) · **prefer-native** (the translation binary if one is running, else fall back to the standard methods) · **force-native** (the translation binary and nothing else — no fallback, no spool-to-another-method). Mutually exclusive; composes with the window. *\"Native\"* = the `[message-idle-translation-binary]` PTY channel.\n 150│- **persistence** (*how long it waits*) — **durable** (default; spooled until delivered or TTL) · **ephemeral** (dropped if it cannot deliver in its accepted window — at the moment the window opens with no live carrier, or at TTL, whichever is first). Ephemeral is the **only** path permitted to drop silently (the REQ-HAZARD-IDLE-SILENT-NONDELIVERY carve-out); every non-ephemeral path spools and reports non-delivery. <!-- v0.15.0 PARTIAL (W3): ephemeral evaporation covers the spt-hosted-binary no-car...\n 151│Window restricts *when* delivery is accepted, channel restricts *which method* carries it, persistence restricts *how long* it waits — they compose freely (e.g. `force-native` + `active-only` = the binary injects during the active window, never idle; `force-native` + `ephemeral` = binary-or-nothing).\n 152│\n    │...\n 167│- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.\n*168│- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.\n 169│- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs...\n 170│- **Config knobs** (pulse period, echo-commune window, route-guard window, daily refresh) are spt-core **global settings** with optional **per-endpoint override** — never per-adapter.\n 171│- **Event-block vocabulary and file-drop filenames are fixed spt-core constants** (documented for adapter authors), not manifest-configurable. <!-- [doc->REQ-RESUME-CONTEXT-PULL] --> This includes the **checkpoint sentinel `!!checkpoint!!`** — the agent-checkpoint trigger an adapter embeds in a commune/signoff drop body (one bare token = checkpoint with default wake; a `!!checkpoint!! <text> !!checkpoint!!` pair makes the inter-marker text a custom wake directive). It is spt-core control metadata: spt-...\n    │...\n 193│- **`api history-log`** — Path B: ingest normalized records into spt-core's native history store.\n*194│- **`api presence`** — adapter reports user interaction → updates the presence datum `(last_active_node, last_active_endpoint, ts)`. In the spt-hosted topology, presence is **also** updated by the broker *detecting* (sensing, not watching/logging) user input on a held PTY — privacy-preserving (it notes that input occurred, records no content).\n*195│- **`api emit --type <sensory_type> <payload>`** — a broker-launched **Shell** binary pushes a sensory payload to its owner agent (owner known from `api bind`; REST-only, never spooled). See the Shell model.\n 196│\n 197│**Not `api` commands — file-drop flow:** `commune` and `signoff` are deprecated as commands (modern SPT) in favor of file drops. The agent/adapter writes `<id>-commune.md` / `<id>-signoff.md`; the daemon watches the manifest-declared commune/signoff dirs (the spawn-session seam fields), ingests, and deletes (drop files are daemon-owned single-writer — KNOWN-HAZARDS 6.4). These stay off the `api` surface and the agent surface alike.\n 198│\n    │...\n 217│\n*218│**spt-hosted (terminal wrapper / GUI launcher; the daemon launches the binary into a broker PTY):**\n*219│1. The frontend/CLI launches the agent: the daemon runs the **spawn-session** command template into a broker-held PTY.\n*220│2. The binary boots and fires **`api bind`** (or skips it under the strict UUID-injection + stable-pid commitment). **No catalyst/seed file** — the daemon is the launcher, already holds a direct channel (it spawned the process and owns the PTY), so a file round-trip would only add drive churn for no benefit.\n*221│3. The daemon delivers events; method is **manifest-configurable per activity-state** — direct PTY injection, or a relay even here (some adapters prefer a relay over PTY injection for idle delivery), or HTTP. During *activity*, delivery still defaults to the non-disruptive hook-injection path, not raw PTY writes.\n 222│4. Psyche is daemon-spawned, same as above.\n 223│\n 224│So the old `$LIVE start` splits by topology: harness-hosted = SessionStart writes an adapter-agnostic seed → `$SPT listen <id>` consumes seed (by `parent_pid`) + resolves adapter/profile (ADR-0021) + binds + relays — legacy parity (`$LIVE start <id>` → `$SPT listen <id>`, no mandatory `--adapter`); spt-hosted = daemon spawn-session + `api bind` (direct, no file). The asymmetry is the file-bridge-only-when-no-direct-channel principle.\n## Cargo.lock\n 2925│[[package]]\n*2926│name = \"portable-pty\"\n 2927│version = \"0.8.1\"\n 2928│source = \"registry+https://github.com/rust-lang/crates.io-index\"\n 2929│checksum = \"806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be\"\n     │...\n 4086│dependencies = [\n*4087│ \"portable-pty\",\n 4088│ \"serde\",\n 4089│ \"serde_json\",\n 4090│ \"spt-msg\",\n## Cargo.toml\n  6│# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).\n *7│# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.\n *8│# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).\n  9│# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.\n 10│[workspace]\n 11│resolver = \"2\"\n## DAEMON-LIFECYCLE-COMPLETION-PLAN.md\n 27│- `brainproc::run_brain` drives **no** lifecycle — `brainproc.rs:175-182`:\n*28│  *\"Today the supervised daemon brain hosts no PTY sessions ... so this is a\n 29│  no-op now and forward-correct when daemon-hosted sessions land (the\n 30│  live-agent adapter).\"* `run_pulse_loop` has **no production caller**.\n 31│- `spt api listen` still runs the **interim in-process** Psyche + pulse —\n   │...\n 74│- **Both topologies daemon-spawn the Psyche.** harness-hosted (`listen` relay)\n*75│  and spt-hosted (`bind`, no relay, direct PTY) both get the brain-hosted Psyche,\n 76│  keyed on the endpoint being a LiveAgent — *independent of message delivery*\n 77│  (CONTEXT:168, :177).\n 78│- **Liveness is daemon-authoritative** (CONTEXT:194) — the brain marks/holds the\n 79│  Psyche perch `online`; no relay-process / pid coupling. Brain restart re-hosts\n*80│  the lifecycle (the survival the broker/brain split, ADR-0018, already buys).\n 81│- **No behavior change for the Self session** — `api boundary`, `api state`,\n 82│  commune/signoff file-drops, the digest two-origin merge all keep working; only\n 83│  *where the loop runs* changes.\n## DAEMON-LIFECYCLE-PLAN.md\n 4│> Origin: live rollout 2026-06-08 — a manual `spt daemon run` on kitsubito\n*5│> fought the systemd `spt-daemon` user service over the broker socket\n 6│> (auto-restart-fail loop). User: \"`start`/`stop` should manage the service\n 7│> when there is one; `run` must behave the same on every platform.\"\n 8│\n## DIGEST-MILESTONE-PLAN.md\n  36│**Task 0 (first commit of execution):** set the three `required_stages` above in\n *37│`traceable-reqs.toml`. The tree is green now because they are empty; activation +\n  38│first evidence land together so no commit is ever left activated-but-unevidenced.\n  39│\n  40│## 3. Design (settled — see ADR-0019, do not re-litigate)\n    │...\n 114│- [ ] `[digest]` section validated at register (bad extractor / bad knob caught at `adapter add`, not spawn).\n*115│- [ ] `spt adapter digest-proof` runs against a **real CC session JSONL** sample and surfaces a deliberately-broken extractor's drops (no silent empty).\n*116│- [ ] Spanning E2E: digest bridges a `/clear`, boundary marker present, window not reset-to-empty.\n 117│- [ ] Two-origin E2E: a psyche/echo/owl injection appears interleaved by ts.\n 118│- [ ] Full suite: workspace 0-fail, `clippy -D`, `--no-default-features`, xtask check, `traceable-reqs check` EXIT=0.\n 119│- [ ] Docs: MANIFEST `[digest]`, harness integration checklist, CHANGELOG supersession note (NOT a v0.5.0 retro-edit), CONTEXT + ADR-0019 consistent.\n## F-019-TRANSLATION-BINARY-INSTALL-DIR-RESOLVE-DESIGN.md\n  8│\n *9│**The `[message-idle-translation-binary]` never ran.** The adapter ships a **bare relative** path `path = \"cc-spt-idle-translate\"` (no `.exe`). spt-core passes it **verbatim** to the broker, which spawns it as `Command::new(path)` (broker.rs:1677) — resolved against the **daemon's cwd/PATH**, NOT the adapter install dir. It isn't on PATH → spawn fails → `build_translation` returns `None` → the session **FAILS CLOSED to raw inject** (broker.rs:1671-1677), which types the message + Enter raw (the s...\n 10│\n 11│**Proof:** (1) ground-truth instrumentation showed the inject worker DOES enqueue `{key:enter}`→`0x0d` etc. when a binary runs; (2) 0 resident translation processes for live endpoints; (3) perri set the manifest path to the ABSOLUTE `_github` exe → fresh endpoint → the binary ran and the message **submitted**.\n 12│\n   │...\n 20│\n*21│- `install_dir` is available at `launch_harness_brokered_in` (harnesshost.rs:185-196, the `install_dir` arg) — it's already recorded on the broker session (W3a/ADR-0025).\n*22│- Cleanest seam: in `launch_harness_brokered_in`, after `prepare_harness_spawn` returns `PreparedSpawn`, map `translation_binary` through `spt_runtime::runtime::resolve_program_in_dir(&path, install_dir)` when `install_dir` is `Some` (mirror the bare-fallback for `None`). Then the SpawnReq carries the resolved absolute path → `Command::new` succeeds → the real binary runs.\n 23│  - (Alternative: pass `install_dir` into `prepare_harness_spawn` and resolve there, co-located with the other program resolution. Builder's choice — keep it co-located with how the session program is resolved if cleaner.)\n 24│\n 25│No adapter change needed: the shipped `path = \"cc-spt-idle-translate\"` then resolves to `<install_dir>/cc-spt-idle-translate.exe`.\n## M0-PLAN.md\n 22│wire-version, typed+binary payloads (proto); atomic-write, spool, perch layout, registry\n*23│persistence, info.json (store). Out (later milestones): daemon/broker (M2/M3), networking\n*24│& pairing (M4), live/Psyche (M2), terminal/PTY (M3), cross-node registry *resolution* (M4 —\n 25│M0 builds local persistence only).\n 26│\n 27│## Sequencing rationale\n## M1-PLAN.md\n 28│**loopback only**); harness manifest + Psyche/live lifecycle + `api` surface (M2);\n*29│PTY/terminal + the consolidated daemon (M3). **Interim-model note:** M1 ready\n 30│agents run a standalone per-agent TCP listener; M3's `spt-daemon` consolidates all\n 31│listeners into one supervisor (CONTEXT \"spt-daemon\"). M1 builds the messaging\n 32│mechanism; M3 owns where it runs.\n   │...\n 94│**Stay `[]` (later milestones):** all R-NET/R-PAIR (M4), R-DAEMON/R-API/R-SEAM/R-START\n*95│(M2), R-TERM (M3), R-UPD (M3), R-REACH (M4), the live/Psyche + broker hazards\n*96│(CONPTY-DSR, RESTART-IDEMPOTENT, GRACE/ECHO/SIGNOFF, DROP-FILE-SINGLE-WRITER M2/M3).\n 97│\n 98│## Workspace change\n 99│\n## M11-PLAN.md\n 167│  reliable-ordered QUIC stream pair on the **D4 net-stream substrate** (`NetHost::open_stream`,\n*168│  broker-owned, survives brain restart, ADR-0004 §B) — a NEW held-stream lifecycle, distinct\n 169│  from the one-shot request/reply `serve_shell_link`. W3 anchor = the NetHost **loopback**\n 170│  conn (C2 same-node mock; cross-node real-Iroh is the W5 rung, R2). **Open** at shell-online\n*171│  (`shellhost::bind_shell_by_token`, manifest-enabled only); broker holds the `stream_id` in\n 172│  shell state **online-generation stamped** — *the* crash-proof no-stale-stream leg (R1):\n 173│  a stale stream_id from a prior online-gen is NEVER reused after relink even if\n 174│  `close_shell`'s drop never ran (brain crash mid-close). **Close** at `close_shell`\n## M12-CANDIDATES.md\n 32│\n*33│3. **spt-hosted HARNESS bringup + local user PTY attach (CLI). — CONFIRMED MISSING, TOP PRIORITY.**\n 34│   Investigation 2026-06-14 (file evidence):\n*35│   - Daemon-side PTY spawn EXISTS but shells-only: `crates/spt-daemon/src/brain.rs:spawn_session_pid()`,\n*36│     `crates/spt-daemon/src/shellhost.rs:launch_shell_brokered_in()`; CLI only `spt shell spawn`.\n*37│   - NO user CLI to launch a HARNESS endpoint into a broker PTY (harnesses still\n 38│     harness-hosted: adapter launches binary → binds via `spt api listen`).\n 39│   - Local user terminal attach = DESIGN-ONLY: cross-node remote attach exists\n 40│     (`crates/spt-daemon/src/attach.rs`, M4-D5b), but no local `spt attach <id>`.\n   │...\n 44│     spawn-session seam for harnesses.\n*45│   - **`spt rc`** — connect to an spt-hosted PTY; works **cross-node** (local attach is the\n 46│     same command, subset). Already M12-slated per operator. Wraps the existing cross-node\n 47│     attach machinery (`crates/spt-daemon/src/attach.rs`) into a user CLI.\n 48│   - spt-claude-code's `cc`/`cc <id>` = thin wrapper over `spt endpoint run` defaulted to\n   │...\n 50│   **GATING: full-fat M12 is now a PREREQUISITE for spt-claude-code** — operator ruled v1\n*51│   acceptance = legacy parity AND cross-subnet/PTY proof, so local attach + the picker are\n 52│   MANDATORY v1. todlando builds full-fat M12 after this grill, BEFORE perri starts.\n 53│\n 54│4. **Cross-adapter fallback targets `<adapter>:<profile>`.** Node-wide cross-adapter\n   │...\n 60│- **Psyche cross-machine sync = automatic** (M4-D6c, P2P over Iroh; `crates/spt-daemon/src/sync.rs`,\n*61│  `pump/sync.rs`). tracked/ replicates in-subnet by default; gh-repo `psyche-sync-setup`\n 62│  RETIRED to optional hub-mode. → spt-claude-code `/spt:setup` #8 reduces to a rare opt-in.\n 63│- **`spt endpoint fork <src> <new_id>` exists** (cross-subnet copy, same-node). Forking IS a\n 64│  first-class CLI offering. (`cli.rs:221`)\n## M12-ENDPOINT-RUN-PICKER.md\n  8│> Relationship: `spt endpoint run` = the rich bringup picker. `spt rc` = connect to an\n *9│> spt-hosted PTY (cross-node attach, M12-slated). spt-claude-code's `cc`/`cc <id>` =\n 10│> thin wrapper over `spt endpoint run` defaulted to the `claude-spt` adapter.\n 11│\n 12│## Layer 1 — kind\n   │...\n 85│- Keybinds: ←→ category · ↑↓ item · enter confirm · esc back · / filter · n new. **Phone\n*86│  caveat:** keybinds risk accidental input on a future smartphone PTY app → design the\n 87│  interaction model so a tap/non-keybind mode can layer on later (don't hard-couple to keys).\n 88│- Type-to-filter for large sets: YES (`/`). Keybind legend pinned at bottom: YES.\n 89│- \"View now\" = read-only attach (watch, no input); attached sessions need an explicit detach\n*90│  keybind. Empty category → \"no endpoints — press n to create\".\n 91│- `cc`/`cc <id>` wrapper: bare `cc` → picker scoped to claude-spt; `cc <id>` → if exists,\n 92│  attach/start directly (skip picker); else create-new with id pre-filled. Non-interactive\n 93│  flags enable `cc-<id>` shortcut scripts.\n## M12-PLAN.md\n  4│> (full picker spec). **M12 is a GATING PREREQUISITE for the downstream `spt-claude-code`\n *5│> project** — operator ruled v1 acceptance = legacy parity AND cross-subnet/PTY proof, so\n *6│> local PTY attach + the bringup picker are mandatory before perri starts. Build full-fat.\n  7│>\n *8│> Value prop framing: `spt rc` (connect to an spt-hosted PTY, cross-node) is a core M12 value.\n  9│>\n 10│> **Traceability:** add a `REQ-*` to `traceable-reqs.toml` for each item BEFORE building it\n 11│> (rule 3); activate stages incrementally as evidence lands, `int` at the final wave (rule 5).\n   │...\n 14│## What already EXISTS (don't rebuild — verified 2026-06-14)\n*15│- Daemon PTY spawn (`brain.rs:spawn_session_pid`, `shellhost.rs:launch_shell_brokered_in`) —\n 16│  shells-only today.\n 17│- spawn-session seam (`runtime.rs:spawn_session`) — implemented, shell-exercised.\n 18│- Cross-node remote attach (`attach.rs`, M4-D5b) — daemon-internal, no user CLI.\n   │...\n 22│## Wave 1 — spt-hosted HARNESS bringup + attach (the gating foundation)\n*23│Goal: a user can launch a harness endpoint into a broker PTY and attach a local terminal.\n 24│- **T1.1** `spt endpoint run` non-interactive core: wire the spawn-session seam for HARNESS\n*25│  endpoints (not just shells) → daemon spawns the harness command template into a broker PTY,\n 26│  registers the perch, returns id. Flags: `--adapter <a[:profile]>` `--id <id>` `--create`\n 27│  `--resume <session>` `--attach|--start|--view`. (Foundation for `cc-<id>` shortcuts.)\n*28│- **T1.2** Local user PTY attach CLI: `spt rc <id>` (and the local case) — attach a terminal\n*29│  to a broker-held PTY; works cross-node (reuse `attach.rs`), local is the subset. Read-only\n*30│  `--view`. Clean detach. Mind KNOWN-HAZARDS (PTY ownership, no termination on detach).\n 31│- REQs: REQ-HOST-RUN-*, REQ-RC-* (add to manifest). Stages doc+impl+unit this wave.\n 32│\n 33│## Wave 2 — `spt endpoint run` interactive picker (ratatui)\n   │...\n 74│  elevation on Win+Linux; whoami alias. Full suite green; `traceable-reqs check` EXIT=0;\n*75│  cross-subnet/PTY proof demonstrated (this is the spt-claude-code v1 acceptance dependency).\n 76│- Then unblock perri to begin `spt-claude-code` (see `../spt-claude-code/SCOPE.md`).\n## M12-W1-PLAN.md\n  3│> JIT build plan (todlando, 2026-06-14). Parent: `M12-PLAN.md` W1. Design-locked with doyle\n *4│> (one-path `rc` invariant + `[session.self]`→broker-PTY mirror approved). This doc is the\n  5│> survivable build spec — coding off this is mechanical.\n  6│\n  7│## Goal (gate condition for W1)\n *8│A user launches a HARNESS endpoint into a broker-held PTY and attaches a local terminal;\n  9│the same `spt rc` path attaches cross-node. Reverses today's harness-hosted-only launch.\n 10│\n 11│## REQs (registered, `traceable-reqs.toml`)\n*12│- **REQ-HOST-RUN-1** — `spt endpoint run` spawns `[session.self]` into a broker PTY for\n*13│  kind=harness (mirrors `shellhost::launch_shell_brokered_in`), registers the perch, returns id.\n*14│- **REQ-RC-1** — `spt rc <id>` attaches a local terminal to a broker-held PTY via the\n*15│  cross-node attach pump; local = degenerate single-node case (ONE path). `--view` read-only,\n 16│  clean detach (no session termination).\n 17│- Activation order (rule 2, memory [[traceable-per-wave-activation]]): each commit sets\n 18│  `required_stages` to ONLY the evidence in that commit, cumulative. doc+impl+unit land this\n   │...\n 21│## Verified seams (file:line)\n*22│- **Broker spawn**: `spt-daemon/src/brain.rs:380` `spawn_session_pid(SpawnReq) -> (u64, Option<u32>)`.\n 23│  `SpawnReq{program,args,rows,cols,endpoint}` (`msg.rs:124`), reply `Spawned{session_id,pid}`.\n*24│- **Mirror target**: `spt-daemon/src/shellhost.rs:210` `launch_shell_brokered_in(...)` —\n 25│  fills opaque template (`runtime::fill_template`+`tokenize`), `Brain::cold_start` → `spawn_session_pid`,\n 26│  records pid. New harness path mirrors this but: (a) template = `[session.self].command`,\n*27│  (b) endpoint label = the harness endpoint id (NOT shell_session_label), (c) PTY size = real\n 28│  terminal dims (a harness IS a terminal — unlike shells' 24x512 anti-wrap), (d) registration\n 29│  per the harness perch contract (fill from trace), (e) NO link-token (harness binds via `api`).\n*30│- **Attach pump**: `spt-daemon/src/attach.rs` — `serve_attach(brain,stream_id,endpoint,origin_node,net_from_seq)`\n 31│  -> `AttachServeOutcome{Refused,Exited(Option<i32>),Detached}`; `request_attach(brain,conn_id,session_id,from_seq,open_op)->stream_id`;\n 32│  `send_attach_input(brain,stream_id,bytes,wire_op)`. Cross-node rides QUIC (`spt-net/src/net/attach.rs`\n 33│  `AttachRecord::{Request,Input,Output,Exit}`). Local = single-node degenerate case (loopback peer).\n   │...\n 50│  No new daemon registration code — `spt endpoint run` only needs to (1) spawn `[session.self]`\n*51│  into the broker PTY labeled `<id>`, (2) ensure the harness binds to `<id>` (the env/id question\n*52│  below). Broker-held PTY ⇒ harness survives `rc` detach (hazard satisfied, by construction).\n*53│- **cwd — REQ-HOST-RUN-2 (doyle-tracked, post-W1 / pre-M12-gate).** W1 ships broker-inherited\n 54│  cwd (= daemon cwd) as a bringup-PROOF SHORTCUT only. NOT optional for M12: the consumer (Claude\n 55│  Code) is project-scoped — wrong cwd ⇒ wrong `.claude`/session-history/digest source; `cc <id>`\n 56│  at a project root must land CC in that project. Real cwd DOES need the additive `SpawnReq.cwd`\n*57│  wire change (broker spawns the PTY; cwd must reach `portable-pty` CommandBuilder). N-1-safe,\n 58│  done deliberately. Registered now (stages=[]); must land before the M12 gate.\n*59│- **Follow-up flags (doyle, NOT W1 blockers):** (1) PTY *resize* events must flow through the\n*60│  attach pump for an interactive harness — W1 sets initial real dims only; live resize = later W.\n 61│  (2) Detach keybind (T1.2.5): explicit, cannot collide with harness passthrough input (legacy\n 62│  capsule = ctrl-b prefix); documented. Folded into REQ-RC-1.\n 63│- **RESOLVED — VERDICT (A): argv `{id}` template, NO SpawnReq wire change.** Harness learns its\n   │...\n 66│  `fill_template` (`runtime.rs:93`) fills `{id}`/`{session_id}`/`{adapter_name}` (`SUBSTITUTION_KEYS`\n*67│  `runtime.rs:36`) before tokenize. Broker spawn `broker.rs:531 dispatch_spawn` →\n*68│  `pty.rs:95 PtySession::spawn_program(program,args,size)` → portable-pty CommandBuilder INHERITS\n*69│  broker env (no env param). `[env]` doc confirms \"spt-hosted inherits from the broker.\" So the\n 70│  daemon fn just substitutes `{id}` + the standard keys; harness self-binds to `<id>` post-spawn.\n 71│  Session id: fresh create = mint one; `--resume <session>` = use that.\n 72│\n   │...\n 76│   `--resume <session>`, `--attach|--start|--view` (mutually-exclusive terminal action).\n*77│2. New daemon fn (shellhost sibling, e.g. `harnesshost::launch_harness_brokered_in` or extend\n## M12-W1.5-PLAN.md\n *1│# M12 Wave 1.5 — broker loopback-conn + local `spt rc` attach\n  2│\n  3│> JIT plan (todlando, 2026-06-14). doyle-scoped after W1 PASS. Goal: LOCAL `spt rc <id>`\n *4│> attach rides the BYTE-IDENTICAL `serve_attach` pump as cross-node — because self-dial is\n  5│> impossible (iroh: \"Connecting to ourself is not supported\", pinned by\n  6│> `loopback_self_dial_is_refused_local_uses_fallback_transport`). doyle ruling = **B1**: an\n *7│> in-process broker loopback connection, NOT B2 local-direct (which would reintroduce a\n *8│> lesser-tested local branch). Sensitive broker-layer work (KNOWN-HAZARDS 6.7 zone).\n  9│\n 10│## The invariant (doyle, binding)\n*11│ONE `serve_attach` pump. Transport is swappable UNDER the stream. Cross-node = QUIC conn;\n*12│local = broker loopback conn. Both surface as broker net-stream events (`NetStreamData` /\n*13│`net_stream_subscribe` / `net_stream_send`) so `serve_attach` (target) + the `rc::pump`\n*14│(operator) are unchanged. The W1.5 int test MUST assert both transports hit the same pump.\n 15│\n 16│## What already exists (W1)\n*17│- Operator pump: `spt/src/rc.rs` `run_attach` + `pump` (transport-invariant — reads\n 18│  `NetStreamData`, writes via `send_attach_input`). Currently codes the now-REFUSED self-dial\n 19│  (`brain.net_dial(net_status().addr)`) — **this is the one line W1.5 swaps** to a loopback-dial.\n 20│- Target serve: `spt-daemon/src/attach.rs serve_attach`, auto-dispatched at\n   │...\n 25│## B1 design (the loopback conn)\n*26│A broker-internal connection with NO iroh handshake whose two bidi stream ends are wired\n 27│in-process, so a stream opened by the operator side delivers its bytes as `NetStreamData` to\n 28│a subscriber on the \"remote\" side (which the dispatch loop serves via `serve_attach`), and\n 29│vice-versa. Local `spt rc`: loopback-dial → `conn_id` → `request_attach(conn_id, session_id)`\n*30│→ subscribe → pump — identical to cross-node from `request_attach` up.\n 31│\n 32│### Investigation TODOs (read before coding — NOT yet done)\n*33│1. **`spt-daemon/src/broker.rs`**: the conn table + stream table + how `dispatch_*` routes an\n 34│   inbound stream's first frame to `StreamFamily::Attach`→`serve_attach`. How a QUIC stream's\n 35│   bytes become `NetStreamData` events (the accept/serve plumbing). Find the seam to inject a\n 36│   local pair.\n   │...\n 41│   stream whose sends loop back to a peer stream id the dispatch loop picks up as inbound.\n*42│   Likely an in-process channel pair inside the broker, surfaced through the SAME\n 43│   `NetStreamData`/`net_stream_send`/`net_stream_subscribe` IPC the QUIC path uses.\n 44│4. **Brain-side**: a `brain.net_dial_loopback(op) -> conn_id` (or reuse `net_dial` with a\n*45│   sentinel local addr the broker recognizes). Decide: new frame KIND vs sentinel addr.\n 46│5. **Origin for the gate**: `serve_attach`'s `origin_node` is the handshake-proven id. A\n 47│   loopback conn has no handshake — use THIS node's own id as origin (access_check is\n 48│   default-open for self; confirm). Don't fabricate a foreign origin.\n   │...\n 50│## Tasks\n*51│- **T1.5.1** Broker loopback conn: `Conn::Loopback` + in-process stream cross-wiring; inbound\n*52│  loopback streams dispatch to `serve_attach` exactly like QUIC. (broker.rs + nethost.rs)\n 53│- **T1.5.2** `brain.net_dial_loopback` (or sentinel) → `conn_id`; minimal Brain API.\n 54│- **T1.5.3** Swap `rc.rs` conn-acquisition: local `run_attach` loopback-dials instead of\n*55│  `net_dial(own_addr)`. (One seam; pump unchanged.)\n*56│- **T1.5.4** Int test `local_attach_via_loopback_conn_rides_the_same_pump`: spawn a session,\n 57│  loopback-dial, request_attach, drive input, render echo, detach→session outlives. PLUS the\n*58│  **one-pump assertion**: both the cross-node int and this local int exercise the same\n*59│  `serve_attach`+pump path (assert by construction / shared helper, per doyle).\n 60│- **T1.5.5** Activate REQ-RC-1 local int evidence (int already active from W1 cross-node face;\n 61│  add the `[int->REQ-RC-1]` tag on the local test). `traceable-reqs check` EXIT=0.\n 62│\n\n## .config/\n### nextest.toml\n  5│#   Phase A  `-E 'not ( <HEAVY> )'`  — the light pool, full parallelism, fast.\n *6│#   Phase B  `-E '<HEAVY>'`          — the heavy real-broker/PTY/daemon-tree class,\n  7│#                                      run AFTER Phase A so the box is QUIET, and\n  8│#                                      serialized by the test-group below.\n  9│# <HEAVY> is the SAME filter string in both CI phases AND in the test-group override\n   │...\n 14│[profile.default]\n*15│# A hung test (e.g. a blocking PTY/socket read that never completes) must FAIL FAST,\n 16│# never wedge the CI job up to its wall-clock timeout. Warn once a test exceeds 60s,\n 17│# then SIGKILL + fail it after 4 such periods (4 minutes). No legitimate test in this\n 18│# workspace runs that long; this is purely the no-infinite-wedge backstop.\n   │...\n 20│\n*21│# Concurrency cap for the heavy real-broker / PTY / daemon-tree integration tests.\n 22│#\n 23│# WHY: nextest fans a phase into ONE parallel pool sized to the runner's core count.\n*24│# The \"heavy\" tests each spin a REAL broker, forkpty a REAL PTY child, and/or spawn a\n 25│# REAL `spt daemon`/brain process tree. Two failure modes were observed on the shared\n 26│# self-hosted runners:\n*27│#   1. heavy-vs-heavy contention: ~16 real brokers launching at once contend for the\n*28│#      scheduler / PTY allocation / socket setup, producing wait-deadline / PTY-spawn\n 29│#      / child-reap TIMING flakes whose victim rotates run to run (spawn_env on Linux\n*30│#      forkpty, g4 on Windows, controller_writer, attach_survives, …).\n 31│#   2. heavy-starved-by-light: even with the heavy class serialized, the ~1200 LIGHT\n 32│#      tests still ran at full parallelism in the SAME pool and saturated the box, so\n 33│#      a heavy test's helper thread lost the CPU race (a_journaled_input_wedge\n   │...\n 38│# can't bite either, even on the idle box. max-threads = 1 is contention-free and\n*39│# deterministic (a cap > 1 still lets two real brokers race = lucky, not reliable).\n 40│# Group membership == the <HEAVY> phase filter. (Driver-pacing in inject_control_wedge\n 41│# additionally removes that gate's self-inflicted CPU storm — a correctness nicety on\n 42│# top of the structural split.)\n 43│[test-groups]\n*44│heavy-broker-pty = { max-threads = 1 }\n 45│\n*46│# <HEAVY> — the heavy real-broker/PTY/daemon-tree class, by package+binary. Swept by\n*47│# concrete evidence: a binary is HEAVY iff some test in it spawns a real PTY child\n 48│# (a `SpawnReq`/`spawn_program` with a `cat`/`findstr`/`yes`/`sleep`/`ping`/FLOOD\n 49│# program) OR a real `spt daemon`/brain process tree (`supervise_brain`, CARGO_BIN_EXE\n*50│# daemon run). Pure in-process loopback-QUIC wire tests (net_broker with NO PTY child:\n*51│# netbroker, netstream, mesh, sync, replicate, presence, …) are LIGHT and stay in\n*52│# Phase A. All spt-term test binaries forkpty a real PTY child, so the whole package\n 53│# is heavy. Keep this string IDENTICAL to both -E filters in ci.yml.\n 54│[[profile.default.overrides]]\n*55│filter = '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|livehost_bootgate_e2e|n1_pairing|oneliner_e2e|resume_te...\n*56│test-group = 'heavy-broker-pty'\n 57│\n*58│# FLAKE-LEDGER #14 (W-3, todlando 2026-07-06): the rc real-broker UNIT tests inside the\n 59│# `spt` BINARY escape the integration-binary HEAVY filter above — they are `kind(bin)`\n 60│# (the binary's own `#[cfg(test)]` module), not `kind(test)` — so they ran in the LIGHT\n*61│# Phase-A pool at full parallelism. `rc::tests::attach_viewport_reconnects_across_a_broker_bounce`\n*62│# (a real broker + a broker-bounce reconnect) then TIMED OUT @240s under a 360-way\n 63│# `-p spt --bin spt` run; 1/1 ISOLATED green in ~1.3s — the 4th distinct full-parallel\n 64│# trip, so the remedy is RECIPE hardening (serialize the class), not the test. Fold the\n*65│# rc broker/bounce class into the same heavy group so it can never be starved again;\n 66│# EXTEND this name set on each new sighting (the FLAKE-LEDGER discipline).\n 67│[[profile.default.overrides]]\n\n## .github/workflows/\n### ci.yml\n  41│      # Concurrent-attach per-IPC-call READ-deadline headroom (the result-gating\n *42│      # cold_start_pump brains: a_journaled / p0_paste attachers, drivers, viewer,\n  43│      # keystroke op + the W2 helpers). DISTINCT from the watchdog above: the Duration\n *44│      # passed to cold_start_pump is io_timeout (call_deadline = now + io_timeout) — NOT\n  45│      # a connect bound (the socket connect is unbounded). request_attach reads the\n  46│      # journaled net_open_stream ack under that deadline; on the shared self-hosted\n  47│      # boxes co-tenant scheduler starvation pushes that SERVICED ack past the LOCAL-tight\n    │...\n  54│    # A wedged test must not block the single self-hosted runner slot (the\n *55│    # 2026-06-03 handoff.rs ConPTY stall burned 22 hosted minutes unbounded).\n  56│    # 50 (was 25): the slow Linux runner (kitsubito) needs more than 25min for a\n  57│    # full build + the whole suite (the int tests carry fixed wall-clock sleeps);\n  58│    # a version-bump full rebuild pushed v0.7.0 over. The real fix is the build\n    │...\n  81│      # guard — but a prior run SIGKILLed at the nextest slow-timeout backstop (240s)\n *82│      # leaves its forkpty children reparented to init. The inject_control_wedge\n  83│      # gates' flood child is `yes FLOOD…`, which burns a FULL CORE forever; several\n  84│      # such orphans accumulated across failed runs saturate this shared runner's CPU\n *85│      # and re-introduce the very load/timing contention the heavy-broker-pty\n  86│      # test-group removes (the symptom the kitsubito reliability run hit). Reap the\n  87│      # flood orphans (unique marker — cannot match an unrelated process) plus any\n  88│      # workspace-scoped leaked daemon, before checkout. Best-effort, never fails.\n    │...\n 116│      # TWO-PHASE split (correctness > raw speed for the release gate): the heavy\n*117│      # real-broker/PTY/daemon-tree class is timing-sensitive and STARVES when it\n 118│      # shares the global pool with the ~1200 light tests on a saturated self-hosted\n 119│      # box (kitsubito a_journaled_input_wedge subscribed=false; Windows\n 120│      # job_escape_e2e tree-reap timeout). So run the LIGHT pool first at full\n 121│      # parallelism, THEN the HEAVY class on the now-idle box (and serialized by the\n*122│      # heavy-broker-pty test-group). The <HEAVY> filter MUST stay identical in both\n 123│      # `-E` strings below AND in .config/nextest.toml's override — Phase A is the\n 124│      # exact negation of Phase B, so the two phases partition the suite with nothing\n 125│      # dropped or double-run.\n 126│      - name: Test — Phase A (light pool, full parallel)\n*127│        run: cargo nextest run --workspace --no-fail-fast -E 'not ( (package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e...\n 128│      - name: Test — Phase B (heavy class, serialized, on a now-quiet box)\n*129│        run: cargo nextest run --workspace --no-fail-fast -E '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|liv...\n 130│      # nextest does not run doctests — keep them on `cargo test --doc`.\n 131│      - name: Doctests\n 132│        run: cargo test --workspace --doc\n    │...\n 178│      # launched from THIS checkout's target dir — scoped to $GITHUB_WORKSPACE so\n*179│      # it never touches a live agent's daemon/broker (hfenduleam is a\n 180│      # SELF-HOSTED runner sharing the machine with live agents; a machine-wide\n 181│      # `spt.exe` kill would tear down production). A null Path row is skipped by\n 182│      # the `-like` (the safe direction). Linux can overwrite a running exe, so\n    │...\n 241│\n*242│  # ---- D7-2 new-brain × old-broker N-1 verb-surface gate (ADR-0018 V6) ----\n 243│  # Steady state after every routine brain-only update is a NEW brain talking to\n*244│  # an OLDER broker, so the current brain must serve the socket verbs against a\n*245│  # prior-era broker without a clap/arity reject or a verb desync (KH-2.3). The\n*246│  # \"old broker\" is the restoration split boundary — D1 skeleton 0c95435 (a\n 247│  # pre-restoration release has no brain supervisor and cannot pair; doyle D7 vet\n 248│  # call 4a). It is built ONCE into a stable cache keyed by the pin SHA (mirrors\n 249│  # the traceability job's self-heal) and reused until the pin advances — to the\n 250│  # latest PUBLISHED split-era release once one exists. The n1_pairing harness is\n*251│  # env-gated (SPT_N1_OLD_BROKER), so the normal `cargo test` sweep silent-skips\n 252│  # it; this job sets the env and turns the skip into a real gate on both runners.\n 253│  n1-gate:\n 254│    strategy:\n    │...\n 275│        with:\n*276│          # The pinned old-broker ref (0c95435) must be reachable to build it.\n 277│          fetch-depth: 0\n 278│      # Same Linux mold linker as the `test` job — and, since both Linux jobs run\n 279│      # on the same runner and share its target dir, matching RUSTFLAGS lets this\n    │...\n 283│        run: echo 'RUSTFLAGS=-C link-arg=-fuse-ld=mold' >> \"$GITHUB_ENV\"\n*284│      - name: N-1 gate (new-brain × old-broker) — linux\n 285│        if: runner.os == 'Linux'\n 286│        shell: bash\n 287│        run: |\n    │...\n 289│          PIN=0c95435\n*290│          CACHE=\"$HOME/spt-n1-oldbroker\"\n 291│          BIN=\"$CACHE/target/debug/spt\"\n 292│          # Self-healing cache: rebuild the pin only when it moves. Clone OUTSIDE\n 293│          # the workspace (a clone inside gets adopted by the cargo workspace).\n    │...\n 300│          fi\n*301│          SPT_N1_OLD_BROKER=\"$BIN\" cargo test -p spt --test n1_pairing -- --nocapture\n*302│      - name: N-1 gate (new-brain × old-broker) — windows\n 303│        if: runner.os == 'Windows'\n 304│        # pwsh (PowerShell 7), NOT `powershell` (5.1): the runner's 5.1 execution\n 305│        # policy refuses the unsigned temp script GitHub generates\n### release.yml\n  29│        # (crates/spt-daemon/src/release.rs) — the update-set + installer key on\n *30│        # them. `target` empty = the host-default toolchain (gnu / msvc); a set\n  31│        # `target` cross-builds that Rust triple (the static musl tier, W3).\n  32│        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\n  33│        include:\n    │...\n 127│        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\n*128│        # release loudly rather than shipping an empty/boilerplate body — the\n 129│        # changelog is not optional.\n 130│        run: |\n 131│          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2","truncation":{"content":"# spt-core/\n## CHANGELOG.md\n 16|\n*17|> **Applying this update needs a daemon restart.** Unlike the recent seamless\n 18|> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n 19|> --apply` lands it via a full daemon restart rather than a live swap — your\n 20|> sessions restart as the daemon comes back. This is a one-time cost to install\n...\n 46|  fix lives in the incoming version, updating *to* this release is what makes your next\n*47|  in-place update seamless.)\n 48|\n 49|## [0.30.4] - 2026-07-09\n 50|\n...\n 60|  session's controller, so your open sessions and `spt rc` keep working across the\n*61|  brain swap. (Follow-up to the previous release's seamless-swap fix.)\n 62|\n 63|## [0.30.3] - 2026-07-09\n 64|\n...\n 72|  completing the swap. The incoming version now drives the running daemon to clear a\n*73|  hard-stopped prior session during the handoff, so the update completes seamlessly with\n 74|  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\n 75|  incoming version's logic, updating *to* this release is what makes your next in-place\n*76|  update seamless.)\n 77|\n 78|- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\n 79|  \"never wakes an idle target\") could still be delivered to a target the instant it went\n...\n 112|  would stall the whole peer-sync round, so `spt subnet status` listed the subnet's\n*113|  members but showed an empty live-node list and warned that the peer pump had\n 114|  stalled. An unresponsive peer is now dropped and retried on its own instead of\n 115|  holding up the round, so the node keeps converging and peers reappear.\n 116|\n...\n 158|- **Hosted sessions are re-launched after a daemon restart.** Previously-online sessions come back automatically once the daemon restarts (including as part of an update) instead of being left offline. The restart still interrupts them — this restores the session, it does not preserve its in-flight work.\n*159|- **An attached terminal no longer freezes behind a stalled session.** If a session's underlying process hangs or is suspended, `spt rc` keeps updating and a stuck connection is dropped automatically so you never lose control; reattaching and taking control (`--take`) keep working. `spt daemon status` reports when such a drop happened.\n 160|- **A long, multi-line message typed into a live agent's terminal arrives complete.** When a message is delivered by typing it into a live session, it is no longer truncated at the front. (Messages drained through the polling channel were never affected.)\n 161|- **`spt endpoint digest --json` no longer repeats rows.** Activity replayed across a session checkpoint is collapsed to a single entry.\n 162|- **Waking an agent starts exactly one session.** Two wake requests arriving at once no longer launch it twice.\n...\n 272|- **Message delivery and scheduled wake-ups now survive a context reset.** After a context reset, an idle agent could stop receiving messages and scheduled wake-ups until its session was restarted; the session boundary now re-stamps its readiness in the correct order across the reset, so messages and wakes right after a reset are delivered instead of dropped.\n*273|- **A `--force-native` send that cannot be delivered now says why.** The failure message distinguishes its cause — empty message, no daemon running, endpoint active mid-turn, no working translation binary, or not a controllable endpoint — instead of a single identical message for every case.\n 274|\n 275|## [0.23.0] - 2026-07-03\n 276|\n...\n 326|- **`spt endpoint list --show-all`.** Suspended (resting) endpoints are now hidden by default to cut clutter; `--show-all` reveals them. Each machine's total discloses how many were hidden (nothing silently vanishes), and a corrupt record always shows regardless.\n*327|- **`spt daemon status` now reports the running daemon's version.** It shows the version the running daemon was built from beside the installed version and flags a mismatch — so you can tell when an update is on disk but the daemon still needs a restart to fully load it. The `--json` output gains matching `broker_image` / `broker_stale` fields.\n 328|\n 329|### Changed\n 330|\n...\n 349|\n*350|- **`spt send` with no explicit sender now stamps `cli@<node>`.** A delivered message never shows a blank sender — a message sent without a from-identity is attributed to the originating node instead of arriving empty.\n 351|- **Clearer delivery diagnostics.** Spool and idle-drain failures now emit loud, one-shot log lines, and operator notes on message-delivery timing are documented.\n 352|\n 353|### Fixed\n...\n 386|\n*387|- **Attaching to a running terminal session no longer corrupts the scrollback.** Cold-attaching to a full-screen TUI (e.g. Claude Code) used to replay the raw output ring into a fresh terminal — flipping the alternate screen on and off mid-stream and spilling TUI frames into history. The broker now keeps an authoritative screen model and synthesizes a clean repaint of the current screen on a cold attach, so you get the live frame, not a corrupt transcript. (A resume from a known point still re-fetches ...\n 388|- **`spt rc` to an already-running endpoint no longer prints garbled escape codes on Windows 10 / raw PowerShell.** The garbling was the same raw-ring replay problem as above (not a client terminal-mode issue — `spt endpoint run --attach` rendered fine in the same terminal); the clean-repaint-on-attach fix resolves it. VT output is also enabled defensively on legacy Windows consoles.\n*389|- **`spt rc` to a live local endpoint no longer times out with `brain IPC read deadline elapsed` after a self-update.** A single panic while the broker's effect journal was locked could poison it and brick every subsequent attach; the journal now recovers from a poisoned lock, and the loopback attach path fails fast with a real error instead of an opaque 10-second deadline.\n 390|- **`spt send` across nodes no longer reports `SENT(WAN)` when nothing was delivered.** A cross-node send was a fire-and-forget local buffer write — a refused or no-perch delivery was silently dropped while the sender printed success. The receiver now writes the delivery outcome back and the sender waits for it, so a real failure prints an honest line; the dial also tries the last-known direct address first (mirroring the gossip path) instead of forcing a cold discovery on every send.\n 391|- **The remote endpoint count no longer drifts as endpoints are added and purged.** A remote viewer's `--nodes` count counted non-routable ghost rows in the denominator and never evicted purged endpoints, producing wrong ratios like `0/2` or `1/3`. The count now uses a routable-only denominator, and rows left offline past a grace window are evicted from the gossiped snapshot instead of leaking forever.\n*392|- **A locally-controlled endpoint no longer shows as \"ready to control\" on other machines.** Remote viewers only learned about a *remote* controller; a locally-driven endpoint gossiped as uncontrolled. The broker now advertises whether *anyone* (local or remote) is driving an endpoint, so remote viewers see it as controlled.\n 393|- **`spt adapter add` no longer swallows install errors.** Errors from the install-as-first-update step were discarded; the failing step's output is now surfaced, and the composite post-install step runs at install time.\n*394|- **`spt adapter update` no longer fails to re-register after fetching.** The update derived the install directory from the `[update]` repo name rather than the adapter's registered source directory — so after a repo rename it wrote to a fresh empty directory and then failed re-register with `os error 2`. Update now targets the registered source directory and tolerates a changed update repo.\n 395|- **`spt endpoint digest` now works for endpoints whose transcript lives under a relocated profile.** A profile that relocates the harness transcript tree (via a runtime env like `CLAUDE_CONFIG_DIR`) produced `NO_DIGEST` because the on-demand extractor ran without that environment. The digest path now carries the profile's transcript-location environment to the extractor, which locates and reads the transcript itself.\n 396|\n 397|## [0.18.0] - 2026-06-30\n...\n 441|- **`[message-idle-translation-binary]` takes a `command`.** The idle-delivery translation binary can be declared with a `command` (program token plus args, with adapter-static substitution) instead of the bare `path`, so it can be invoked as a subcommand of a consolidated adapter binary. The spawn and stdin/stdout protocol are unchanged.\n*442|- **Empty-scope creation flow.** Running `spt endpoint run` (or bare `spt`) on a node with no endpoints at all now opens directly on the adapter-creation screen instead of an empty picker.\n 443|\n 444|### Changed\n 445|\n...\n 541|\n*542|- **Idle message delivery for spt-hosted endpoints now uses a translation binary.** An adapter can declare a `[message-idle-translation-binary]`; spt-core brings it up alongside the endpoint, where it polls the relay for incoming messages and optionally emits keypresses, delays, and text injection that spt-core applies to the endpoint's terminal. Idle delivery now flows through the relay poll for every endpoint, instead of spt-hosted endpoints falling back to direct PTY injection.\n 543|- **Windows paste in `spt rc`.** Ctrl+V and right-click now paste the local clipboard into the attached agent as a single bracketed paste.\n 544|- **Windows special keys in `spt rc`.** Arrow keys, Home/End, Delete, function keys and other special keys are translated to terminal sequences and reach the agent — previously only plain characters got through.\n 545|- **`spt rc` forwards mouse scroll** to the agent's terminal.\n...\n 549|- **Pasting or typing into an spt-hosted endpoint no longer freezes the daemon.** A large paste or input burst could wedge the daemon's input path so every new attach died; input now runs on a dedicated per-session writer and never blocks the daemon (this also covers the effect-journal stall on interactive input).\n*550|- **Session resume actually resumes now.** Agent endpoints track session history and offer explicit session resume via `spt endpoint run` (`--resume <session>` or *Resume from history*); the adapter manifest can now declare a `[session.resume]` command so the relaunch reattaches the prior session instead of always starting a new one. The run picker now shows each row's working directory and local time.\n 551|- **`spt rc --view` viewers survive a high-output terminal.** A viewer no longer dies when the output backlog rolls over or it is briefly evicted — it snaps forward or skips to live instead of failing — and a slow controller can no longer starve a concurrent viewer.\n 552|- **`spt rc` no longer races a just-started agent.** Attach now waits for the endpoint to come online, instead of failing when you attach immediately after `spt endpoint run`.\n 553|- **The daemon no longer flashes or respawns a console window on Windows.**\n...\n 600|- **`spt send` now delivers to an agent whose terminal is hosted by the daemon.** When the target has no `spt api listen` relay (the daemon holds its terminal directly), `spt send` previously queued the message silently. It now injects the message into the agent's session and reports \"Sent\" only once delivery is confirmed — otherwise it queues it (\"Queued\") as before, and never reports a false \"Sent\".\n*601|- **`spt endpoint run` now fills in `[env]` values.** Placeholders such as `{id}` in an adapter's `[env]` entries (for example `SPT_ENDPOINT_ID`) were never substituted, so an agent launched without explicit flags came up with an empty endpoint id and never registered. The values are now substituted and set on the launched process.\n 602|- **A daemon-hosted terminal now reports a clear message when the daemon is stopped,** instead of failing with a raw \"failed to fill whole buffer\" that looked like a crash.\n 603|- **Clearer error when an adapter's manifest has not been extracted yet.** Using such an adapter now reports an actionable message (and logs it as skipped) instead of a raw \"os error 2\" and a silent drop.\n 604|- **A removed endpoint no longer lingers with a stale status** — it is now shown as offline.\n...\n 620|\n*621|- **The picker now loads each agent's project history** — it previously always showed empty.\n 622|- **A self-owned agent listed under both Local and Subnet no longer shows conflicting status.** The live local status is now authoritative for both listings.\n 623|\n 624|## [0.9.1] - 2026-06-17\n...\n 712|\n*713|- **`spt adapter digest-proof` now fills the same substitution keys the live extractor does.** A proof run previously supplied an empty key map, so any extractor template using `{session_id}` (the shape in the published examples) failed instead of producing a sample. It now populates `{id}` and `{session_id}` to match runtime, with an optional `--session` to pin a specific value.\n 714|\n 715|## [0.7.1] - 2026-06-15\n 716|\n## CI-SELFHOST-PLAN.md\n 64|      runner + target).\n*65|- [ ] Decide whether the netstream/netbroker UDP-binding tests need a firewall\n 66|      allowance on gravity (loopback-only since BindScope::Loopback — no).\n## CONTEXT.md\n 2|\n*3|**Platform scope:** Windows + Linux for v1. macOS is out (no test machine available) but kept structurally easy — `portable-pty` and Iroh both support it, so macOS is a later test/CI-budget decision, not a re-architecture.\n 4|\n 5|**Legacy migration:** it should be possible — ideally *automatic* — for a user to migrate an existing `claude_skill_owl` (modern SPT) install to spt-core (identity, agents, tracked Psyche context). Exact mechanism deferred to design; the commitment is that migration is a first-class supported path, not a manual rebuild.\n 6|\n...\n 29|**spt-daemon** (per-machine supervisor):\n*30|The single always-on, one-per-machine logical supervisor. Owns the PTYs for all hosted sessions, the node's network identity + WAN endpoint, the subnet registry, all spools, **all poll-listener logic, and all Psyche/pulse loops** — everything is consolidated here (no separate poll-listener or Psyche-wrapper processes; listeners already touch sessions directly under capsule/idle, and Psyche wrappers already invoke harness binaries directly, so they belong in the one supervisor). Collapses what the siste...\n 31|\n*32|Internally the logical daemon is split into two implementation layers for seamless self-update (see Self-update):\n*33|- **broker** (stable \"kernel\") — holds *only* the un-transferable, must-not-die resources: PTY master fds, the spawned harness child processes, and listening network sockets. Minimal, dumb, versioned local IPC. Almost never updates.\n*34|- **daemon brain** (\"userspace\") — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk state and re-attaches to the broker's held handles.\n 35|\n*36|Logical addressing is unchanged — still one per-machine `spt-daemon`; the broker is an internal layer, not separately addressable. There is exactly **one broker per machine** (per `SPT_HOME`) — *not* one per endpoint: a single broker holds every hosted endpoint's resources, and it is present whenever the daemon runs, even with zero endpoints online (the bare-daemon case). It is therefore the always-present per-machine layer, which is why the single-daemon lock + liveness anchor belong to it.\n 37|\n 38|**in-session relay**:\n*39|A thin, stateless `spt.exe` task that exists only in **harness-hosted** sessions (where the agent harness is the parent process and spt cannot reach into its process tree — today's Monitor model). It streams the daemon brain's events into the session's stdout. All *stateful* listener logic lives in the daemon; the relay is a dumb pipe, freely killable and respawnable. **spt-hosted** sessions need no separate harness-owned relay — the daemon owns the PTY and consumes the same poll feed itself. Idle de...\n 40|\n 41|### Deliverable shape\n 42|\n...\n 106|**composite update — `[update.post]`** (ratified 2026-06-25, v0.16.0 update-arc grill; ADR-0029):\n*107|An optional **avenue-agnostic** post-step `{ command, self_verifies }` spt-core runs **after** the primary avenue resolves, in the same `spt adapter update` — so one lever pulls the adapter `.spt` (`gh_release`) **and** runs a delegated reconcile (e.g. an adapter's `claude plugin update` cross-platform binary). It **runs unconditionally** (even when the adapter version did not change — the post-step's own idempotent check decides), receiving a one-line **stdin JSON** seam (`adapter_applied`, `adapter...\n 108|\n 109|**resident adapter binary**: an adapter-owned process spt-core keeps alive for an endpoint's lifetime (today the `[message-idle-translation-binary]`), as opposed to **ephemeral** adapter binaries — the Psyche loop (daemon-hosted, ADR-0004), the `[digest]` extractor, `[session.*]` runners, hooks — which spawn on demand and pick up an update on their next invocation. Only resident binaries are stopped/restarted on a live update; ephemerals self-heal.\n 110|_Avoid_: calling the Psyche loop or an on-demand extractor a \"resident\" binary; \"restart the endpoint\" for what is a per-binary cycle.\n...\n 116|\n*117|Governing principle: **SPT is not a harness.** Model choice, billing shape, harness-internal env, and harness-internal context are entirely the adapter's concern, expressed inside the adapter's own command templates. spt-core owns only the template *mechanism* (substitution keys), the substitution *values* it is responsible for, and the surrounding lifecycle. Env for the *endpoint binary itself* is auto-handled by spt-core/broker; env for the *agent running inside* that binary is the adapter's config (e....\n 118|\n*119|**spawn-session seam** — launch a new agent session on this node. Manifest provides: a command template; `cwd`/project; a `headless` flag (optional, default false — for the GUI's resume-of-compatible-adapters); a `resume` flag (optional); and the `commune` + `signoff` file directories relative to `cwd` (so the daemon knows where to watch). Substitution keys spt-core can supply: `{id}` and, optionally, a spt-core-generated valid session UUID (e.g. injected as `--session-id {uuid}`) so an adapter can s...\n 120|- **id resolution:** `id` is optional. With no id, spt-core reproduces today's no-id `/spt:live` behavior — run the lone live agent if that's all the project has; show a picker with proposed default IDs if the project has none; let the user choose if there are several.\n 121|\n 122|**post-spawn seam** — the just-launched binary calls an spt-core command on boot (via the adapter's SessionStart-equivalent hook) to bind itself. Needed because the harness's own session id usually isn't known until after the binary runs. Payload: the harness `session_id` (when binary-generated rather than spt-core-injected); the `parent_pid` (the stable session-binding anchor — see KNOWN-HAZARDS 2.1); an endpoint identity/type confirmation; optionally a local HTTP port the binary listens on (for HTT...\n...\n 135|\n*136|**activity/idle detection** — **not** PTY-quiescence (insufficient: e.g. CC's AskUserQuestion stalls the PTY while holding stdin and needing nuanced input) and **not** a manifest-declared idle signal. Instead, the adapter calls spt-core activity/idle commands at the right moments (from its hooks); those commands manage activity/idle **sentinels inside the session perch**. The idle state lives in the perch, owned by spt-core via the commands API exposed to adapter devs.\n 137|\n 138|**inject-input seam** — message delivery into a running session. Configurable per activity-state (activity / idle / both); multiple methods, any combination:\n*139|- PTY injection (with or without key/submit sequences) — spt-hosted topology;\n 140|- adapter hooks calling spt-core poll commands;\n 141|- an in-adapter-session child relay (à la CC's Monitor tool);\n 142|- adapter manifest requesting HTTP POST delivery to the endpoint binary on a local port (shared via the post-spawn seam).\n...\n 148|- **delivery window** (*when*) — **default** (both windows; delivers in whichever fires first) · **idle-only** (held for the idle window; delivered immediately if already idle) · **active-only** (active window only — the receiver's hook-poll; never wakes an idle agent). *active-only* is the renamed legacy **deferred** (the `deferred=1` spool column + `api poll --include-deferred` are its internal/adapter-facing names). Mutually exclusive.\n*149|- **channel restriction** (*through what*) — **unrestricted** (any configured inject method) · **prefer-native** (the translation binary if one is running, else fall back to the standard methods) · **force-native** (the translation binary and nothing else — no fallback, no spool-to-another-method). Mutually exclusive; composes with the window. *\"Native\"* = the `[message-idle-translation-binary]` PTY channel.\n 150|- **persistence** (*how long it waits*) — **durable** (default; spooled until delivered or TTL) · **ephemeral** (dropped if it cannot deliver in its accepted window — at the moment the window opens with no live carrier, or at TTL, whichever is first). Ephemeral is the **only** path permitted to drop silently (the REQ-HAZARD-IDLE-SILENT-NONDELIVERY carve-out); every non-ephemeral path spools and reports non-delivery. <!-- v0.15.0 PARTIAL (W3): ephemeral evaporation covers the spt-hosted-binary no-car...\n 151|Window restricts *when* delivery is accepted, channel restricts *which method* carries it, persistence restricts *how long* it waits — they compose freely (e.g. `force-native` + `active-only` = the binary injects during the active window, never idle; `force-native` + `ephemeral` = binary-or-nothing).\n 152|\n...\n 167|- **Hook output capability is declared per harness-event** (`can_inject`). CC's Stop hook cannot inject context — that single fact drives the echo-gate sentinel + relay fallback. The manifest expresses it so spt-core knows when to fall back.\n*168|- **Env injection is asymmetric** (file-bridge-only-when-not-launcher, applied to env): spt-hosted sessions inherit env from the broker that spawns them; harness-hosted sessions need the harness's declared env channel. With `spt` on PATH the env table is small.\n 169|- **Cross-adapter fallback** is a **node-wide setting**, not a manifest field: if a Psyche/echo invocation under one adapter is rate-limited, spt-core falls back to another adapter (e.g. `ccs` — its own adapter, not a binary-swap). <!-- [doc->REQ-MANIFEST-6] --> A fallback **target is addressed as `<adapter>:<profile>`** (not just a bare adapter_name) and resolves through the one composite-addressing resolver (`registry::resolve_option`), so a fallback may select a shipped or local profile (`ccs`, `ccs...\n 170|- **Config knobs** (pulse period, echo-commune window, route-guard window, daily refresh) are spt-core **global settings** with optional **per-endpoint override** — never per-adapter.\n 171|- **Event-block vocabulary and file-drop filenames are fixed spt-core constants** (documented for adapter authors), not manifest-configurable. <!-- [doc->REQ-RESUME-CONTEXT-PULL] --> This includes the **checkpoint sentinel `!!checkpoint!!`** — the agent-checkpoint trigger an adapter embeds in a commune/signoff drop body (one bare token = checkpoint with default wake; a `!!checkpoint!! <text> !!checkpoint!!` pair makes the inter-marker text a custom wake directive). It is spt-core control metadata: spt-...\n...\n 193|- **`api history-log`** — Path B: ingest normalized records into spt-core's native history store.\n*194|- **`api presence`** — adapter reports user interaction → updates the presence datum `(last_active_node, last_active_endpoint, ts)`. In the spt-hosted topology, presence is **also** updated by the broker *detecting* (sensing, not watching/logging) user input on a held PTY — privacy-preserving (it notes that input occurred, records no content).\n*195|- **`api emit --type <sensory_type> <payload>`** — a broker-launched **Shell** binary pushes a sensory payload to its owner agent (owner known from `api bind`; REST-only, never spooled). See the Shell model.\n 196|\n 197|**Not `api` commands — file-drop flow:** `commune` and `signoff` are deprecated as commands (modern SPT) in favor of file drops. The agent/adapter writes `<id>-commune.md` / `<id>-signoff.md`; the daemon watches the manifest-declared commune/signoff dirs (the spawn-session seam fields), ingests, and deletes (drop files are daemon-owned single-writer — KNOWN-HAZARDS 6.4). These stay off the `api` surface and the agent surface alike.\n 198|\n...\n 217|\n*218|**spt-hosted (terminal wrapper / GUI launcher; the daemon launches the binary into a broker PTY):**\n*219|1. The frontend/CLI launches the agent: the daemon runs the **spawn-session** command template into a broker-held PTY.\n*220|2. The binary boots and fires **`api bind`** (or skips it under the strict UUID-injection + stable-pid commitment). **No catalyst/seed file** — the daemon is the launcher, already holds a direct channel (it spawned the process and owns the PTY), so a file round-trip would only add drive churn for no benefit.\n*221|3. The daemon delivers events; method is **manifest-configurable per activity-state** — direct PTY injection, or a relay even here (some adapters prefer a relay over PTY injection for idle delivery), or HTTP. During *activity*, delivery still defaults to the non-disruptive hook-injection path, not raw PTY writes.\n 222|4. Psyche is daemon-spawned, same as above.\n 223|\n 224|So the old `$LIVE start` splits by topology: harness-hosted = SessionStart writes an adapter-agnostic seed → `$SPT listen <id>` consumes seed (by `parent_pid`) + resolves adapter/profile (ADR-0021) + binds + relays — legacy parity (`$LIVE start <id>` → `$SPT listen <id>`, no mandatory `--adapter`); spt-hosted = daemon spawn-session + `api bind` (direct, no file). The asymmetry is the file-bridge-only-when-no-direct-channel principle.\n## Cargo.lock\n 2925|[[package]]\n*2926|name = \"portable-pty\"\n 2927|version = \"0.8.1\"\n 2928|source = \"registry+https://github.com/rust-lang/crates.io-index\"\n 2929|checksum = \"806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be\"\n...\n 4086|dependencies = [\n*4087| \"portable-pty\",\n 4088| \"serde\",\n 4089| \"serde_json\",\n 4090| \"spt-msg\",\n## Cargo.toml\n 6|# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).\n*7|# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.\n*8|# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).\n 9|# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.\n 10|[workspace]\n 11|resolver = \"2\"\n## DAEMON-LIFECYCLE-COMPLETION-PLAN.md\n 27|- `brainproc::run_brain` drives **no** lifecycle — `brainproc.rs:175-182`:\n*28|  *\"Today the supervised daemon brain hosts no PTY sessions ... so this is a\n 29|  no-op now and forward-correct when daemon-hosted sessions land (the\n 30|  live-agent adapter).\"* `run_pulse_loop` has **no production caller**.\n 31|- `spt api listen` still runs the **interim in-process** Psyche + pulse —\n...\n 74|- **Both topologies daemon-spawn the Psyche.** harness-hosted (`listen` relay)\n*75|  and spt-hosted (`bind`, no relay, direct PTY) both get the brain-hosted Psyche,\n 76|  keyed on the endpoint being a LiveAgent — *independent of message delivery*\n 77|  (CONTEXT:168, :177).\n 78|- **Liveness is daemon-authoritative** (CONTEXT:194) — the brain marks/holds the\n 79|  Psyche perch `online`; no relay-process / pid coupling. Brain restart re-hosts\n*80|  the lifecycle (the survival the broker/brain split, ADR-0018, already buys).\n 81|- **No behavior change for the Self session** — `api boundary`, `api state`,\n 82|  commune/signoff file-drops, the digest two-origin merge all keep working; only\n 83|  *where the loop runs* changes.\n## DAEMON-LIFECYCLE-PLAN.md\n 4|> Origin: live rollout 2026-06-08 — a manual `spt daemon run` on kitsubito\n*5|> fought the systemd `spt-daemon` user service over the broker socket\n 6|> (auto-restart-fail loop). User: \"`start`/`stop` should manage the service\n 7|> when there is one; `run` must behave the same on every platform.\"\n 8|\n## DIGEST-MILESTONE-PLAN.md\n 36|**Task 0 (first commit of execution):** set the three `required_stages` above in\n*37|`traceable-reqs.toml`. The tree is green now because they are empty; activation +\n 38|first evidence land together so no commit is ever left activated-but-unevidenced.\n 39|\n 40|## 3. Design (settled — see ADR-0019, do not re-litigate)\n...\n 114|- [ ] `[digest]` section validated at register (bad extractor / bad knob caught at `adapter add`, not spawn).\n*115|- [ ] `spt adapter digest-proof` runs against a **real CC session JSONL** sample and surfaces a deliberately-broken extractor's drops (no silent empty).\n*116|- [ ] Spanning E2E: digest bridges a `/clear`, boundary marker present, window not reset-to-empty.\n 117|- [ ] Two-origin E2E: a psyche/echo/owl injection appears interleaved by ts.\n 118|- [ ] Full suite: workspace 0-fail, `clippy -D`, `--no-default-features`, xtask check, `traceable-reqs check` EXIT=0.\n 119|- [ ] Docs: MANIFEST `[digest]`, harness integration checklist, CHANGELOG supersession note (NOT a v0.5.0 retro-edit), CONTEXT + ADR-0019 consistent.\n## F-019-TRANSLATION-BINARY-INSTALL-DIR-RESOLVE-DESIGN.md\n 8|\n*9|**The `[message-idle-translation-binary]` never ran.** The adapter ships a **bare relative** path `path = \"cc-spt-idle-translate\"` (no `.exe`). spt-core passes it **verbatim** to the broker, which spawns it as `Command::new(path)` (broker.rs:1677) — resolved against the **daemon's cwd/PATH**, NOT the adapter install dir. It isn't on PATH → spawn fails → `build_translation` returns `None` → the session **FAILS CLOSED to raw inject** (broker.rs:1671-1677), which types the message + Enter raw (the s...\n 10|\n 11|**Proof:** (1) ground-truth instrumentation showed the inject worker DOES enqueue `{key:enter}`→`0x0d` etc. when a binary runs; (2) 0 resident translation processes for live endpoints; (3) perri set the manifest path to the ABSOLUTE `_github` exe → fresh endpoint → the binary ran and the message **submitted**.\n 12|\n...\n 20|\n*21|- `install_dir` is available at `launch_harness_brokered_in` (harnesshost.rs:185-196, the `install_dir` arg) — it's already recorded on the broker session (W3a/ADR-0025).\n*22|- Cleanest seam: in `launch_harness_brokered_in`, after `prepare_harness_spawn` returns `PreparedSpawn`, map `translation_binary` through `spt_runtime::runtime::resolve_program_in_dir(&path, install_dir)` when `install_dir` is `Some` (mirror the bare-fallback for `None`). Then the SpawnReq carries the resolved absolute path → `Command::new` succeeds → the real binary runs.\n 23|  - (Alternative: pass `install_dir` into `prepare_harness_spawn` and resolve there, co-located with the other program resolution. Builder's choice — keep it co-located with how the session program is resolved if cleaner.)\n 24|\n 25|No adapter change needed: the shipped `path = \"cc-spt-idle-translate\"` then resolves to `<install_dir>/cc-spt-idle-translate.exe`.\n## M0-PLAN.md\n 22|wire-version, typed+binary payloads (proto); atomic-write, spool, perch layout, registry\n*23|persistence, info.json (store). Out (later milestones): daemon/broker (M2/M3), networking\n*24|& pairing (M4), live/Psyche (M2), terminal/PTY (M3), cross-node registry *resolution* (M4 —\n 25|M0 builds local persistence only).\n 26|\n 27|## Sequencing rationale\n## M1-PLAN.md\n 28|**loopback only**); harness manifest + Psyche/live lifecycle + `api` surface (M2);\n*29|PTY/terminal + the consolidated daemon (M3). **Interim-model note:** M1 ready\n 30|agents run a standalone per-agent TCP listener; M3's `spt-daemon` consolidates all\n 31|listeners into one supervisor (CONTEXT \"spt-daemon\"). M1 builds the messaging\n 32|mechanism; M3 owns where it runs.\n...\n 94|**Stay `[]` (later milestones):** all R-NET/R-PAIR (M4), R-DAEMON/R-API/R-SEAM/R-START\n*95|(M2), R-TERM (M3), R-UPD (M3), R-REACH (M4), the live/Psyche + broker hazards\n*96|(CONPTY-DSR, RESTART-IDEMPOTENT, GRACE/ECHO/SIGNOFF, DROP-FILE-SINGLE-WRITER M2/M3).\n 97|\n 98|## Workspace change\n 99|\n## M11-PLAN.md\n 167|  reliable-ordered QUIC stream pair on the **D4 net-stream substrate** (`NetHost::open_stream`,\n*168|  broker-owned, survives brain restart, ADR-0004 §B) — a NEW held-stream lifecycle, distinct\n 169|  from the one-shot request/reply `serve_shell_link`. W3 anchor = the NetHost **loopback**\n 170|  conn (C2 same-node mock; cross-node real-Iroh is the W5 rung, R2). **Open** at shell-online\n*171|  (`shellhost::bind_shell_by_token`, manifest-enabled only); broker holds the `stream_id` in\n 172|  shell state **online-generation stamped** — *the* crash-proof no-stale-stream leg (R1):\n 173|  a stale stream_id from a prior online-gen is NEVER reused after relink even if\n 174|  `close_shell`'s drop never ran (brain crash mid-close). **Close** at `close_shell`\n## M12-CANDIDATES.md\n 32|\n*33|3. **spt-hosted HARNESS bringup + local user PTY attach (CLI). — CONFIRMED MISSING, TOP PRIORITY.**\n 34|   Investigation 2026-06-14 (file evidence):\n*35|   - Daemon-side PTY spawn EXISTS but shells-only: `crates/spt-daemon/src/brain.rs:spawn_session_pid()`,\n*36|     `crates/spt-daemon/src/shellhost.rs:launch_shell_brokered_in()`; CLI only `spt shell spawn`.\n*37|   - NO user CLI to launch a HARNESS endpoint into a broker PTY (harnesses still\n 38|     harness-hosted: adapter launches binary → binds via `spt api listen`).\n 39|   - Local user terminal attach = DESIGN-ONLY: cross-node remote attach exists\n 40|     (`crates/spt-daemon/src/attach.rs`, M4-D5b), but no local `spt attach <id>`.\n...\n 44|     spawn-session seam for harnesses.\n*45|   - **`spt rc`** — connect to an spt-hosted PTY; works **cross-node** (local attach is the\n 46|     same command, subset). Already M12-slated per operator. Wraps the existing cross-node\n 47|     attach machinery (`crates/spt-daemon/src/attach.rs`) into a user CLI.\n 48|   - spt-claude-code's `cc`/`cc <id>` = thin wrapper over `spt endpoint run` defaulted to\n...\n 50|   **GATING: full-fat M12 is now a PREREQUISITE for spt-claude-code** — operator ruled v1\n*51|   acceptance = legacy parity AND cross-subnet/PTY proof, so local attach + the picker are\n 52|   MANDATORY v1. todlando builds full-fat M12 after this grill, BEFORE perri starts.\n 53|\n 54|4. **Cross-adapter fallback targets `<adapter>:<profile>`.** Node-wide cross-adapter\n...\n 60|- **Psyche cross-machine sync = automatic** (M4-D6c, P2P over Iroh; `crates/spt-daemon/src/sync.rs`,\n*61|  `pump/sync.rs`). tracked/ replicates in-subnet by default; gh-repo `psyche-sync-setup`\n 62|  RETIRED to optional hub-mode. → spt-claude-code `/spt:setup` #8 reduces to a rare opt-in.\n 63|- **`spt endpoint fork <src> <new_id>` exists** (cross-subnet copy, same-node). Forking IS a\n 64|  first-class CLI offering. (`cli.rs:221`)\n## M12-ENDPOINT-RUN-PICKER.md\n 8|> Relationship: `spt endpoint run` = the rich bringup picker. `spt rc` = connect to an\n*9|> spt-hosted PTY (cross-node attach, M12-slated). spt-claude-code's `cc`/`cc <id>` =\n 10|> thin wrapper over `spt endpoint run` defaulted to the `claude-spt` adapter.\n 11|\n 12|## Layer 1 — kind\n...\n 85|- Keybinds: ←→ category · ↑↓ item · enter confirm · esc back · / filter · n new. **Phone\n*86|  caveat:** keybinds risk accidental input on a future smartphone PTY app → design the\n 87|  interaction model so a tap/non-keybind mode can layer on later (don't hard-couple to keys).\n 88|- Type-to-filter for large sets: YES (`/`). Keybind legend pinned at bottom: YES.\n 89|- \"View now\" = read-only attach (watch, no input); attached sessions need an explicit detach\n*90|  keybind. Empty category → \"no endpoints — press n to create\".\n 91|- `cc`/`cc <id>` wrapper: bare `cc` → picker scoped to claude-spt; `cc <id>` → if exists,\n 92|  attach/start directly (skip picker); else create-new with id pre-filled. Non-interactive\n 93|  flags enable `cc-<id>` shortcut scripts.\n## M12-PLAN.md\n 4|> (full picker spec). **M12 is a GATING PREREQUISITE for the downstream `spt-claude-code`\n*5|> project** — operator ruled v1 acceptance = legacy parity AND cross-subnet/PTY proof, so\n*6|> local PTY attach + the bringup picker are mandatory before perri starts. Build full-fat.\n 7|>\n*8|> Value prop framing: `spt rc` (connect to an spt-hosted PTY, cross-node) is a core M12 value.\n 9|>\n 10|> **Traceability:** add a `REQ-*` to `traceable-reqs.toml` for each item BEFORE building it\n 11|> (rule 3); activate stages incrementally as evidence lands, `int` at the final wave (rule 5).\n...\n 14|## What already EXISTS (don't rebuild — verified 2026-06-14)\n*15|- Daemon PTY spawn (`brain.rs:spawn_session_pid`, `shellhost.rs:launch_shell_brokered_in`) —\n 16|  shells-only today.\n 17|- spawn-session seam (`runtime.rs:spawn_session`) — implemented, shell-exercised.\n 18|- Cross-node remote attach (`attach.rs`, M4-D5b) — daemon-internal, no user CLI.\n...\n 22|## Wave 1 — spt-hosted HARNESS bringup + attach (the gating foundation)\n*23|Goal: a user can launch a harness endpoint into a broker PTY and attach a local terminal.\n 24|- **T1.1** `spt endpoint run` non-interactive core: wire the spawn-session seam for HARNESS\n*25|  endpoints (not just shells) → daemon spawns the harness command template into a broker PTY,\n 26|  registers the perch, returns id. Flags: `--adapter <a[:profile]>` `--id <id>` `--create`\n 27|  `--resume <session>` `--attach|--start|--view`. (Foundation for `cc-<id>` shortcuts.)\n*28|- **T1.2** Local user PTY attach CLI: `spt rc <id>` (and the local case) — attach a terminal\n*29|  to a broker-held PTY; works cross-node (reuse `attach.rs`), local is the subset. Read-only\n*30|  `--view`. Clean detach. Mind KNOWN-HAZARDS (PTY ownership, no termination on detach).\n 31|- REQs: REQ-HOST-RUN-*, REQ-RC-* (add to manifest). Stages doc+impl+unit this wave.\n 32|\n 33|## Wave 2 — `spt endpoint run` interactive picker (ratatui)\n...\n 74|  elevation on Win+Linux; whoami alias. Full suite green; `traceable-reqs check` EXIT=0;\n*75|  cross-subnet/PTY proof demonstrated (this is the spt-claude-code v1 acceptance dependency).\n 76|- Then unblock perri to begin `spt-claude-code` (see `../spt-claude-code/SCOPE.md`).\n## M12-W1-PLAN.md\n 3|> JIT build plan (todlando, 2026-06-14). Parent: `M12-PLAN.md` W1. Design-locked with doyle\n*4|> (one-path `rc` invariant + `[session.self]`→broker-PTY mirror approved). This doc is the\n 5|> survivable build spec — coding off this is mechanical.\n 6|\n 7|## Goal (gate condition for W1)\n*8|A user launches a HARNESS endpoint into a broker-held PTY and attaches a local terminal;\n 9|the same `spt rc` path attaches cross-node. Reverses today's harness-hosted-only launch.\n 10|\n 11|## REQs (registered, `traceable-reqs.toml`)\n*12|- **REQ-HOST-RUN-1** — `spt endpoint run` spawns `[session.self]` into a broker PTY for\n*13|  kind=harness (mirrors `shellhost::launch_shell_brokered_in`), registers the perch, returns id.\n*14|- **REQ-RC-1** — `spt rc <id>` attaches a local terminal to a broker-held PTY via the\n*15|  cross-node attach pump; local = degenerate single-node case (ONE path). `--view` read-only,\n 16|  clean detach (no session termination).\n 17|- Activation order (rule 2, memory [[traceable-per-wave-activation]]): each commit sets\n 18|  `required_stages` to ONLY the evidence in that commit, cumulative. doc+impl+unit land this\n...\n 21|## Verified seams (file:line)\n*22|- **Broker spawn**: `spt-daemon/src/brain.rs:380` `spawn_session_pid(SpawnReq) -> (u64, Option<u32>)`.\n 23|  `SpawnReq{program,args,rows,cols,endpoint}` (`msg.rs:124`), reply `Spawned{session_id,pid}`.\n*24|- **Mirror target**: `spt-daemon/src/shellhost.rs:210` `launch_shell_brokered_in(...)` —\n 25|  fills opaque template (`runtime::fill_template`+`tokenize`), `Brain::cold_start` → `spawn_session_pid`,\n 26|  records pid. New harness path mirrors this but: (a) template = `[session.self].command`,\n*27|  (b) endpoint label = the harness endpoint id (NOT shell_session_label), (c) PTY size = real\n 28|  terminal dims (a harness IS a terminal — unlike shells' 24x512 anti-wrap), (d) registration\n 29|  per the harness perch contract (fill from trace), (e) NO link-token (harness binds via `api`).\n*30|- **Attach pump**: `spt-daemon/src/attach.rs` — `serve_attach(brain,stream_id,endpoint,origin_node,net_from_seq)`\n 31|  -> `AttachServeOutcome{Refused,Exited(Option<i32>),Detached}`; `request_attach(brain,conn_id,session_id,from_seq,open_op)->stream_id`;\n 32|  `send_attach_input(brain,stream_id,bytes,wire_op)`. Cross-node rides QUIC (`spt-net/src/net/attach.rs`\n 33|  `AttachRecord::{Request,Input,Output,Exit}`). Local = single-node degenerate case (loopback peer).\n...\n 50|  No new daemon registration code — `spt endpoint run` only needs to (1) spawn `[session.self]`\n*51|  into the broker PTY labeled `<id>`, (2) ensure the harness binds to `<id>` (the env/id question\n*52|  below). Broker-held PTY ⇒ harness survives `rc` detach (hazard satisfied, by construction).\n*53|- **cwd — REQ-HOST-RUN-2 (doyle-tracked, post-W1 / pre-M12-gate).** W1 ships broker-inherited\n 54|  cwd (= daemon cwd) as a bringup-PROOF SHORTCUT only. NOT optional for M12: the consumer (Claude\n 55|  Code) is project-scoped — wrong cwd ⇒ wrong `.claude`/session-history/digest source; `cc <id>`\n 56|  at a project root must land CC in that project. Real cwd DOES need the additive `SpawnReq.cwd`\n*57|  wire change (broker spawns the PTY; cwd must reach `portable-pty` CommandBuilder). N-1-safe,\n 58|  done deliberately. Registered now (stages=[]); must land before the M12 gate.\n*59|- **Follow-up flags (doyle, NOT W1 blockers):** (1) PTY *resize* events must flow through the\n*60|  attach pump for an interactive harness — W1 sets initial real dims only; live resize = later W.\n 61|  (2) Detach keybind (T1.2.5): explicit, cannot collide with harness passthrough input (legacy\n 62|  capsule = ctrl-b prefix); documented. Folded into REQ-RC-1.\n 63|- **RESOLVED — VERDICT (A): argv `{id}` template, NO SpawnReq wire change.** Harness learns its\n...\n 66|  `fill_template` (`runtime.rs:93`) fills `{id}`/`{session_id}`/`{adapter_name}` (`SUBSTITUTION_KEYS`\n*67|  `runtime.rs:36`) before tokenize. Broker spawn `broker.rs:531 dispatch_spawn` →\n*68|  `pty.rs:95 PtySession::spawn_program(program,args,size)` → portable-pty CommandBuilder INHERITS\n*69|  broker env (no env param). `[env]` doc confirms \"spt-hosted inherits from the broker.\" So the\n 70|  daemon fn just substitutes `{id}` + the standard keys; harness self-binds to `<id>` post-spawn.\n 71|  Session id: fresh create = mint one; `--resume <session>` = use that.\n 72|\n...\n 76|   `--resume <session>`, `--attach|--start|--view` (mutually-exclusive terminal action).\n*77|2. New daemon fn (shellhost sibling, e.g. `harnesshost::launch_harness_brokered_in` or extend\n## M12-W1.5-PLAN.md\n*1|# M12 Wave 1.5 — broker loopback-conn + local `spt rc` attach\n 2|\n 3|> JIT plan (todlando, 2026-06-14). doyle-scoped after W1 PASS. Goal: LOCAL `spt rc <id>`\n*4|> attach rides the BYTE-IDENTICAL `serve_attach` pump as cross-node — because self-dial is\n 5|> impossible (iroh: \"Connecting to ourself is not supported\", pinned by\n 6|> `loopback_self_dial_is_refused_local_uses_fallback_transport`). doyle ruling = **B1**: an\n*7|> in-process broker loopback connection, NOT B2 local-direct (which would reintroduce a\n*8|> lesser-tested local branch). Sensitive broker-layer work (KNOWN-HAZARDS 6.7 zone).\n 9|\n 10|## The invariant (doyle, binding)\n*11|ONE `serve_attach` pump. Transport is swappable UNDER the stream. Cross-node = QUIC conn;\n*12|local = broker loopback conn. Both surface as broker net-stream events (`NetStreamData` /\n*13|`net_stream_subscribe` / `net_stream_send`) so `serve_attach` (target) + the `rc::pump`\n*14|(operator) are unchanged. The W1.5 int test MUST assert both transports hit the same pump.\n 15|\n 16|## What already exists (W1)\n*17|- Operator pump: `spt/src/rc.rs` `run_attach` + `pump` (transport-invariant — reads\n 18|  `NetStreamData`, writes via `send_attach_input`). Currently codes the now-REFUSED self-dial\n 19|  (`brain.net_dial(net_status().addr)`) — **this is the one line W1.5 swaps** to a loopback-dial.\n 20|- Target serve: `spt-daemon/src/attach.rs serve_attach`, auto-dispatched at\n...\n 25|## B1 design (the loopback conn)\n*26|A broker-internal connection with NO iroh handshake whose two bidi stream ends are wired\n 27|in-process, so a stream opened by the operator side delivers its bytes as `NetStreamData` to\n 28|a subscriber on the \"remote\" side (which the dispatch loop serves via `serve_attach`), and\n 29|vice-versa. Local `spt rc`: loopback-dial → `conn_id` → `request_attach(conn_id, session_id)`\n*30|→ subscribe → pump — identical to cross-node from `request_attach` up.\n 31|\n 32|### Investigation TODOs (read before coding — NOT yet done)\n*33|1. **`spt-daemon/src/broker.rs`**: the conn table + stream table + how `dispatch_*` routes an\n 34|   inbound stream's first frame to `StreamFamily::Attach`→`serve_attach`. How a QUIC stream's\n 35|   bytes become `NetStreamData` events (the accept/serve plumbing). Find the seam to inject a\n 36|   local pair.\n...\n 41|   stream whose sends loop back to a peer stream id the dispatch loop picks up as inbound.\n*42|   Likely an in-process channel pair inside the broker, surfaced through the SAME\n 43|   `NetStreamData`/`net_stream_send`/`net_stream_subscribe` IPC the QUIC path uses.\n 44|4. **Brain-side**: a `brain.net_dial_loopback(op) -> conn_id` (or reuse `net_dial` with a\n*45|   sentinel local addr the broker recognizes). Decide: new frame KIND vs sentinel addr.\n 46|5. **Origin for the gate**: `serve_attach`'s `origin_node` is the handshake-proven id. A\n 47|   loopback conn has no handshake — use THIS node's own id as origin (access_check is\n 48|   default-open for self; confirm). Don't fabricate a foreign origin.\n...\n 50|## Tasks\n*51|- **T1.5.1** Broker loopback conn: `Conn::Loopback` + in-process stream cross-wiring; inbound\n*52|  loopback streams dispatch to `serve_attach` exactly like QUIC. (broker.rs + nethost.rs)\n 53|- **T1.5.2** `brain.net_dial_loopback` (or sentinel) → `conn_id`; minimal Brain API.\n 54|- **T1.5.3** Swap `rc.rs` conn-acquisition: local `run_attach` loopback-dials instead of\n*55|  `net_dial(own_addr)`. (One seam; pump unchanged.)\n*56|- **T1.5.4** Int test `local_attach_via_loopback_conn_rides_the_same_pump`: spawn a session,\n 57|  loopback-dial, request_attach, drive input, render echo, detach→session outlives. PLUS the\n*58|  **one-pump assertion**: both the cross-node int and this local int exercise the same\n*59|  `serve_attach`+pump path (assert by construction / shared helper, per doyle).\n 60|- **T1.5.5** Activate REQ-RC-1 local int evidence (int already active from W1 cross-node face;\n 61|  add the `[int->REQ-RC-1]` tag on the local test). `traceable-reqs check` EXIT=0.\n 62|\n\n## .config/\n### nextest.toml\n 5|#   Phase A  `-E 'not ( <HEAVY> )'`  — the light pool, full parallelism, fast.\n*6|#   Phase B  `-E '<HEAVY>'`          — the heavy real-broker/PTY/daemon-tree class,\n 7|#                                      run AFTER Phase A so the box is QUIET, and\n 8|#                                      serialized by the test-group below.\n 9|# <HEAVY> is the SAME filter string in both CI phases AND in the test-group override\n...\n 14|[profile.default]\n*15|# A hung test (e.g. a blocking PTY/socket read that never completes) must FAIL FAST,\n 16|# never wedge the CI job up to its wall-clock timeout. Warn once a test exceeds 60s,\n 17|# then SIGKILL + fail it after 4 such periods (4 minutes). No legitimate test in this\n 18|# workspace runs that long; this is purely the no-infinite-wedge backstop.\n...\n 20|\n*21|# Concurrency cap for the heavy real-broker / PTY / daemon-tree integration tests.\n 22|#\n 23|# WHY: nextest fans a phase into ONE parallel pool sized to the runner's core count.\n*24|# The \"heavy\" tests each spin a REAL broker, forkpty a REAL PTY child, and/or spawn a\n 25|# REAL `spt daemon`/brain process tree. Two failure modes were observed on the shared\n 26|# self-hosted runners:\n*27|#   1. heavy-vs-heavy contention: ~16 real brokers launching at once contend for the\n*28|#      scheduler / PTY allocation / socket setup, producing wait-deadline / PTY-spawn\n 29|#      / child-reap TIMING flakes whose victim rotates run to run (spawn_env on Linux\n*30|#      forkpty, g4 on Windows, controller_writer, attach_survives, …).\n 31|#   2. heavy-starved-by-light: even with the heavy class serialized, the ~1200 LIGHT\n 32|#      tests still ran at full parallelism in the SAME pool and saturated the box, so\n 33|#      a heavy test's helper thread lost the CPU race (a_journaled_input_wedge\n...\n 38|# can't bite either, even on the idle box. max-threads = 1 is contention-free and\n*39|# deterministic (a cap > 1 still lets two real brokers race = lucky, not reliable).\n 40|# Group membership == the <HEAVY> phase filter. (Driver-pacing in inject_control_wedge\n 41|# additionally removes that gate's self-inflicted CPU storm — a correctness nicety on\n 42|# top of the structural split.)\n 43|[test-groups]\n*44|heavy-broker-pty = { max-threads = 1 }\n 45|\n*46|# <HEAVY> — the heavy real-broker/PTY/daemon-tree class, by package+binary. Swept by\n*47|# concrete evidence: a binary is HEAVY iff some test in it spawns a real PTY child\n 48|# (a `SpawnReq`/`spawn_program` with a `cat`/`findstr`/`yes`/`sleep`/`ping`/FLOOD\n 49|# program) OR a real `spt daemon`/brain process tree (`supervise_brain`, CARGO_BIN_EXE\n*50|# daemon run). Pure in-process loopback-QUIC wire tests (net_broker with NO PTY child:\n*51|# netbroker, netstream, mesh, sync, replicate, presence, …) are LIGHT and stay in\n*52|# Phase A. All spt-term test binaries forkpty a real PTY child, so the whole package\n 53|# is heavy. Keep this string IDENTICAL to both -E filters in ci.yml.\n 54|[[profile.default.overrides]]\n*55|filter = '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|livehost_bootgate_e2e|n1_pairing|oneliner_e2e|resume_te...\n*56|test-group = 'heavy-broker-pty'\n 57|\n*58|# FLAKE-LEDGER #14 (W-3, todlando 2026-07-06): the rc real-broker UNIT tests inside the\n 59|# `spt` BINARY escape the integration-binary HEAVY filter above — they are `kind(bin)`","truncated":true,"truncatedBy":"bytes","totalLines":633,"totalBytes":59125,"outputLines":522,"outputBytes":51149,"lastLinePartial":false,"firstLineExceedsLimit":false},"linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}},"truncation":{"direction":"head","truncatedBy":"bytes","totalLines":633,"totalBytes":59125,"outputLines":522,"outputBytes":51149,"shownRange":{"start":1,"end":522},"nextOffset":523}}},"isError":false,"timestamp":1783730433322}}
{"type":"message","id":"6367ca10","parentId":"fe624f0e","timestamp":"2026-07-11T00:41:01.850Z","message":{"role":"toolResult","toolCallId":"call_9QVfB71IoVab0jux3OfxCsQ9|fc_08496348794f7e09016a519100682481989766a299ea67f848","toolName":"grep","content":[{"type":"text","text":"Grep timed out after 30s; narrow paths or pattern, or scope with `glob` first"}],"details":{},"isError":true,"timestamp":1783730461849}}
{"type":"message","id":"baadce96","parentId":"6367ca10","timestamp":"2026-07-11T00:41:10.701Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning internal Git object reading**\n\n<!-- -->"},{"type":"toolCall","id":"call_zcTgFpWmWczrlDMTm3Wu87r3|fc_08496348794f7e09016a51912745588198bbbb31a3d7dca3e4","name":"grep","arguments":{"i":"Find blocking operations","pattern":"block_on|blocking|recv\\(|recv_timeout|send\\(|send_timeout|write_all|flush\\(|lock\\(|join\\(|sleep\\(|timeout\\(","path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/pump; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/inject.rs; spt-core/crates/spt/src/rc.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_yqLNEtu4VCgEt70X9Up1WVh9|fc_08496348794f7e09016a519127456c819883500129c136223b","name":"grep","arguments":{"i":"Trace swap resume paths","pattern":"swap|resume|reattach|reconnect|subscribe|handoff|drain|settle|echo","path":"spt-core/crates/spt-daemon/src; spt-core/crates/spt/src; spt-core/crates/spt-daemon/tests","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_DM7wk4Yqwg4XA5OXkANt5Eji|fc_08496348794f7e09016a519127457481988220f8826774aa18","name":"read","arguments":{"i":"Read repository remote","path":"spt-core/.git/config","selector":"raw"}},{"type":"toolCall","id":"call_aPfBbvI1wID06FFfHSlzd6up|fc_08496348794f7e09016a51912745788198b5b1b3c15e26d42c","name":"read","arguments":{"i":"Read release changelog","path":"spt-core/CHANGELOG.md","selector":"1-92"}},{"type":"toolCall","id":"call_roHYrpQYhqYugAdtr8fWdHxH|fc_08496348794f7e09016a519127457c81988b30e9d2c3b0cb7f","name":"read","arguments":{"i":"Read tag hashes","path":"spt-core/.git/packed-refs","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":16471,"output":415,"cacheRead":16896,"cacheWrite":0,"totalTokens":33782,"reasoningTokens":71,"cost":{"input":0.08235500000000001,"output":0.012450000000000001,"cacheRead":0.008447999999999999,"cacheWrite":0,"total":0.10325300000000001}},"stopReason":"toolUse","timestamp":1783730461901,"responseId":"resp_08496348794f7e09016a51911ea10481988ab79182b451efde","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51911f8fa48198a9fda47db75d3f82","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZEnX7o0mpYi8L5-fRe6frQ-VcPBNETzrUZYv8wJ-GYjCEQMxmEoPOivKR79N-rszfC16RjNwRbgYeIg1BBJ9mNgCjYT5DlspeurQEHDEc-R8PP_tJ0KBN9JunFLJmV5B-d6ekAEykGn8PqKPuQtCgUe8cl4_yUWKa90WKXc52o3ZnfqAEcVl7sZIPhC51kXVUHqPLZCrMnagsrlstvHtQ9DTwmJyjZZMMO036GZTwPuWnGEG0w6rgz01k_jQ0LHEDVILxozxK9TjiBJOUvjLDz3RIhNWArpPCXZjf28z_PnIi4Q0YMiMXluAp8uOoPIG_Oxfs-usyDP9Bg9wAFupIsSZisPDI8doALj9axDCOd3SW7QnBY9irdsgTcWTx4DWefnoeRTFShGwIKJyE_g5YXsOhsJGfpD2oVwXW6SjgTy3zlYMNnNl9BFRn3cvs1ne05ezFHM_RbyS_KLRnW5AvCf4q1ljL7KoSlZs5ZRLNAdqUYN34pP3TX-pMv8EiWNYJKzpywRothvAiWpo1xMJW_PayWYW-OCFfmKqceVXQANy1mEHLHZJChiOKG0GxC4vOLmJcwQ-cMWcBRtlsXGSEw87gXrru2giGOXvbM59ozHoyj-E2NOjnZRpspOI1liiNZl1fBm2Nj6mLP0y4ktGlULinnV3fHIITB6Lb2HOOFc-wY2Q4-JyODdqHcC9cw4_lSLrYttjF9FoPVcIspe4__Ih28zLodVhE1bSCXz-VHkVWF2ZxnsWrIZe9hfWTCvmGl9lWrgB4jIlk5wlizs9paXiu4LUwWNEwTHT1f1WHkAli8K2zSXkZm40SUYgW0d-ME2B1fATxMWCGB9bx3vUbyV7Z9iXTUqgDa0ZkjjETHPnqOtCyyMuQIVQJ22afeAb2sDEhV1-Rb5LsBbYxPNYsKr9l-zBVp52FNhMC7tNzdjPEUcbClAndJRayKUWt0osV5K1xWlex6J4csZeRIBX8xDn7GGRk6lSL9NYCl6PajSLXD44Fjx6J7jL78dHjcXcBN5EWXAIDS76TDgb2vPl0mLr9IaEJF9IRtaPZAakDnByQZBT0McZneKNfH_R3g5MnGx-mBd1kr1BR6RLpGRMbYWJVOyTXl6E1QIuyXFNewNgzd-Jeszqw4eT3dRPoIlpmPU5IC9Gy37qbIyLTyr4i0GoNzkhiueT2IehKqGV5nu5MHpwAYL_DlbfxOekqmGx8XjjmTABokv6rMvGHKXEhZi-VAzcO1TyFABoZ0UeVzmNasEE0XoddoZOB4Yt6nqUhLDuwkuJAa1NcfaQTYbxbYt82TwSrQL3-VcQ0dIMHfqC0w=","summary":[{"type":"summary_text","text":"**Planning internal Git object reading**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a51912745588198bbbb31a3d7dca3e4","type":"function_call","status":"completed","arguments":"{\"i\":\"Find blocking operations\",\"pattern\":\"block_on|blocking|recv\\\\(|recv_timeout|send\\\\(|send_timeout|write_all|flush\\\\(|lock\\\\(|join\\\\(|sleep\\\\(|timeout\\\\(\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/pump; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/inject.rs; spt-core/crates/spt/src/rc.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_zcTgFpWmWczrlDMTm3Wu87r3","name":"grep"},{"id":"fc_08496348794f7e09016a519127456c819883500129c136223b","type":"function_call","status":"completed","arguments":"{\"i\":\"Trace swap resume paths\",\"pattern\":\"swap|resume|reattach|reconnect|subscribe|handoff|drain|settle|echo\",\"path\":\"spt-core/crates/spt-daemon/src; spt-core/crates/spt/src; spt-core/crates/spt-daemon/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_yqLNEtu4VCgEt70X9Up1WVh9","name":"grep"},{"id":"fc_08496348794f7e09016a519127457481988220f8826774aa18","type":"function_call","status":"completed","arguments":"{\"i\":\"Read repository remote\",\"path\":\"spt-core/.git/config\",\"selector\":\"raw\"}","call_id":"call_DM7wk4Yqwg4XA5OXkANt5Eji","name":"read"},{"id":"fc_08496348794f7e09016a51912745788198b5b1b3c15e26d42c","type":"function_call","status":"completed","arguments":"{\"i\":\"Read release changelog\",\"path\":\"spt-core/CHANGELOG.md\",\"selector\":\"1-92\"}","call_id":"call_aPfBbvI1wID06FFfHSlzd6up","name":"read"},{"id":"fc_08496348794f7e09016a519127457c81988b30e9d2c3b0cb7f","type":"function_call","status":"completed","arguments":"{\"i\":\"Read tag hashes\",\"path\":\"spt-core/.git/packed-refs\",\"selector\":\"raw\"}","call_id":"call_roHYrpQYhqYugAdtr8fWdHxH","name":"read"}]},"duration":8799.142299999949,"ttft":1025.7889000000432,"contextSnapshot":{"promptTokens":33367,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_zcTgFpWmWczrlDMTm3Wu87r3|fc_08496348794f7e09016a51912745588198bbbb31a3d7dca3e4","toolName":"grep","startedAt":"2026-07-11T00:41:10.702Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/pump; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/inject.rs; spt-core/crates/spt/src/rc.rs"},"intent":"Find blocking operations"},"id":"129e70d3","parentId":"baadce96","timestamp":"2026-07-11T00:41:10.702Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yqLNEtu4VCgEt70X9Up1WVh9|fc_08496348794f7e09016a519127456c819883500129c136223b","toolName":"grep","startedAt":"2026-07-11T00:41:10.702Z","args":{"path":"spt-core/crates/spt-daemon/src; spt-core/crates/spt/src; spt-core/crates/spt-daemon/tests"},"intent":"Trace swap resume paths"},"id":"049ce692","parentId":"129e70d3","timestamp":"2026-07-11T00:41:10.702Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_DM7wk4Yqwg4XA5OXkANt5Eji|fc_08496348794f7e09016a519127457481988220f8826774aa18","toolName":"read","startedAt":"2026-07-11T00:41:10.702Z","args":{"path":"spt-core/.git/config"},"intent":"Read repository remote"},"id":"c22517c8","parentId":"049ce692","timestamp":"2026-07-11T00:41:10.702Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_aPfBbvI1wID06FFfHSlzd6up|fc_08496348794f7e09016a51912745788198b5b1b3c15e26d42c","toolName":"read","startedAt":"2026-07-11T00:41:10.702Z","args":{"path":"spt-core/CHANGELOG.md"},"intent":"Read release changelog"},"id":"7a97a28c","parentId":"c22517c8","timestamp":"2026-07-11T00:41:10.702Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_roHYrpQYhqYugAdtr8fWdHxH|fc_08496348794f7e09016a519127457c81988b30e9d2c3b0cb7f","toolName":"read","startedAt":"2026-07-11T00:41:10.702Z","args":{"path":"spt-core/.git/packed-refs"},"intent":"Read tag hashes"},"id":"c4ecfc03","parentId":"7a97a28c","timestamp":"2026-07-11T00:41:10.702Z"}
{"type":"message","id":"b2b16c3e","parentId":"c4ecfc03","timestamp":"2026-07-11T00:41:10.709Z","message":{"role":"toolResult","toolCallId":"call_DM7wk4Yqwg4XA5OXkANt5Eji|fc_08496348794f7e09016a519127457481988220f8826774aa18","toolName":"read","content":[{"type":"text","text":"[core]\n\trepositoryformatversion = 0\n\tfilemode = false\n\tbare = false\n\tlogallrefupdates = true\n\tignorecase = true\n[remote \"origin\"]\n\turl = https://github.com/SaberMage/spt-core.git\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"dev-freeform\"]\n\tremote = origin\n\tmerge = refs/heads/dev-freeform\n[lfs]\n\trepositoryformatversion = 0\n[branch \"simple-fixes\"]\n\tremote = origin\n\tmerge = refs/heads/simple-fixes\n[branch \"m10-digest-extractor\"]\n\tremote = origin\n\tmerge = refs/heads/m10-digest-extractor\n[branch \"docs/msg-envelope-adr-0020\"]\n\tremote = origin\n\tmerge = refs/heads/docs/msg-envelope-adr-0020\n[branch \"doyle-work\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"fix/digest-proof-session-id-keys\"]\n\tremote = origin\n\tmerge = refs/heads/fix/digest-proof-session-id-keys\n[branch \"m11-shell-substrate\"]\n\tremote = origin\n\tmerge = refs/heads/m11-shell-substrate\n[branch \"v0.8.1-livehost-bootrace\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.1-livehost-bootrace\n[branch \"v0.8.2-templating-residency\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.2-templating-residency\n[branch \"v0.8.3-broker-quic-deadline\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.3-broker-quic-deadline\n[branch \"v0.9.0-harness-resolution\"]\n\tremote = origin\n\tmerge = refs/heads/v0.9.0-harness-resolution\n[branch \"fix/win-program-resolve\"]\n\tremote = origin\n\tmerge = refs/heads/fix/win-program-resolve\n[branch \"v0.9.1-picker-status\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.9.1-resolve-dotstem\"]\n\tremote = origin\n\tmerge = refs/heads/v0.9.1-resolve-dotstem\n[branch \"v0.10.0-picker-status\"]\n\tremote = origin\n\tmerge = refs/heads/v0.10.0-picker-status\n[branch \"v0.11.0-findings\"]\n\tremote = origin\n\tmerge = refs/heads/v0.11.0-findings\n[branch \"v0.12.0-lifecycle\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.12.1-lifecycle\"]\n\tremote = origin\n\tmerge = refs/heads/v0.12.1-lifecycle\n[branch \"v0.13.0-delivery-control\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.0-delivery-control\n[branch \"v0.13.0-p1b-ack-deadlock\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.0-p1b-ack-deadlock\n[branch \"wedge-trace-instrumentation\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-instrumentation\n[branch \"wedge-trace-v2\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v2\n[branch \"wedge-trace-v3\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v3\n[branch \"wedge-trace-v4\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v4\n[branch \"pump-carrier-fix\"]\n\tremote = origin\n\tmerge = refs/heads/pump-carrier-fix\n[branch \"viewer-drain-decouple-b4\"]\n\tremote = origin\n\tmerge = refs/heads/viewer-drain-decouple-b4\n[branch \"feat/translate-proof\"]\n\tremote = origin\n\tmerge = refs/heads/feat/translate-proof\n[branch \"v0.13.2-adapter-packaging\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.2-adapter-packaging\n[branch \"design/endpoint-creation-flow\"]\n\tremote = origin\n\tmerge = refs/heads/design/endpoint-creation-flow\n[branch \"fix/f019-translation-install-dir-resolve\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.15.0-messaging\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.17.0-robust-join\"]\n\tremote = origin\n\tmerge = refs/heads/v0.17.0-robust-join\n[branch \"update-fetch-ux\"]\n\tremote = origin\n\tmerge = refs/heads/update-fetch-ux\n[branch \"field-bug-milestone\"]\n\tremote = origin\n\tmerge = refs/heads/field-bug-milestone\n[branch \"counter39-field-bugs\"]\n\tremote = origin\n\tmerge = refs/heads/counter39-field-bugs\n[branch \"endpoint-list-redesign\"]\n\tremote = origin\n\tmerge = refs/heads/endpoint-list-redesign\n[branch \"boundary-rotation-credential\"]\n\tremote = origin\n\tmerge = refs/heads/boundary-rotation-credential\n[branch \"f023-wan-idle-delivery\"]\n\tremote = origin\n\tmerge = refs/heads/f023-wan-idle-delivery\n[branch \"f025-listredesign\"]\n\tremote = origin\n\tmerge = refs/heads/f025-listredesign\n[branch \"f026-pickertruth\"]\n\tremote = origin\n\tmerge = refs/heads/f026-pickertruth\n[branch \"f030-psyche-ephemeral\"]\n\tremote = origin\n\tmerge = refs/heads/f030-psyche-ephemeral\n[branch \"release/v0.25.0\"]\n\tremote = origin\n\tmerge = refs/heads/release/v0.25.0\n[branch \"remote-truth\"]\n\tremote = origin\n\tmerge = refs/heads/remote-truth\n[branch \"worker-truth\"]\n\tremote = origin\n\tmerge = refs/heads/worker-truth\n[branch \"lifecycle-truth\"]\n\tremote = origin\n\tmerge = refs/heads/lifecycle-truth\n[branch \"fix/inject-settle-rearm\"]\n\tremote = origin\n\tmerge = refs/heads/fix/inject-settle-rearm\n[branch \"release/0.29.1\"]\n\tremote = origin\n\tmerge = refs/heads/release/0.29.1\n[branch \"triage/musl-tier\"]\n\tremote = origin\n\tmerge = refs/heads/triage/musl-tier\n[branch \"feat/musl-w1-platform-registry\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w1-platform-registry\n[branch \"musl-w1\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w1-platform-registry\n[branch \"musl-w2\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w2-register-musl\n[branch \"feat/musl-w3-release-artifact\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w3-release-artifact\n[branch \"rca/pump-w1-fastfail\"]\n\tremote = origin\n\tmerge = refs/heads/rca/pump-w1-fastfail\n[branch \"pump-w2-design\"]\n\tremote = origin\n\tmerge = refs/heads/pump-w2-design\n[branch \"docs/reorg-plans-process\"]\n\tremote = origin\n\tmerge = refs/heads/docs/reorg-plans-process\n[branch \"spec/brain-handoff-pilot\"]\n\tremote = origin\n\tmerge = refs/heads/spec/brain-handoff-pilot\n[branch \"f-035-window-drain-honor\"]\n\tremote = origin\n\tmerge = refs/heads/f-035-window-drain-honor\n[branch \"update-wedge-drain-drive\"]\n\tremote = origin\n\tmerge = refs/heads/update-wedge-drain-drive\n[branch \"uwedge2-resume-viewer\"]\n\tremote = origin\n\tmerge = refs/heads/uwedge2-resume-viewer\n[branch \"release/v0.30.4\"]\n\tremote = origin\n\tmerge = refs/heads/release/v0.30.4\n[branch \"fix/update-wedge-bounded-conn-write\"]\n\tremote = origin\n\tmerge = refs/heads/fix/update-wedge-bounded-conn-write\n[branch \"release-merge/uwedge-r4\"]\n\tremote = origin\n\tmerge = refs/heads/release-merge/uwedge-r4\n[branch \"test/uwedge-r4-stale-guard-swap\"]\n\tremote = origin\n\tmerge = refs/heads/test/uwedge-r4-stale-guard-swap\n[branch \"triage/msg-identity-milestone\"]\n\tremote = origin\n\tmerge = refs/heads/triage/msg-identity-milestone\n[branch \"triage/update-wedge-rca-trace\"]\n\tremote = origin\n\tmerge = refs/heads/triage/update-wedge-rca-trace\n[branch \"build/msg-identity-w1-f036\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w1-f036\n[branch \"build/msg-identity-w2-f034\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w2-f034\n[branch \"build/msg-identity-w3-f032\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w3-f032\n[branch \"build/msg-identity-w4-delivery\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w4-delivery\n"}],"details":{"displayContent":{"text":"[core]\n\trepositoryformatversion = 0\n\tfilemode = false\n\tbare = false\n\tlogallrefupdates = true\n\tignorecase = true\n[remote \"origin\"]\n\turl = https://github.com/SaberMage/spt-core.git\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"dev-freeform\"]\n\tremote = origin\n\tmerge = refs/heads/dev-freeform\n[lfs]\n\trepositoryformatversion = 0\n[branch \"simple-fixes\"]\n\tremote = origin\n\tmerge = refs/heads/simple-fixes\n[branch \"m10-digest-extractor\"]\n\tremote = origin\n\tmerge = refs/heads/m10-digest-extractor\n[branch \"docs/msg-envelope-adr-0020\"]\n\tremote = origin\n\tmerge = refs/heads/docs/msg-envelope-adr-0020\n[branch \"doyle-work\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"fix/digest-proof-session-id-keys\"]\n\tremote = origin\n\tmerge = refs/heads/fix/digest-proof-session-id-keys\n[branch \"m11-shell-substrate\"]\n\tremote = origin\n\tmerge = refs/heads/m11-shell-substrate\n[branch \"v0.8.1-livehost-bootrace\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.1-livehost-bootrace\n[branch \"v0.8.2-templating-residency\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.2-templating-residency\n[branch \"v0.8.3-broker-quic-deadline\"]\n\tremote = origin\n\tmerge = refs/heads/v0.8.3-broker-quic-deadline\n[branch \"v0.9.0-harness-resolution\"]\n\tremote = origin\n\tmerge = refs/heads/v0.9.0-harness-resolution\n[branch \"fix/win-program-resolve\"]\n\tremote = origin\n\tmerge = refs/heads/fix/win-program-resolve\n[branch \"v0.9.1-picker-status\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.9.1-resolve-dotstem\"]\n\tremote = origin\n\tmerge = refs/heads/v0.9.1-resolve-dotstem\n[branch \"v0.10.0-picker-status\"]\n\tremote = origin\n\tmerge = refs/heads/v0.10.0-picker-status\n[branch \"v0.11.0-findings\"]\n\tremote = origin\n\tmerge = refs/heads/v0.11.0-findings\n[branch \"v0.12.0-lifecycle\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.12.1-lifecycle\"]\n\tremote = origin\n\tmerge = refs/heads/v0.12.1-lifecycle\n[branch \"v0.13.0-delivery-control\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.0-delivery-control\n[branch \"v0.13.0-p1b-ack-deadlock\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.0-p1b-ack-deadlock\n[branch \"wedge-trace-instrumentation\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-instrumentation\n[branch \"wedge-trace-v2\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v2\n[branch \"wedge-trace-v3\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v3\n[branch \"wedge-trace-v4\"]\n\tremote = origin\n\tmerge = refs/heads/wedge-trace-v4\n[branch \"pump-carrier-fix\"]\n\tremote = origin\n\tmerge = refs/heads/pump-carrier-fix\n[branch \"viewer-drain-decouple-b4\"]\n\tremote = origin\n\tmerge = refs/heads/viewer-drain-decouple-b4\n[branch \"feat/translate-proof\"]\n\tremote = origin\n\tmerge = refs/heads/feat/translate-proof\n[branch \"v0.13.2-adapter-packaging\"]\n\tremote = origin\n\tmerge = refs/heads/v0.13.2-adapter-packaging\n[branch \"design/endpoint-creation-flow\"]\n\tremote = origin\n\tmerge = refs/heads/design/endpoint-creation-flow\n[branch \"fix/f019-translation-install-dir-resolve\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.15.0-messaging\"]\n\tremote = origin\n\tmerge = refs/heads/main\n[branch \"v0.17.0-robust-join\"]\n\tremote = origin\n\tmerge = refs/heads/v0.17.0-robust-join\n[branch \"update-fetch-ux\"]\n\tremote = origin\n\tmerge = refs/heads/update-fetch-ux\n[branch \"field-bug-milestone\"]\n\tremote = origin\n\tmerge = refs/heads/field-bug-milestone\n[branch \"counter39-field-bugs\"]\n\tremote = origin\n\tmerge = refs/heads/counter39-field-bugs\n[branch \"endpoint-list-redesign\"]\n\tremote = origin\n\tmerge = refs/heads/endpoint-list-redesign\n[branch \"boundary-rotation-credential\"]\n\tremote = origin\n\tmerge = refs/heads/boundary-rotation-credential\n[branch \"f023-wan-idle-delivery\"]\n\tremote = origin\n\tmerge = refs/heads/f023-wan-idle-delivery\n[branch \"f025-listredesign\"]\n\tremote = origin\n\tmerge = refs/heads/f025-listredesign\n[branch \"f026-pickertruth\"]\n\tremote = origin\n\tmerge = refs/heads/f026-pickertruth\n[branch \"f030-psyche-ephemeral\"]\n\tremote = origin\n\tmerge = refs/heads/f030-psyche-ephemeral\n[branch \"release/v0.25.0\"]\n\tremote = origin\n\tmerge = refs/heads/release/v0.25.0\n[branch \"remote-truth\"]\n\tremote = origin\n\tmerge = refs/heads/remote-truth\n[branch \"worker-truth\"]\n\tremote = origin\n\tmerge = refs/heads/worker-truth\n[branch \"lifecycle-truth\"]\n\tremote = origin\n\tmerge = refs/heads/lifecycle-truth\n[branch \"fix/inject-settle-rearm\"]\n\tremote = origin\n\tmerge = refs/heads/fix/inject-settle-rearm\n[branch \"release/0.29.1\"]\n\tremote = origin\n\tmerge = refs/heads/release/0.29.1\n[branch \"triage/musl-tier\"]\n\tremote = origin\n\tmerge = refs/heads/triage/musl-tier\n[branch \"feat/musl-w1-platform-registry\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w1-platform-registry\n[branch \"musl-w1\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w1-platform-registry\n[branch \"musl-w2\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w2-register-musl\n[branch \"feat/musl-w3-release-artifact\"]\n\tremote = origin\n\tmerge = refs/heads/feat/musl-w3-release-artifact\n[branch \"rca/pump-w1-fastfail\"]\n\tremote = origin\n\tmerge = refs/heads/rca/pump-w1-fastfail\n[branch \"pump-w2-design\"]\n\tremote = origin\n\tmerge = refs/heads/pump-w2-design\n[branch \"docs/reorg-plans-process\"]\n\tremote = origin\n\tmerge = refs/heads/docs/reorg-plans-process\n[branch \"spec/brain-handoff-pilot\"]\n\tremote = origin\n\tmerge = refs/heads/spec/brain-handoff-pilot\n[branch \"f-035-window-drain-honor\"]\n\tremote = origin\n\tmerge = refs/heads/f-035-window-drain-honor\n[branch \"update-wedge-drain-drive\"]\n\tremote = origin\n\tmerge = refs/heads/update-wedge-drain-drive\n[branch \"uwedge2-resume-viewer\"]\n\tremote = origin\n\tmerge = refs/heads/uwedge2-resume-viewer\n[branch \"release/v0.30.4\"]\n\tremote = origin\n\tmerge = refs/heads/release/v0.30.4\n[branch \"fix/update-wedge-bounded-conn-write\"]\n\tremote = origin\n\tmerge = refs/heads/fix/update-wedge-bounded-conn-write\n[branch \"release-merge/uwedge-r4\"]\n\tremote = origin\n\tmerge = refs/heads/release-merge/uwedge-r4\n[branch \"test/uwedge-r4-stale-guard-swap\"]\n\tremote = origin\n\tmerge = refs/heads/test/uwedge-r4-stale-guard-swap\n[branch \"triage/msg-identity-milestone\"]\n\tremote = origin\n\tmerge = refs/heads/triage/msg-identity-milestone\n[branch \"triage/update-wedge-rca-trace\"]\n\tremote = origin\n\tmerge = refs/heads/triage/update-wedge-rca-trace\n[branch \"build/msg-identity-w1-f036\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w1-f036\n[branch \"build/msg-identity-w2-f034\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w2-f034\n[branch \"build/msg-identity-w3-f032\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w3-f032\n[branch \"build/msg-identity-w4-delivery\"]\n\tremote = origin\n\tmerge = refs/heads/build/msg-identity-w4-delivery\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\config"}}},"isError":false,"timestamp":1783730470709}}
{"type":"message","id":"cbbbcd76","parentId":"b2b16c3e","timestamp":"2026-07-11T00:41:10.736Z","message":{"role":"toolResult","toolCallId":"call_roHYrpQYhqYugAdtr8fWdHxH|fc_08496348794f7e09016a519127457c81988b30e9d2c3b0cb7f","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":1783730470736}}
{"type":"message","id":"da67b040","parentId":"cbbbcd76","timestamp":"2026-07-11T00:41:10.737Z","message":{"role":"toolResult","toolCallId":"call_aPfBbvI1wID06FFfHSlzd6up|fc_08496348794f7e09016a51912745788198b5b1b3c15e26d42c","toolName":"read","content":[{"type":"text","text":"# Changelog\n\nAll notable **user-facing** changes to `spt` — what a person running the CLI\nnotices or does differently. The `## [<version>]` section of each release\nbecomes that release's GitHub Release notes verbatim (see\n`docs/RELEASE-RUNBOOK.md`). This project follows\n[Keep a Changelog](https://keepachangelog.com) and semantic versioning\n(pre-1.0: breaking changes bump the minor).\n\n## [Unreleased]\n\n## [0.30.6] - 2026-07-10\n\nA reliability patch closing the last update-wedge failure mode: a stuck session\nconsumer can no longer freeze the daemon under load.\n\n> **Applying this update needs a daemon restart.** Unlike the recent seamless\n> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n> --apply` lands it via a full daemon restart rather than a live swap — your\n> sessions restart as the daemon comes back. This is a one-time cost to install\n> the fix.\n\n### Fixed\n\n- Under load, if a session's controller consumer stopped reading (for example a\n  wedged `spt rc --take` that stalled without disconnecting), the daemon could\n  block indefinitely trying to write to it — freezing live terminal sessions with\n  no recovery short of restarting the box. Every write the daemon makes to a\n  session connection is now time-bounded and cancelable: a stuck consumer is cut\n  loose instead of taking the whole daemon down, so your other sessions keep\n  streaming and a fresh `spt rc` resumes cleanly.\n\n## [0.30.5] - 2026-07-09\n\nA reliability patch completing the in-place-update fix: applying an update no longer\nfreezes your live terminal sessions.\n\n### Fixed\n\n- Applying an update in place with `spt update fetch --apply` could still freeze all of\n  your live terminal sessions a few seconds after the swap — they stopped streaming and\n  `spt rc` could no longer attach, so the box had to be restarted to recover. (The\n  previous release narrowed this but did not fully close it.) Resuming across the update\n  no longer floods the daemon's internal channel, so your open sessions keep streaming and\n  `spt rc` keeps working straight through the swap — no freeze, no restart. (Because the\n  fix lives in the incoming version, updating *to* this release is what makes your next\n  in-place update seamless.)\n\n## [0.30.4] - 2026-07-09\n\nA follow-up reliability patch for in-place updates: resumed sessions are no longer\nseized during the update handoff.\n\n### Fixed\n\n- Applying an update in place with `spt update fetch --apply` could freeze or seize\n  control of your live terminal sessions as the daemon restarted — a resumed session\n  could be taken over so that your open session and `spt rc` stopped responding across\n  the swap. Resumed sessions now re-attach as observers instead of taking over the\n  session's controller, so your open sessions and `spt rc` keep working across the\n  brain swap. (Follow-up to the previous release's seamless-swap fix.)\n\n## [0.30.3] - 2026-07-09\n\nA reliability patch for in-place updates, plus two message- and endpoint-delivery\ncorrectness fixes.\n\n### Fixed\n\n- Applying an update in place with `spt update fetch --apply` could freeze every live\n  terminal session for about 30 seconds and then roll the update back instead of\n  completing the swap. The incoming version now drives the running daemon to clear a\n  hard-stopped prior session during the handoff, so the update completes seamlessly with\n  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\n  incoming version's logic, updating *to* this release is what makes your next in-place\n  update seamless.)\n\n- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\n  \"never wakes an idle target\") could still be delivered to a target the instant it went\n  idle. Active-only messages now stay held for the target's active window as documented\n  and no longer leak in on the active-to-idle edge.\n\n- `spt endpoint list --json` could report a locally-hosted endpoint that was actually\n  live as suspended or offline, even while the human-readable listing correctly showed it\n  online — which could cause an adapter to needlessly suspend itself. The JSON listing now\n  reflects the same local liveness truth the interactive view uses. Re-binding an endpoint\n  also no longer discards its pending wake/rest state.\n\n## [0.30.2] - 2026-07-09\n\nA follow-on reliability patch for subnets with several peers.\n\n### Fixed\n\n- On a subnet with several peers, one unreachable or slow-to-connect peer could\n…\n  0.30.1 fix, which stopped a single silent peer from stalling the whole round.)\n\n…\nFirst public release of `spt`.\n\n[Showing lines 1-95 of 900. Use :96 to continue]"}],"details":{"truncation":{"content":"# Changelog\r\n\r\nAll notable **user-facing** changes to `spt` — what a person running the CLI\r\nnotices or does differently. The `## [<version>]` section of each release\r\nbecomes that release's GitHub Release notes verbatim (see\r\n`docs/RELEASE-RUNBOOK.md`). This project follows\r\n[Keep a Changelog](https://keepachangelog.com) and semantic versioning\r\n(pre-1.0: breaking changes bump the minor).\r\n\r\n## [Unreleased]\r\n\r\n## [0.30.6] - 2026-07-10\r\n\r\nA reliability patch closing the last update-wedge failure mode: a stuck session\r\nconsumer can no longer freeze the daemon under load.\r\n\r\n> **Applying this update needs a daemon restart.** Unlike the recent seamless\r\n> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\r\n> --apply` lands it via a full daemon restart rather than a live swap — your\r\n> sessions restart as the daemon comes back. This is a one-time cost to install\r\n> the fix.\r\n\r\n### Fixed\r\n\r\n- Under load, if a session's controller consumer stopped reading (for example a\r\n  wedged `spt rc --take` that stalled without disconnecting), the daemon could\r\n  block indefinitely trying to write to it — freezing live terminal sessions with\r\n  no recovery short of restarting the box. Every write the daemon makes to a\r\n  session connection is now time-bounded and cancelable: a stuck consumer is cut\r\n  loose instead of taking the whole daemon down, so your other sessions keep\r\n  streaming and a fresh `spt rc` resumes cleanly.\r\n\r\n## [0.30.5] - 2026-07-09\r\n\r\nA reliability patch completing the in-place-update fix: applying an update no longer\r\nfreezes your live terminal sessions.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could still freeze all of\r\n  your live terminal sessions a few seconds after the swap — they stopped streaming and\r\n  `spt rc` could no longer attach, so the box had to be restarted to recover. (The\r\n  previous release narrowed this but did not fully close it.) Resuming across the update\r\n  no longer floods the daemon's internal channel, so your open sessions keep streaming and\r\n  `spt rc` keeps working straight through the swap — no freeze, no restart. (Because the\r\n  fix lives in the incoming version, updating *to* this release is what makes your next\r\n  in-place update seamless.)\r\n\r\n## [0.30.4] - 2026-07-09\r\n\r\nA follow-up reliability patch for in-place updates: resumed sessions are no longer\r\nseized during the update handoff.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could freeze or seize\r\n  control of your live terminal sessions as the daemon restarted — a resumed session\r\n  could be taken over so that your open session and `spt rc` stopped responding across\r\n  the swap. Resumed sessions now re-attach as observers instead of taking over the\r\n  session's controller, so your open sessions and `spt rc` keep working across the\r\n  brain swap. (Follow-up to the previous release's seamless-swap fix.)\r\n\r\n## [0.30.3] - 2026-07-09\r\n\r\nA reliability patch for in-place updates, plus two message- and endpoint-delivery\r\ncorrectness fixes.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could freeze every live\r\n  terminal session for about 30 seconds and then roll the update back instead of\r\n  completing the swap. The incoming version now drives the running daemon to clear a\r\n  hard-stopped prior session during the handoff, so the update completes seamlessly with\r\n  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\r\n  incoming version's logic, updating *to* this release is what makes your next in-place\r\n  update seamless.)\r\n\r\n- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\r\n  \"never wakes an idle target\") could still be delivered to a target the instant it went\r\n  idle. Active-only messages now stay held for the target's active window as documented\r\n  and no longer leak in on the active-to-idle edge.\r\n\r\n- `spt endpoint list --json` could report a locally-hosted endpoint that was actually\r\n  live as suspended or offline, even while the human-readable listing correctly showed it\r\n  online — which could cause an adapter to needlessly suspend itself. The JSON listing now\r\n  reflects the same local liveness truth the interactive view uses. Re-binding an endpoint\r\n  also no longer discards its pending wake/rest state.\r\n\r\n## [0.30.2] - 2026-07-09\r\n\r\nA follow-on reliability patch for subnets with several peers.\r\n\r\n### Fixed\r\n\r\n- On a subnet with several peers, one unreachable or slow-to-connect peer could\r","truncated":true,"truncatedBy":"lines","totalLines":900,"totalBytes":4576,"outputLines":95,"outputBytes":4576,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"# Changelog\r\n\r\nAll notable **user-facing** changes to `spt` — what a person running the CLI\r\nnotices or does differently. The `## [<version>]` section of each release\r\nbecomes that release's GitHub Release notes verbatim (see\r\n`docs/RELEASE-RUNBOOK.md`). This project follows\r\n[Keep a Changelog](https://keepachangelog.com) and semantic versioning\r\n(pre-1.0: breaking changes bump the minor).\r\n\r\n## [Unreleased]\r\n\r\n## [0.30.6] - 2026-07-10\r\n\r\nA reliability patch closing the last update-wedge failure mode: a stuck session\r\nconsumer can no longer freeze the daemon under load.\r\n\r\n> **Applying this update needs a daemon restart.** Unlike the recent seamless\r\n> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\r\n> --apply` lands it via a full daemon restart rather than a live swap — your\r\n> sessions restart as the daemon comes back. This is a one-time cost to install\r\n> the fix.\r\n\r\n### Fixed\r\n\r\n- Under load, if a session's controller consumer stopped reading (for example a\r\n  wedged `spt rc --take` that stalled without disconnecting), the daemon could\r\n  block indefinitely trying to write to it — freezing live terminal sessions with\r\n  no recovery short of restarting the box. Every write the daemon makes to a\r\n  session connection is now time-bounded and cancelable: a stuck consumer is cut\r\n  loose instead of taking the whole daemon down, so your other sessions keep\r\n  streaming and a fresh `spt rc` resumes cleanly.\r\n\r\n## [0.30.5] - 2026-07-09\r\n\r\nA reliability patch completing the in-place-update fix: applying an update no longer\r\nfreezes your live terminal sessions.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could still freeze all of\r\n  your live terminal sessions a few seconds after the swap — they stopped streaming and\r\n  `spt rc` could no longer attach, so the box had to be restarted to recover. (The\r\n  previous release narrowed this but did not fully close it.) Resuming across the update\r\n  no longer floods the daemon's internal channel, so your open sessions keep streaming and\r\n  `spt rc` keeps working straight through the swap — no freeze, no restart. (Because the\r\n  fix lives in the incoming version, updating *to* this release is what makes your next\r\n  in-place update seamless.)\r\n\r\n## [0.30.4] - 2026-07-09\r\n\r\nA follow-up reliability patch for in-place updates: resumed sessions are no longer\r\nseized during the update handoff.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could freeze or seize\r\n  control of your live terminal sessions as the daemon restarted — a resumed session\r\n  could be taken over so that your open session and `spt rc` stopped responding across\r\n  the swap. Resumed sessions now re-attach as observers instead of taking over the\r\n  session's controller, so your open sessions and `spt rc` keep working across the\r\n  brain swap. (Follow-up to the previous release's seamless-swap fix.)\r\n\r\n## [0.30.3] - 2026-07-09\r\n\r\nA reliability patch for in-place updates, plus two message- and endpoint-delivery\r\ncorrectness fixes.\r\n\r\n### Fixed\r\n\r\n- Applying an update in place with `spt update fetch --apply` could freeze every live\r\n  terminal session for about 30 seconds and then roll the update back instead of\r\n  completing the swap. The incoming version now drives the running daemon to clear a\r\n  hard-stopped prior session during the handoff, so the update completes seamlessly with\r\n  your sessions staying up — no freeze, no rollback. (Because the fix lives in the\r\n  incoming version's logic, updating *to* this release is what makes your next in-place\r\n  update seamless.)\r\n\r\n- A message sent active-only (`spt send --active-only` / `--deferred`, documented as\r\n  \"never wakes an idle target\") could still be delivered to a target the instant it went\r\n  idle. Active-only messages now stay held for the target's active window as documented\r\n  and no longer leak in on the active-to-idle edge.\r\n\r\n- `spt endpoint list --json` could report a locally-hosted endpoint that was actually\r\n  live as suspended or offline, even while the human-readable listing correctly showed it\r\n  online — which could cause an adapter to needlessly suspend itself. The JSON listing now\r\n  reflects the same local liveness truth the interactive view uses. Re-binding an endpoint\r\n  also no longer discards its pending wake/rest state.\r\n\r\n## [0.30.2] - 2026-07-09\r\n\r\nA follow-on reliability patch for subnets with several peers.\r\n\r\n### Fixed\r\n\r\n- On a subnet with several peers, one unreachable or slow-to-connect peer could\r\n…\n  0.30.1 fix, which stopped a single silent peer from stalling the whole round.)\n\n…\nFirst public release of `spt`.","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,null,101,102,null,899]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CHANGELOG.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":900,"totalBytes":4576,"outputLines":95,"outputBytes":4576,"shownRange":{"start":1,"end":95},"nextOffset":96}}},"isError":false,"timestamp":1783730470736}}
{"type":"message","id":"ebd0ba7d","parentId":"da67b040","timestamp":"2026-07-11T00:41:10.765Z","message":{"role":"toolResult","toolCallId":"call_zcTgFpWmWczrlDMTm3Wu87r3|fc_08496348794f7e09016a51912745588198bbbb31a3d7dca3e4","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\n 126|    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n*127|    /// on its `rx.recv()` Err, reads this and — when true — emits a\n 128|    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n 129|    /// the serving brain skips to live instead of tearing the attach down.\n 130|    evicted: Arc<AtomicBool>,\n...\n 138|/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n*139|/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\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...\n 504|    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\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...\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 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 520|    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n 521|    /// state; the mutex is taken only around the brief set/clear and the read, never\n...\n 524|    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n*525|    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n 526|    /// when a socket write fails).\n 527|    _writer: JoinHandle<()>,\n 528|}\n...\n 534|/// Output fans out to ONE interactive `controller` (the authoritative,\n*535|/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n 536|/// an isolated bounded queue + writer thread, evicted on overflow). Every\n 537|/// controller send and viewer `try_send` happens while the enclosing\n 538|/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\n...\n 715|    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n*716|    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n 717|    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n 718|    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n 719|    /// log lock; `None` otherwise.\n...\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 728|    /// exactly like a viewer: the drain runs at full PTY speed regardless of\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 734|    /// single drain thread.\n 735|    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n...\n 802|        let frame = output_envelope(self.session_id, seq, bytes);\n*803|        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n 804|        if !self.viewers.is_empty() {\n 805|            let mut evicted: Vec<u64> = Vec::new();\n 806|            for (vid, sink) in self.viewers.iter() {\n*807|                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n 808|                    evicted.push(*vid);\n 809|                }\n 810|            }\n...\n 813|                    // Mark eviction BEFORE dropping the sink: the writer's\n*814|                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n 815|                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n 816|                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n 817|                    if let Some(sink) = self.viewers.remove(&vid) {\n...\n 823|        }\n*824|        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n 825|        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n 826|        // slow controller falls behind the ring) unless it has been Full for the\n 827|        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n...\n 831|        let result = if let Some(c) = self.controller.as_mut() {\n*832|            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n 833|                Ok(()) => {\n 834|                    c.last_ok = now;\n 835|                    None\n...\n 951|    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n*952|    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n 953|    /// controller) it is NOT finished, so a live controller is never touched (the\n 954|    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n 955|    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n...\n 993|    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and\n*994|    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same\n 995|    /// controller generation the drain saw (`epoch`), so a NEWER controller that\n 996|    /// attached during the stall is never unseated.\n 997|    fn mark_controller_gone(&mut self, epoch: u64) {\n...\n 1004|    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n*1005|    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n 1006|    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n 1007|    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n 1008|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n...\n 1011|            c.write_blocked_since\n*1012|                .lock()\n 1013|                .ok()\n 1014|                .and_then(|g| *g)\n 1015|                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n...\n 1177|                    let frame = displaced_envelope(self.session_id, by.as_deref().unwrap_or(\"\"));\n*1178|                    let _ = old.tx.try_send(CtrlMsg::Control(frame));\n 1179|                }\n 1180|                self.become_controller(sub, by, from_seq);\n 1181|                SubscribeOutcome::TookControl\n### attach.rs\n 138|///\n*139|/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n 140|/// bounded channel is full (a controller that falls behind its own echo under a\n 141|/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n 142|/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n...\n 211|\n*212|/// Serve one attach stream against this brain's broker, blocking until the\n 213|/// session exits, the operator detaches, or the gate refuses.\n 214|///\n 215|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n...\n 311|                        });\n*312|                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n 313|                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 314|                        return Err(e);\n 315|                    }\n...\n 349|                            {\n*350|                                wire.net_stream_send(stream_id, &[], None, true)?;\n 351|                                return Ok(AttachServeOutcome::Refused);\n 352|                            }\n 353|                            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): use the\n...\n 452|                        // renders the busy guidance client-side from driven_by.\n*453|                        wire.net_stream_send(stream_id, &[], None, true)?;\n 454|                        return Ok(AttachServeOutcome::Refused);\n 455|                    }\n 456|                }\n...\n 460|                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n*461|                wire.net_stream_send(stream_id, &line, None, false)?;\n 462|            }\n 463|            // This controller was kicked by a different operator's `--take`.\n 464|            BrokerEvent::Displaced { by, .. } if attached => {\n 465|                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n*466|                wire.net_stream_send(stream_id, &line, None, true)?;\n 467|                // No dormant edge: the endpoint stays driven by the taker (whose\n 468|                // own serve loop already fired the wake).\n 469|                return Ok(AttachServeOutcome::Displaced);\n...\n 486|                    if since < RESUBSCRIBE_INTERVAL {\n*487|                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n 488|                    }\n 489|                }\n 490|                brain.attach_skip_to_live(sid, Some(origin_node))?;\n...\n 495|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*496|                wire.net_stream_send(stream_id, &[], None, true)?;\n 497|                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 498|                // info.json, carrying the current driven_by through), THEN release\n 499|                // the role over IPC so the broker's driven_by=None is the LAST\n...\n 520|                // the record, and the operator's render cursor is the dedup.\n*521|                wire.net_stream_send(stream_id, &line, None, false)?;\n 522|            }\n 523|            BrokerEvent::Exit { code, .. } if attached => {\n 524|                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n*525|                wire.net_stream_send(stream_id, &line, None, true)?;\n 526|                if role == Some(ServeRole::Controller) {\n 527|                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 528|                }\n...\n 587|    });\n*588|    brain.net_stream_send(opened.stream_id, &line, None, false)?;\n 589|    Ok(opened.stream_id)\n 590|}\n 591|\n...\n 604|    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });\n*605|    brain.net_stream_send(stream_id, &line, None, false)?;\n 606|    Ok(())\n 607|}\n 608|\n...\n 623|    });\n*624|    brain.net_stream_send(stream_id, &line, None, false)?;\n 625|    Ok(())\n 626|}\n\n### pump/\n#### mod.rs\n 272|            owlery: perch::owlery_dir(),\n*273|            ctx_root: home.join(\"context\"),\n*274|            notif_db: identity.join(\"notifs.db\"),\n*275|            scratch: home.join(\"scratch\").join(\"pump\"),\n*276|            subnets: identity.join(\"subnet.json\"),\n*277|            vis: identity.join(\"visibility.json\"),\n*278|            ops: identity.join(\"pump-ops.json\"),\n*279|            releases: home.join(\"releases\"),\n*280|            release_keys: identity.join(\"release-keys.json\"),\n*281|            pull_marker: identity.join(crate::resting::PULL_MARKER_FILE),\n*282|            advertise_marker: identity.join(crate::registryhost::ADVERTISE_MARKER_FILE),\n*283|            peer_addrs: identity.join(\"peer-addrs.json\"),\n 284|            heartbeat: crate::endpoint::pump_heartbeat_path(),\n*285|            rotations: identity.join(\"rotation-pending.json\"),\n 286|        }\n 287|    }\n 288|}\n...\n 425|                .parent()\n*426|                .map(|p| p.join(\"attachment.json\"))\n 427|                .unwrap_or_else(|| PathBuf::from(\"attachment.json\")),\n 428|        );\n 429|        // THE WIDEN (Mesh-D6, REQ-MESH-3): the push target is the full roster\n...\n 435|            .parent()\n*436|            .map(|p| p.join(\"roster.json\"))\n 437|            .unwrap_or_else(|| PathBuf::from(\"roster.json\"));\n 438|        let policy = SyncPolicy {\n 439|            subnets: subnets\n...\n 592|        if !any_due {\n*593|            thread::sleep(TICK);\n 594|            continue;\n 595|        }\n 596|\n...\n 938|\n*939|/// Resolve the ONE address to submit a non-blocking dial to (M8 decisions 14/20,\n 940|/// REQ-CONV-1 seeded order): the durable last-known address (`peer-addrs.json`)\n 941|/// FIRST — the cold-start fast path that skips the id-only discovery round-trip —\n 942|/// else the injected resolver (production: id-only discovery). A stale seed costs\n...\n 945|/// a stale addr never strands a peer. The OBSERVED-addr write-back the old\n*946|/// blocking dial carried now rides the `PRESENCE_CONNECTED` event\n 947|/// ([`handle_presence_event`]). `None` = unresolvable right now — the peer\n 948|/// reschedules on its own cadence, never a round failure.\n 949|// [impl->REQ-CONV-1]\n...\n 965|    let opened = brain.net_open_stream(conn_id, None)?;\n*966|    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n 967|    Ok(())\n 968|}\n 969|\n...\n 981|        }\n*982|        thread::sleep(Duration::from_millis(10));\n 983|    }\n 984|    Err(last)\n 985|}\n...\n 1078|        while slept < backoff && !stop.load(Ordering::Relaxed) {\n*1079|            thread::sleep(TICK.min(backoff - slept));\n 1080|            slept += TICK;\n 1081|        }\n 1082|    }\n#### registry.rs\n 193|        let dir = tempfile::tempdir().unwrap();\n*194|        let subnets_path = dir.path().join(\"subnet.json\");\n*195|        let rotations_path = dir.path().join(\"rotation-pending.json\");\n 196|\n 197|        let mut subnets = SubnetStore::default();\n 198|        let before_home = subnets.create_subnet(\"home\").unwrap().clone();\n...\n 229|        let dir = tempfile::tempdir().unwrap();\n*230|        let marker = dir.path().join(\"advertise-now\");\n 231|        let mut w = RegistryWorker {\n 232|            registry: Arc::new(RegistryHost::new_at(\n 233|                \"aa11\",\n*234|                EpochSource::load_from(&dir.path().join(\"epoch.json\")),\n*235|                dir.path().join(\"registry\"),\n 236|            )),\n*237|            owlery: dir.path().join(\"owlery\"),\n*238|            subnets: dir.path().join(\"subnet.json\"),\n*239|            rotations: dir.path().join(\"rotation-pending.json\"),\n 240|            advertise_marker: marker.clone(),\n 241|            cadence: Duration::from_secs(30),\n 242|            evict_after: Duration::from_secs(600),\n...\n 260|            // A served subnet plus one advertisable Self perch.\n*261|            let owlery = home.join(\"owlery\");\n 262|            let id = \"adv-ag\";\n 263|            let p = spt_store::perch::resolve_perch_path_in(\n 264|                &owlery,\n...\n 277|\n*278|            let subnets_path = home.join(\"subnet.json\");\n 279|            let mut subnets = SubnetStore::load();\n 280|            let before = subnets.create_subnet(\"rot\").unwrap().clone();\n 281|            subnets.save().unwrap();\n...\n 284|            // A seed rotation already due — pre_round must fire it.\n*285|            let rotations_path = home.join(\"rotation-pending.json\");\n 286|            let mut pending = spt_store::rotation::RotationPending::default();\n 287|            pending.coalesce(\"rot\", &[\"aa\".to_string()], 1_000);\n 288|            pending.save_to(&rotations_path).unwrap();\n...\n 292|                    \"aa11\",\n*293|                    EpochSource::load_from(&home.join(\"epoch.json\")),\n*294|                    home.join(\"registry\"),\n 295|                )),\n 296|                owlery: owlery.clone(),\n 297|                subnets: subnets_path.clone(),\n 298|                rotations: rotations_path,\n*299|                advertise_marker: home.join(\"advertise-now\"),\n 300|                cadence: Duration::from_secs(30),\n 301|                evict_after: Duration::from_secs(600),\n 302|                adverts: Vec::new(),\n\n## spt/src/\n### rc.rs\n 982|    std::thread::spawn(move || {\n*983|        let mut stdin = std::io::stdin().lock();\n 984|        let mut buf = [0u8; 1024];\n 985|        let mut armed = false; // saw the detach prefix, awaiting the next byte\n 986|        loop {\n...\n 993|                let hex: Vec<String> = buf[..n].iter().map(|b| format!(\"{b:02x}\")).collect();\n*994|                eprintln!(\"RC_KEYS n={n} bytes=[{}]\", hex.join(\" \"));\n 995|            }\n 996|            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);\n*997|            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {\n 998|                break;\n 999|            }\n 1000|            if parsed.detach {\n*1001|                let _ = tx.send(StdinMsg::Detach);\n 1002|                return;\n 1003|            }\n 1004|        }\n...\n 1029|                        if let Some(bytes) = clipboard_paste(read_clipboard) {\n*1030|                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1031|                                break;\n 1032|                            }\n 1033|                        }\n...\n 1041|                            let bytes = scroll_sgr(up, me.column, me.row);\n*1042|                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1043|                                break;\n 1044|                            }\n 1045|                        }\n...\n 1052|                        KeyAction::Detach => {\n*1053|                            let _ = tx.send(StdinMsg::Detach);\n 1054|                            return;\n 1055|                        }\n 1056|                        KeyAction::Forward(bytes) => {\n*1057|                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1058|                                break;\n 1059|                            }\n 1060|                        }\n...\n 1064|                Event::Paste(s) => {\n*1065|                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {\n 1066|                        break;\n 1067|                    }\n 1068|                }\n...\n 1228|    if status.is_some() {\n*1229|        let _ = stdout.write_all(&StatusRow::restore_bytes());\n*1230|        let _ = stdout.flush();\n 1231|    }\n 1232|    // Out of raw mode (guard drops after) before the parting line.\n 1233|    drop(_raw);\n...\n 1403|    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n*1404|    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n 1405|    // pipes), so the single-threaded pump interleaves stdin + broker events\n 1406|    // without wedging. The generous io_timeout bounds the setup calls (dial,\n 1407|    // sessions, attach-open ack); the pump itself passes its own short slice.\n...\n 1585|            let remaining = reconnect_remaining_secs(elapsed);\n*1586|            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n*1587|            let _ = out.flush();\n 1588|            // Operator gives up mid-reconnect: honor the detach keys.\n 1589|            if let Some(rx) = rx {\n 1590|                let mut detached = false;\n*1591|                while let Ok(msg) = rx.try_recv() {\n 1592|                    if matches!(msg, StdinMsg::Detach) {\n 1593|                        detached = true;\n 1594|                    }\n...\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 1608|        match reestablished {\n...\n 1836|        stdout\n*1837|            .write_all(&status.assert_bytes())\n 1838|            .map_err(|e| format!(\"status assert: {e}\"))?;\n*1839|        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840|    }\n 1841|    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n 1842|    // (the session outlives the viewport). Best-effort: an already-finished or\n...\n 1844|    fn detach(brain: &mut Brain, stream_id: u64) -> PumpEnd {\n*1845|        let _ = brain.net_stream_send(stream_id, &[], None, true);\n 1846|        PumpEnd::Detached\n 1847|    }\n 1848|    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n...\n 1884|                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885|                            if stdout.write_all(&status.assert_bytes()).is_ok() {"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src/broker.rs, spt-core/crates/spt-daemon/src/pump, spt-core/crates/spt-daemon/src/attach.rs, spt-core/crates/spt-daemon/src/inject.rs, spt-core/crates/spt/src/rc.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":88,"fileCount":5,"files":["spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/pump/mod.rs","spt-core/crates/spt-daemon/src/pump/registry.rs","spt-core/crates/spt-daemon/src/attach.rs","spt-core/crates/spt/src/rc.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/pump/mod.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/pump/registry.rs","count":14},{"path":"spt-core/crates/spt-daemon/src/attach.rs","count":14},{"path":"spt-core/crates/spt/src/rc.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\n  126│    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n *127│    /// on its `rx.recv()` Err, reads this and — when true — emits a\n  128│    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n  129│    /// the serving brain skips to live instead of tearing the attach down.\n  130│    evicted: Arc<AtomicBool>,\n     │...\n  138│/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n *139│/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\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     │...\n  504│    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\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     │...\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  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  520│    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n  521│    /// state; the mutex is taken only around the brief set/clear and the read, never\n     │...\n  524│    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n *525│    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n  526│    /// when a socket write fails).\n  527│    _writer: JoinHandle<()>,\n  528│}\n     │...\n  534│/// Output fans out to ONE interactive `controller` (the authoritative,\n *535│/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n  536│/// an isolated bounded queue + writer thread, evicted on overflow). Every\n  537│/// controller send and viewer `try_send` happens while the enclosing\n  538│/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\n     │...\n  715│    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n *716│    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n  717│    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n  718│    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n  719│    /// log lock; `None` otherwise.\n     │...\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  728│    /// exactly like a viewer: the drain runs at full PTY speed regardless of\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  734│    /// single drain thread.\n  735│    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n     │...\n  802│        let frame = output_envelope(self.session_id, seq, bytes);\n *803│        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n  804│        if !self.viewers.is_empty() {\n  805│            let mut evicted: Vec<u64> = Vec::new();\n  806│            for (vid, sink) in self.viewers.iter() {\n *807│                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n  808│                    evicted.push(*vid);\n  809│                }\n  810│            }\n     │...\n  813│                    // Mark eviction BEFORE dropping the sink: the writer's\n *814│                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n  815│                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n  816│                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n  817│                    if let Some(sink) = self.viewers.remove(&vid) {\n     │...\n  823│        }\n *824│        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n  825│        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n  826│        // slow controller falls behind the ring) unless it has been Full for the\n  827│        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n     │...\n  831│        let result = if let Some(c) = self.controller.as_mut() {\n *832│            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n  833│                Ok(()) => {\n  834│                    c.last_ok = now;\n  835│                    None\n     │...\n  951│    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n *952│    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n  953│    /// controller) it is NOT finished, so a live controller is never touched (the\n  954│    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n  955│    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n     │...\n  993│    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and\n *994│    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same\n  995│    /// controller generation the drain saw (`epoch`), so a NEWER controller that\n  996│    /// attached during the stall is never unseated.\n  997│    fn mark_controller_gone(&mut self, epoch: u64) {\n     │...\n 1004│    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n*1005│    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n 1006│    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n 1007│    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n 1008│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n     │...\n 1011│            c.write_blocked_since\n*1012│                .lock()\n 1013│                .ok()\n 1014│                .and_then(|g| *g)\n 1015│                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n     │...\n 1177│                    let frame = displaced_envelope(self.session_id, by.as_deref().unwrap_or(\"\"));\n*1178│                    let _ = old.tx.try_send(CtrlMsg::Control(frame));\n 1179│                }\n 1180│                self.become_controller(sub, by, from_seq);\n 1181│                SubscribeOutcome::TookControl\n### attach.rs\n 138│///\n*139│/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n 140│/// bounded channel is full (a controller that falls behind its own echo under a\n 141│/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n 142│/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n    │...\n 211│\n*212│/// Serve one attach stream against this brain's broker, blocking until the\n 213│/// session exits, the operator detaches, or the gate refuses.\n 214│///\n 215│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n    │...\n 311│                        });\n*312│                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n 313│                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 314│                        return Err(e);\n 315│                    }\n    │...\n 349│                            {\n*350│                                wire.net_stream_send(stream_id, &[], None, true)?;\n 351│                                return Ok(AttachServeOutcome::Refused);\n 352│                            }\n 353│                            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): use the\n    │...\n 452│                        // renders the busy guidance client-side from driven_by.\n*453│                        wire.net_stream_send(stream_id, &[], None, true)?;\n 454│                        return Ok(AttachServeOutcome::Refused);\n 455│                    }\n 456│                }\n    │...\n 460│                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n*461│                wire.net_stream_send(stream_id, &line, None, false)?;\n 462│            }\n 463│            // This controller was kicked by a different operator's `--take`.\n 464│            BrokerEvent::Displaced { by, .. } if attached => {\n 465│                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n*466│                wire.net_stream_send(stream_id, &line, None, true)?;\n 467│                // No dormant edge: the endpoint stays driven by the taker (whose\n 468│                // own serve loop already fired the wake).\n 469│                return Ok(AttachServeOutcome::Displaced);\n    │...\n 486│                    if since < RESUBSCRIBE_INTERVAL {\n*487│                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n 488│                    }\n 489│                }\n 490│                brain.attach_skip_to_live(sid, Some(origin_node))?;\n    │...\n 495│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*496│                wire.net_stream_send(stream_id, &[], None, true)?;\n 497│                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 498│                // info.json, carrying the current driven_by through), THEN release\n 499│                // the role over IPC so the broker's driven_by=None is the LAST\n    │...\n 520│                // the record, and the operator's render cursor is the dedup.\n*521│                wire.net_stream_send(stream_id, &line, None, false)?;\n 522│            }\n 523│            BrokerEvent::Exit { code, .. } if attached => {\n 524│                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n*525│                wire.net_stream_send(stream_id, &line, None, true)?;\n 526│                if role == Some(ServeRole::Controller) {\n 527│                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 528│                }\n    │...\n 587│    });\n*588│    brain.net_stream_send(opened.stream_id, &line, None, false)?;\n 589│    Ok(opened.stream_id)\n 590│}\n 591│\n    │...\n 604│    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });\n*605│    brain.net_stream_send(stream_id, &line, None, false)?;\n 606│    Ok(())\n 607│}\n 608│\n    │...\n 623│    });\n*624│    brain.net_stream_send(stream_id, &line, None, false)?;\n 625│    Ok(())\n 626│}\n\n### pump/\n#### mod.rs\n  272│            owlery: perch::owlery_dir(),\n *273│            ctx_root: home.join(\"context\"),\n *274│            notif_db: identity.join(\"notifs.db\"),\n *275│            scratch: home.join(\"scratch\").join(\"pump\"),\n *276│            subnets: identity.join(\"subnet.json\"),\n *277│            vis: identity.join(\"visibility.json\"),\n *278│            ops: identity.join(\"pump-ops.json\"),\n *279│            releases: home.join(\"releases\"),\n *280│            release_keys: identity.join(\"release-keys.json\"),\n *281│            pull_marker: identity.join(crate::resting::PULL_MARKER_FILE),\n *282│            advertise_marker: identity.join(crate::registryhost::ADVERTISE_MARKER_FILE),\n *283│            peer_addrs: identity.join(\"peer-addrs.json\"),\n  284│            heartbeat: crate::endpoint::pump_heartbeat_path(),\n *285│            rotations: identity.join(\"rotation-pending.json\"),\n  286│        }\n  287│    }\n  288│}\n     │...\n  425│                .parent()\n *426│                .map(|p| p.join(\"attachment.json\"))\n  427│                .unwrap_or_else(|| PathBuf::from(\"attachment.json\")),\n  428│        );\n  429│        // THE WIDEN (Mesh-D6, REQ-MESH-3): the push target is the full roster\n     │...\n  435│            .parent()\n *436│            .map(|p| p.join(\"roster.json\"))\n  437│            .unwrap_or_else(|| PathBuf::from(\"roster.json\"));\n  438│        let policy = SyncPolicy {\n  439│            subnets: subnets\n     │...\n  592│        if !any_due {\n *593│            thread::sleep(TICK);\n  594│            continue;\n  595│        }\n  596│\n     │...\n  938│\n *939│/// Resolve the ONE address to submit a non-blocking dial to (M8 decisions 14/20,\n  940│/// REQ-CONV-1 seeded order): the durable last-known address (`peer-addrs.json`)\n  941│/// FIRST — the cold-start fast path that skips the id-only discovery round-trip —\n  942│/// else the injected resolver (production: id-only discovery). A stale seed costs\n     │...\n  945│/// a stale addr never strands a peer. The OBSERVED-addr write-back the old\n *946│/// blocking dial carried now rides the `PRESENCE_CONNECTED` event\n  947│/// ([`handle_presence_event`]). `None` = unresolvable right now — the peer\n  948│/// reschedules on its own cadence, never a round failure.\n  949│// [impl->REQ-CONV-1]\n     │...\n  965│    let opened = brain.net_open_stream(conn_id, None)?;\n *966│    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n  967│    Ok(())\n  968│}\n  969│\n     │...\n  981│        }\n *982│        thread::sleep(Duration::from_millis(10));\n  983│    }\n  984│    Err(last)\n  985│}\n     │...\n 1078│        while slept < backoff && !stop.load(Ordering::Relaxed) {\n*1079│            thread::sleep(TICK.min(backoff - slept));\n 1080│            slept += TICK;\n 1081│        }\n 1082│    }\n#### registry.rs\n 193│        let dir = tempfile::tempdir().unwrap();\n*194│        let subnets_path = dir.path().join(\"subnet.json\");\n*195│        let rotations_path = dir.path().join(\"rotation-pending.json\");\n 196│\n 197│        let mut subnets = SubnetStore::default();\n 198│        let before_home = subnets.create_subnet(\"home\").unwrap().clone();\n    │...\n 229│        let dir = tempfile::tempdir().unwrap();\n*230│        let marker = dir.path().join(\"advertise-now\");\n 231│        let mut w = RegistryWorker {\n 232│            registry: Arc::new(RegistryHost::new_at(\n 233│                \"aa11\",\n*234│                EpochSource::load_from(&dir.path().join(\"epoch.json\")),\n*235│                dir.path().join(\"registry\"),\n 236│            )),\n*237│            owlery: dir.path().join(\"owlery\"),\n*238│            subnets: dir.path().join(\"subnet.json\"),\n*239│            rotations: dir.path().join(\"rotation-pending.json\"),\n 240│            advertise_marker: marker.clone(),\n 241│            cadence: Duration::from_secs(30),\n 242│            evict_after: Duration::from_secs(600),\n    │...\n 260│            // A served subnet plus one advertisable Self perch.\n*261│            let owlery = home.join(\"owlery\");\n 262│            let id = \"adv-ag\";\n 263│            let p = spt_store::perch::resolve_perch_path_in(\n 264│                &owlery,\n    │...\n 277│\n*278│            let subnets_path = home.join(\"subnet.json\");\n 279│            let mut subnets = SubnetStore::load();\n 280│            let before = subnets.create_subnet(\"rot\").unwrap().clone();\n 281│            subnets.save().unwrap();\n    │...\n 284│            // A seed rotation already due — pre_round must fire it.\n*285│            let rotations_path = home.join(\"rotation-pending.json\");\n 286│            let mut pending = spt_store::rotation::RotationPending::default();\n 287│            pending.coalesce(\"rot\", &[\"aa\".to_string()], 1_000);\n 288│            pending.save_to(&rotations_path).unwrap();\n    │...\n 292│                    \"aa11\",\n*293│                    EpochSource::load_from(&home.join(\"epoch.json\")),\n*294│                    home.join(\"registry\"),\n 295│                )),\n 296│                owlery: owlery.clone(),\n 297│                subnets: subnets_path.clone(),\n 298│                rotations: rotations_path,\n*299│                advertise_marker: home.join(\"advertise-now\"),\n 300│                cadence: Duration::from_secs(30),\n 301│                evict_after: Duration::from_secs(600),\n 302│                adverts: Vec::new(),\n\n## spt/src/\n### rc.rs\n  982│    std::thread::spawn(move || {\n *983│        let mut stdin = std::io::stdin().lock();\n  984│        let mut buf = [0u8; 1024];\n  985│        let mut armed = false; // saw the detach prefix, awaiting the next byte\n  986│        loop {\n     │...\n  993│                let hex: Vec<String> = buf[..n].iter().map(|b| format!(\"{b:02x}\")).collect();\n *994│                eprintln!(\"RC_KEYS n={n} bytes=[{}]\", hex.join(\" \"));\n  995│            }\n  996│            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);\n *997│            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {\n  998│                break;\n  999│            }\n 1000│            if parsed.detach {\n*1001│                let _ = tx.send(StdinMsg::Detach);\n 1002│                return;\n 1003│            }\n 1004│        }\n     │...\n 1029│                        if let Some(bytes) = clipboard_paste(read_clipboard) {\n*1030│                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1031│                                break;\n 1032│                            }\n 1033│                        }\n     │...\n 1041│                            let bytes = scroll_sgr(up, me.column, me.row);\n*1042│                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1043│                                break;\n 1044│                            }\n 1045│                        }\n     │...\n 1052│                        KeyAction::Detach => {\n*1053│                            let _ = tx.send(StdinMsg::Detach);\n 1054│                            return;\n 1055│                        }\n 1056│                        KeyAction::Forward(bytes) => {\n*1057│                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1058│                                break;\n 1059│                            }\n 1060│                        }\n     │...\n 1064│                Event::Paste(s) => {\n*1065│                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {\n 1066│                        break;\n 1067│                    }\n 1068│                }\n     │...\n 1228│    if status.is_some() {\n*1229│        let _ = stdout.write_all(&StatusRow::restore_bytes());\n*1230│        let _ = stdout.flush();\n 1231│    }\n 1232│    // Out of raw mode (guard drops after) before the parting line.\n 1233│    drop(_raw);\n     │...\n 1403│    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n*1404│    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n 1405│    // pipes), so the single-threaded pump interleaves stdin + broker events\n 1406│    // without wedging. The generous io_timeout bounds the setup calls (dial,\n 1407│    // sessions, attach-open ack); the pump itself passes its own short slice.\n     │...\n 1585│            let remaining = reconnect_remaining_secs(elapsed);\n*1586│            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n*1587│            let _ = out.flush();\n 1588│            // Operator gives up mid-reconnect: honor the detach keys.\n 1589│            if let Some(rx) = rx {\n 1590│                let mut detached = false;\n*1591│                while let Ok(msg) = rx.try_recv() {\n 1592│                    if matches!(msg, StdinMsg::Detach) {\n 1593│                        detached = true;\n 1594│                    }\n     │...\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 1608│        match reestablished {\n     │...\n 1836│        stdout\n*1837│            .write_all(&status.assert_bytes())\n 1838│            .map_err(|e| format!(\"status assert: {e}\"))?;\n*1839│        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840│    }\n 1841│    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n 1842│    // (the session outlives the viewport). Best-effort: an already-finished or\n     │...\n 1844│    fn detach(brain: &mut Brain, stream_id: u64) -> PumpEnd {\n*1845│        let _ = brain.net_stream_send(stream_id, &[], None, true);\n 1846│        PumpEnd::Detached\n 1847│    }\n 1848│    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n     │...\n 1884│                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885│                            if stdout.write_all(&status.assert_bytes()).is_ok() {"},"isError":false,"timestamp":1783730470765}}
{"type":"message","id":"81e4df57","parentId":"ebd0ba7d","timestamp":"2026-07-11T00:41:10.807Z","message":{"role":"toolResult","toolCallId":"call_yqLNEtu4VCgEt70X9Up1WVh9|fc_08496348794f7e09016a519127456c819883500129c136223b","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/spt-daemon/src/\n## applyhost.rs\n 23|//!    orchestration is post-M5; ADR-0004 §A).\n*24|//! 3. **Swap the binary**: the current executable steps aside\n 25|//!    (`<exe>.old-<ver>` — a running image survives its own rename on both\n 26|//!    OSes) and the verified artifact lands at the executable path\n 27|//!    atomically. A failed landing rolls the original back — never a\n*28|//!    half-swapped seat.\n 29|//! 4. **Trigger a brain-PROCESS restart** ([`Brain::request_brain_restart`],\n 30|//!    ADR-0018 D3-3): the broker signals its supervisor to cycle the brain\n*31|//!    child onto the just-swapped binary with `start-reason=update`. No hosted\n 32|//!    endpoint terminates or suspends (REQ-UPD-3, absolute for this class — the\n 33|//!    broker holds them all across the cycle). This replaces the old in-process\n*34|//!    `apply_brain_only` handoff, the regression that re-attached a CLI-side\n*35|//!    subscriber while the live brain kept running the OLD code.\n 36|//!\n 37|//! New code runs **immediately** — the supervised respawn lands on the new\n 38|//! executable, no manual bounce, no logon. Readiness-gated promotion +\n...\n 58|    /// exit-0 no-op (F-025). Short-circuits BEFORE the binary-aside rename, whose\n*59|    /// re-run on an already-swapped exe is the 'Access is denied (os error 5)'\n 60|    /// this fixes. `version`/`product_version` name what is already installed.\n 61|    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n 62|    AlreadyApplied {\n...\n 66|    /// The at-rest re-verification refused (tamper / expiry / key trust) —\n*67|    /// nothing swapped, the reason says why.\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...\n 76|    Quarantined { version: u64 },\n*77|    /// Swapped + brain-restart **triggered** — the new code is now **on trial**,\n 78|    /// not yet promoted (ADR-0018 Q7, D6-2): the supervisor promotes it on the\n 79|    /// brain's `ready` signal or auto-rolls-back on failure. `apply` returns\n 80|    /// provisional immediately (it cannot observe the asynchronous verdict — the\n...\n 90|    },\n*91|    /// Swapped the binary on disk with NO running daemon touched — a\n 92|    /// **daemonless** apply (REQ-UPDATE-ONE-SHOT-FINISH). No broker was booted to\n*93|    /// hand off to (the old-broker-boot-pre-swap wart) and no brain was cycled;\n 94|    /// the `AppliedPending` trial is recorded so the NEXT daemon start boots the\n 95|    /// new bytes and promotes it on the fresh brain's `ready`. `version` is the\n 96|    /// monotonic counter; `product_version` the human semver (empty pre-v0.3.2).\n...\n 103|\n*104|/// Either an early terminal apply outcome (nothing swaps) or the go-ahead to\n*105|/// swap, carrying the verified bytes. The shared verify/classify preamble both\n 106|/// [`apply_staged`] and [`apply_staged_daemonless`] run before they diverge on\n 107|/// whether a live broker is handed off to.\n 108|enum Prepared {\n*109|    /// A terminal outcome reached before any swap (NoUpdate / AlreadyApplied /\n 110|    /// Quarantined / Rejected / RefusedClass) — return it verbatim.\n 111|    Done(ApplyStagedOutcome),\n 112|    /// Verified brain-only bytes ready to land at the exe path.\n...\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 126|fn prepare_apply(cache: &ReleaseCache, release_keys: &Path) -> Result<Prepared, String> {\n...\n 153|    // applied image must NOT reach the binary-aside rename below — re-renaming an\n*154|    // already-swapped exe is today's 'Access is denied (os error 5)'. Short-circuit\n 155|    // to a friendly exit-0 no-op instead. Checked before verify/connect: an\n 156|    // already-applied node is up to date regardless of the staged bytes' state.\n 157|    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n...\n 182|            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses\n*183|            // — exactly the path that swapped a Linux ELF in as `spt.exe`.\n 184|            let here = current_platform();\n 185|            match cache.staged_platform() {\n 186|                Some(p) if p == here => {}\n...\n 247|\n*248|/// Swap the binary at `exe_path` (step aside → land → roll back on failure) and\n*249|/// record the open `AppliedPending` trial. Shared by the broker-handoff apply\n 250|/// and the daemonless apply — both land the SAME bytes and record the SAME trial\n 251|/// (the supervisor promotes it on the next brain's `ready`); they differ only in\n 252|/// whether a running brain is cycled afterwards.\n 253|// [impl->REQ-UPD-6]\n*254|fn swap_and_record(\n 255|    cache: &ReleaseCache,\n 256|    exe_path: &Path,\n 257|    version: u64,\n...\n 259|) -> Result<(), String> {\n*260|    // Swap the binary: step aside, land the artifact, roll back on failure.\n## attach.rs\n 39|//! A restarted target brain re-attaches the session from its output cursor,\n*40|//! resubscribes the stream from its net cursor, and re-drives journaled sends;\n*41|//! a restarted operator brain resubscribes and dedups rendered chunks by\n 42|//! `Output.seq`.\n 43|\n 44|use std::io;\n...\n 56|/// served viewport is a **driver** — attaching wakes the endpoint, the\n*57|/// viewport ending detaches it (active → dormant; the transition echo is\n 58|/// gate-armed for the endpoint's own pulse loop). Best-effort exactly like\n 59|/// [`mark_driven_by`]: a session served without a perch has no resting\n 60|/// record to move.\n...\n 85|\n*86|/// The role a serve loop resolved to, once the broker's [`SubscribeOutcome`]\n 87|/// arrives — gates wake (controller-only) and PTY input.\n 88|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 89|enum ServeRole {\n...\n 95|\n*96|/// Minimum spacing between skip-to-live re-subscribes after a viewer eviction\n 97|/// (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). HARD constraint: an evicted viewer that is\n 98|/// hopelessly behind under a sustained flood will be re-evicted immediately after\n*99|/// each re-subscribe; without spacing that is an evict→resubscribe CPU spin. With\n 100|/// it, the operator sees an intermittent LIVE burst each interval (forward\n 101|/// progress), never a busy-loop. ~200ms = several bursts/sec, imperceptible lag.\n*102|const RESUBSCRIBE_INTERVAL: Duration = Duration::from_millis(200);\n 103|\n 104|/// The CONTROLLER fell behind the live ring further than the ring holds — the\n 105|/// dropped frames are GONE (`tail - delivered_through > cap_chunks`), so an\n...\n 109|/// DEFERRED to `REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND`.\n*110|// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n 111|#[derive(Debug)]\n 112|pub struct ControllerIrrecoverablyBehind {\n 113|    /// The frozen `delivered_through` floor the ring has already rolled past.\n...\n 129|/// Is `e` the brain's strict reject-gap fatal (`output gap: got seq N want M`)?\n*130|/// That is the only error the controller gap-resume recovers from; every other\n 131|/// error propagates unchanged.\n 132|fn is_output_gap(e: &io::Error) -> bool {\n 133|    e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"output gap\")\n...\n 139|/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n*140|/// bounded channel is full (a controller that falls behind its own echo under a\n 141|/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n 142|/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n 143|/// controller CANNOT snap-above like a viewer (B2: it is authoritative — it\n 144|/// advances `delivered_through`; skipping rolled frames would be a not-exactly-once\n*145|/// resume). So instead it RE-FETCHES: re-subscribe from\n*146|/// [`Brain::controller_resume_floor`] (`= delivered_through`, frozen at the drop by\n 147|/// `contiguous_advance`) and the broker replays the dropped frames from the ring.\n 148|///\n 149|/// Exactly-once holds IFF the ring still retains the floor (`tail - delivered_through\n 150|/// <= cap_chunks`). If it does not — the controller fell behind further than the\n*151|/// ring holds — the re-subscribe's first frame is STILL a forward gap (floor\n*152|/// unchanged across two resumes). That is [`ControllerIrrecoverablyBehind`]: the\n 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 158|pub fn read_controller_event_resuming(\n 159|    brain: &mut Brain,\n 160|    session_id: u64,\n...\n 162|) -> io::Result<BrokerEvent> {\n*163|    let mut last_resume_floor: Option<u64> = None;\n*164|    let mut swallow_resume_ack = false;\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## brain.rs\n 3|//! The brain holds all the volatile logic; it may be killed and restarted at\n*4|//! any moment (a routine self-update swaps it; a crash kills it). It connects to\n 5|//! the stable [`crate::broker::Broker`] over the versioned IPC, drives sessions\n 6|//! (spawn / input / resize / kill), and consumes their output. Two invariants\n 7|//! this type encodes:\n 8|//!\n*9|//! - **`gen_start = now()` on cold-start *and* handoff (KNOWN-HAZARDS 2.4).**\n 10|//!   The wall-clock generation stamp is taken fresh on every start. A handed-off\n*11|//!   brain rehydrates its resume cursor and generation *counter* from prior\n 12|//!   state, but its `gen_start` is `now()`, **never** the stale value from that\n 13|//!   state — a rehydrated `gen_start` would fire time-based discriminators on\n*14|//!   the new generation. [`Brain::handoff`] takes the prior state by value yet\n 15|//!   ignores its `gen_start_ms`.\n*16|//! - **Gapless, exactly-once resume.** The brain tracks `next_seq` — the next\n*17|//!   output sequence it still needs. On handoff it `subscribe`s from that cursor;\n 18|//!   the broker replays buffered output from there. [`Brain::read_event`] accepts\n 19|//!   only the contiguous next `seq` (advancing the cursor), silently drops an\n 20|//!   already-seen `seq` (a replay duplicate), and treats a forward gap as a hard\n...\n 40|    InputReq,\n*41|    KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,\n 42|    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,\n*43|    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq,\n*44|    SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,\n 45|    ViewerEvictedEvent,\n 46|    KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY, KIND_VIEWER_EVICTED,\n 47|    KIND_BRAIN_RESTARTED, KIND_DISPLACED, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL,\n 48|    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT, KIND_NET_DIAL_SUBMITTED,\n*49|    KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,\n 50|    KIND_NET_SENT,\n 51|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n 52|    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,\n*53|    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 54|    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n*55|    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n 56|};\n 57|use spt_net::net::attach::AttachIntent;\n 58|use crate::transport::{send_hello, LocalSocketTransport};\n...\n 68|\n*69|/// The brain's durable state across a handoff: enough to re-attach gaplessly and\n 70|/// advance the generation. `gen_start_ms` is recorded for observability but is\n*71|/// **deliberately not authoritative on restart** — [`Brain::handoff`] sets a\n 72|/// fresh `gen_start` from `now()` (2.4).\n 73|///\n 74|/// **Test-only continuity (restoration D4-3, ADR-0018 Q6).** This brain→brain\n...\n 76|/// respawns reconstructs continuity by querying the broker\n*77|/// ([`Brain::resume_sessions`] over the broker's cursor-of-record), never this\n*78|/// message. `BrainState` / [`Brain::handoff`] / [`Brain::snapshot`] stay `pub`\n 79|/// solely because the integration tests (separate test target — `#[cfg(test)]`\n*80|/// would hide them) still drive the handoff-frame shape directly; there is **no\n 81|/// non-test caller** (KNOWN-HAZARDS 2.4 grep-clean close-out).\n 82|#[derive(Debug, Clone)]\n 83|pub struct BrainState {\n...\n 85|    pub session_id: u64,\n*86|    /// The generation counter (incremented on each handoff).\n 87|    pub generation: u64,\n*88|    /// The next output sequence still needed (the resume cursor).\n 89|    pub next_seq: u64,\n 90|    /// The prior generation's wall-clock start (ignored as authoritative on\n*91|    /// handoff — kept only to demonstrate it is *not* carried forward).\n 92|    pub gen_start_ms: u64,\n 93|}\n 94|\n## brainproc.rs\n 4|//! ADR-0004 specified the broker/brain split as two **processes**; production\n*5|//! drifted to an in-process thread (`daemon.rs`), so `spt update apply` swaps\n 6|//! the binary on disk but never restarts the running code (the regression\n 7|//! ADR-0018 corrects). D1 restores the process boundary as a *true minimal\n 8|//! skeleton*: `spt daemon run` is the broker process and spawns a supervised\n...\n 21|//! supervisor respawns it from the executable path, which on a real update is\n*22|//! the swapped binary.\n 23|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 24|\n 25|use std::io;\n...\n 72|/// never a parse failure that would brick the brain before it rehydrates.\n*73|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 74|#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n 75|pub enum StartReason {\n 76|    /// The broker's first brain spawn this broker lifetime.\n...\n 79|    Crash,\n*80|    /// A respawn the broker itself initiated for a binary swap (wired in D3-3).\n 81|    Update,\n 82|}\n 83|\n...\n 108|/// its next wait tick, kills the current brain child, and respawns it with\n*109|/// `start-reason=update` onto the (freshly-swapped) binary. Shared (`Arc`)\n 110|/// between the broker serve path and the supervisor — both run in the broker\n 111|/// process. This is the seam that makes a routine `apply` actually run new code,\n 112|/// the pillar the in-process-collapse regression silently unrealized.\n...\n 133|    pub fn take(&self) -> bool {\n*134|        self.requested.swap(false, Ordering::SeqCst)\n 135|    }\n 136|}\n 137|\n...\n 142|/// returns `Err`, exiting the process so the broker-side supervisor respawns it\n*143|/// from the (possibly swapped) binary.\n 144|///\n 145|/// **D2-1 — net consumers run here.** The inbound dispatcher + outbound peer\n 146|/// pump are pure IPC clients (each `Brain::cold_start`s its own connection; no\n 147|/// shared `Arc<Broker>`), so they belong with the restartable brain, not the\n*148|/// broker — a brain respawn re-spawns them from the swapped binary. They are\n 149|/// meaningful only once the broker-owned NetHost is up, so we gate on the\n 150|/// existing `net-status` verb (which also serves as the broker-liveness probe)\n 151|/// and start them exactly once, on the first `enabled` reply. That single gate\n...\n 180|\n*181|    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\n*182|    // continuity from the broker — re-attach EVERY hosted session in resume mode\n 183|    // from its delivered cursor, the production replacement for the retired\n*184|    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\n 185|    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\n 186|    // so this is a no-op now and forward-correct when daemon-hosted sessions land\n 187|    // (the live-agent adapter). A failure here is non-fatal — the brain still\n...\n 191|    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\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 204|            eprintln!(\n*205|                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\n 206|                ids.len()\n 207|            )\n 208|        }\n## broker.rs\n 9|//!\n*10|//! ## Gapless handoff (B2 — the load-bearing invariant)\n 11|//!\n 12|//! A brain may be killed and restarted at any moment (a routine self-update, a\n 13|//! crash). The broker — and every hosted child — must survive that untouched,\n...\n 18|//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n*19|//!   in a bounded ring. The current subscriber (a brain connection) receives\n*20|//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n 21|//!   reach the brain in strict `seq` order with no interleave.\n*22|//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n 23|//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n 24|//!   live. The brain dedups by `seq`. Output produced *while no brain was\n*25|//!   attached* sits in the ring and is replayed on the next subscribe — so a\n 26|//!   brain restart loses nothing.\n 27|//!\n 28|//! Sessions are therefore **broker-owned and outlive any one connection**: a\n*29|//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n 30|//! output log persist. A session leaves the table only when its child exits\n 31|//! (reaped by the exit waiter) or is explicitly killed.\n 32|// [impl->REQ-DAEMON-2]\n...\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 46|use spt_net::net::endpoint::EndpointAddr;\n...\n 55|    evicted_envelope, net_dialed_envelope, output_envelope,\n*56|    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n*57|    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n*58|    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n 59|    NetStreamsReply, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\n*60|    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,\n 61|    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT,\n*62|    KIND_NET_DIAL_SUBMITTED, KIND_NET_PRESENCE_SUBSCRIBE,\n 63|    KIND_NET_SENT,\n 64|    KIND_ADAPTER_APPLY, KIND_APPLIED,\n 65|    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\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_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 71|use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n 72|use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n...\n 90|/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n*91|/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n 92|/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n 93|/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n 94|/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n...\n 105|/// the live stream) or a `Disconnected` one (its writer thread ended after a\n*106|/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n 107|/// is the pure decision the fan-out applies under the log lock.\n 108|// [impl->REQ-HAZARD-VIEWER-ISOLATION]\n 109|fn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n...\n 112|\n*113|/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n 114|/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n 115|/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n*116|/// only its own queue — never the drain. When the queue overflows the sink is\n 117|/// dropped (the `tx` closes → the writer ends) and removed from the map.\n 118|struct ViewerSink {\n 119|    /// The bounded live-frame queue to this viewer's writer thread.\n...\n 138|/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n*139|/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\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...\n 145|\n*146|/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n## config.rs\n 30|/// liveness and signs off (KNOWN-HAZARDS 1.1). Sized to cover a transient recovery\n*31|/// — a binary handoff relaunch or a brief stale poll — without delaying a real\n 32|/// teardown for long. Configurable per machine via `crash_grace_ms`.\n 33|pub const DEFAULT_CRASH_GRACE: Duration = Duration::from_secs(2);\n 34|\n## conn.rs\n 176|    fn poison_and_cancel(&self) {\n*177|        if !self.poisoned.swap(true, Ordering::AcqRel) {\n 178|            // First poison of this conn: loud, once — the observable fact the\n 179|            // field-acceptance harness (blackhole-controller.ps1) watches for.\n 180|            eprintln!(\n...\n 214|    // guard's early `return` becomes the function tail — needless_return\n*215|    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n 216|    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\n 217|    #[cfg_attr(unix, allow(clippy::needless_return))]\n 218|    fn sever(&self) {\n*219|        if self.severed.swap(true, Ordering::AcqRel) {\n 220|            return;\n 221|        }\n 222|        #[cfg(windows)]\n## crc_swap.rs\n*1|//! W3b — content-hash-gated atomic per-file swap for a live adapter update\n 2|//! (ADR-0025, REQ-ADAPTER-LIVE-UPDATE).\n 3|//!\n 4|//! The legacy `spt adapter update` path does `remove_dir_all(install_dir)` then a\n...\n 6|//! the moment a single install-dir binary is still running (the field bug). This\n*7|//! module replaces that with a **content-diff swap**: extract the staged archive\n 8|//! to a temp tree, replace in the install dir **only** the files whose content\n 9|//! hash differs (so identical files — and their still-running binaries — are\n 10|//! never touched), and commit via **stage-all-`.new`-then-one-tight-rename-loop**\n*11|//! so a mid-swap failure never strands the install half-new (doyle W3 gate,\n 12|//! binding partial-failure refinement).\n 13|//!\n 14|//! CRC here is plain content hashing (reuse [`crate::release::sha256_hex`], no new\n...\n 20|\n*21|/// One planned file replacement discovered by [`plan_crc_swap`].\n 22|#[derive(Debug, Clone, PartialEq, Eq)]\n*23|pub struct PlannedSwap {\n 24|    /// Path relative to the tree root (for diagnostics / determinism).\n 25|    pub rel: PathBuf,\n 26|    /// The staged source file (`<staging>/<rel>`).\n...\n 37|// [impl->REQ-ADAPTER-LIVE-UPDATE]\n*38|pub fn plan_crc_swap(staging: &Path, install_dir: &Path) -> std::io::Result<Vec<PlannedSwap>> {\n 39|    let mut plan = Vec::new();\n 40|    collect_diffs(staging, staging, install_dir, &mut plan)?;\n 41|    plan.sort_by(|a, b| a.rel.cmp(&b.rel));\n...\n 50|    install_dir: &Path,\n*51|    plan: &mut Vec<PlannedSwap>,\n 52|) -> std::io::Result<()> {\n 53|    for entry in std::fs::read_dir(dir)? {\n 54|        let entry = entry?;\n...\n 70|        if differs {\n*71|            plan.push(PlannedSwap {\n 72|                rel,\n 73|                staged: path,\n 74|                target,\n...\n 80|\n*81|/// Apply a [`plan_crc_swap`] result with a never-strand commit protocol:\n 82|///\n 83|/// 0. **GC** — sweep any stale `<target>.old*` litter left by a PRIOR bricked run\n 84|///    (best-effort; a still-mapped one stays but is uniquely named).\n...\n 90|///\n*91|/// **C1 (REQ-CRC-SWAP-OLD-DISPLACE): displace, never replace.** The prior impl\n 92|/// renamed the original to a FIXED `<target>.old`. On a live box a surviving\n 93|/// endpoint child still image-maps the old binary, so Phase-3's `remove_file(.old)`\n 94|/// silently fails and the `.old` lingers; the NEXT update's `rename(target, .old)`\n...\n 106|// [impl->REQ-ADAPTER-LIVE-UPDATE]\n*107|// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*108|pub fn apply_crc_swap(plan: &[PlannedSwap]) -> std::io::Result<()> {\n*109|    apply_crc_swap_with(plan, &|from, to| std::fs::rename(from, to))\n 110|}\n 111|\n*112|/// [`apply_crc_swap`] over an injectable `rename` so the never-strand rollback is\n 113|/// unit-testable deterministically: the displace fix removes every rename-OVER-an-\n 114|/// existing-file (fresh names only), so no filesystem fixture can force a mid-loop\n 115|/// commit failure any more — the seam supplies one instead. Production passes\n 116|/// `std::fs::rename`.\n*117|// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*118|fn apply_crc_swap_with(\n*119|    plan: &[PlannedSwap],\n 120|    rename: &dyn Fn(&Path, &Path) -> std::io::Result<()>,\n 121|) -> std::io::Result<()> {\n 122|    // Phase 0 — GC stale `.old*` litter from a prior (bricked) run, but ONLY when\n...\n 130|    // repopulates the target, and the NEXT run (target now present) sweeps it.\n*131|    // [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*132|    for swap in plan {\n*133|        if swap.target.exists() {\n## daemon.rs\n 221|        // the digest-control channel serves `spt digest <id>` snapshot pulls +\n*222|        // delta-stream subscribes against it. Served on a background thread like\n 223|        // the broker. The per-session feed that *populates* the hub is driven by\n 224|        // the brain (the parse is logic — ADR-0004 §B), wired as daemon-hosted\n 225|        // sessions are spawned through the broker; a bare daemon serves the\n...\n 282|        // `BrainRestart` signal is wired into the broker so the `KIND_BRAIN_RESTART`\n*283|        // verb (sent by `spt update apply` after the binary swap) cycles the brain\n 284|        // onto the new binary with `start-reason=update`. The stop flag is held\n 285|        // for symmetry, never raised (the daemon runs until killed). The brain\n 286|        // child inherits this process's already-unelevated token.\n...\n 303|                Arc::clone(&reaper),\n*304|                // The DRAINED promotion gate (REQ-UPDATE-PROMOTE-DRAINED) reads this\n 305|                // broker's live control-plane truth in-process; None on a bind-failed\n 306|                // daemon degrades the gate to readiness-only.\n 307|                broker_handle.clone(),\n...\n 978|/// defense (REQ-HAZARD-DETACHED-DAEMON-STDIO). The decision is EXACTLY \"is stderr an\n*979|/// undrained PIPE\": a pipe nobody drains is the whole hazard — its write BLOCKS the\n 980|/// daemon once full. Every OTHER sink is safe and MUST survive: a FILE (`2>run.log`,\n 981|/// AND every int test's `Stdio::from(file)` brain-log capture) is a disk handle that\n 982|/// never blocks; a CONSOLE scrolls; a NULL/absent handle (the DETACHED_PROCESS rungs)\n 983|/// already discards. So we DELIBERATELY do NOT gate on \"no console\" (`GetConsoleWindow`):\n*984|/// a CREATE_NO_WINDOW daemon has no console window yet a perfectly-drained FILE stderr\n 985|/// — nulling it would blank the capture (the test-harness brain-log assertions) for\n 986|/// ZERO safety gain, since a file never blocks. `stderr_file_type` is the raw\n 987|/// `GetFileType(stderr)`. Pure so both CI legs unit-test it. // [impl->REQ-HAZARD-DETACHED-DAEMON-STDIO]\n...\n 992|/// Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the 3 std handles when\n*993|/// this `daemon run`'s stderr is an undrained PIPE (`GetFileType == FILE_TYPE_PIPE`)\n 994|/// — the only sink that would BLOCK the daemon when it fills — so stdio degrades to a\n 995|/// safe discard REGARDLESS of whether the launching rung remembered `--detached`.\n 996|/// Unlike [`detach_console`] it does NOT `FreeConsole` (that is the `--detached`\n...\n 1000|/// FOREGROUND-CAPTURE NOTE: a piped foreground diagnostic (`spt daemon run 2>&1 | tee\n*1001|/// log`) IS discarded (its stderr is a pipe) — the undrained-pipe defense outweighs\n 1002|/// pipe-tee ergonomics. The SANCTIONED foreground-capture form is a FILE redirect\n 1003|/// (`spt daemon run 2>run.log`): a disk handle is not a pipe, so it survives (this is\n 1004|/// exactly why the int-test brain-log capture is safe). Called unconditionally at\n...\n 1237|    // NULL/absent handle (DETACHED_PROCESS rungs) all SURVIVE — nulling them is\n*1238|    // wrong (blanks a drained capture) and pointless (they never block). RED-FIRST:\n 1239|    // returning `true` unconditionally makes the non-pipe cases go red — which is\n 1240|    // exactly the mass int-test brain-log-blanking regression the file-type gate avoids.\n 1241|    #[test]\n...\n 1245|        const FILE_TYPE_CHAR: u32 = 0x0002;\n*1246|        assert!(should_null_std_handles(FILE_TYPE_PIPE), \"an undrained pipe blocks ⇒ null\");\n 1247|        assert!(\n 1248|            !should_null_std_handles(FILE_TYPE_DISK),\n 1249|            \"a FILE redirect (2>run.log / the int-test brain-log) is disk ⇒ survives\"\n...\n 1261|    // [unit->REQ-DAEMON-9] the net self-heal backoff ladder doubles each retry\n*1262|    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n 1263|    // 30s poll, never an ever-growing or zero-length spin.\n 1264|    #[test]\n 1265|    fn net_retry_backoff_doubles_then_caps() {\n## deadline.rs\n 3|//! Phase-significant periodic timing (the pulse loop) and one-shot deadlines\n*4|//! (alarms) must survive a brain swap: the supervisor respawns a fresh brain\n 5|//! (`Brain::cold_start`) and the outgoing brain is gone before the new one starts\n 6|//! (D3-3), so continuity cannot ride a brain→brain frame — it must come from the\n 7|//! persistent side (disk), exactly as D4 moved session continuity to the broker.\n...\n 166|    /// - **skips past `now`** so a long stall — a `pulse_tick` that outran the\n*167|    ///   interval, or the real killer, a laptop suspend/resume with the daemon\n 168|    ///   resident — collapses to **exactly one** catch-up fire instead of N rapid\n 169|    ///   back-to-back fires. (`next_after(deadline)` alone steps a single grid per\n 170|    ///   fire, so a deadline N grids behind `now` would fire N times in a tight\n 171|    ///   loop — the exact storm the loop-entry `next_fire(now)` collapse promises\n*172|    ///   to avoid; this composes with that collapse so a suspend/resume fires once\n 173|    ///   total.)\n 174|    pub fn advance_after_fire(&self, fired_ms: u64, now_ms: u64) -> u64 {\n 175|        self.next_after(fired_ms.max(now_ms))\n...\n 242|        assert_eq!(a.advance_after_fire(1_300, 1_300), 1_400);\n*243|        // Stall (suspend/resume or an overrunning tick): now sits 10+ grids past\n 244|        // the fired point → ONE catch-up fire at the next grid AFTER now, NOT a\n 245|        // storm of one-grid steps. fire@1_000, now=2_050 → 2_100 (not 1_100).\n 246|        assert_eq!(a.advance_after_fire(1_000, 2_050), 2_100);\n## digest.rs\n 8|//! on every output chunk. The digest is now a **projection of the same normalized\n*9|//! history the echo-commune consumes** — so there is nothing to feed: a snapshot\n 10|//! pull (or a `digest-entry` push) **projects on demand** from the endpoint's\n 11|//! records via [`project_endpoint_digest`], and the hub publishes the result.\n 12|//! Render surfaces read the PTY (the raw byte ring, unchanged); content surfaces\n...\n 31|//! ([`crate::digesthub::DigestHub::project_and_publish`]) to produce a\n*32|//! [`DigestUpdate`]: `from` is where the changed turns begin, and the subscriber\n 33|//! applies it by **truncating its view to `from`, then appending `turns`**. The\n 34|//! delta-stream **contract** ships; its deltas are driven by pull / digest-entry\n 35|//! publishes (autonomous file-watch freshness is deferred to the consuming\n...\n 59|/// `[digest]` presentation defaults that **any consumer may override** at\n*60|/// pull/subscribe). Every field is optional — unset falls through to the\n 61|/// adapter default, then the spt-core fallback ([`DigestConfig::default`]).\n 62|#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]\n 63|pub struct DigestOverride {\n...\n 100|\n*101|/// A structured digest change to push to a subscriber. Sent only when the digest\n 102|/// actually changed (so the stream carries deltas, not heartbeats).\n 103|#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n 104|pub struct DigestUpdate {\n...\n 189|        // Collapse cross-generation replay dupes on the RAW span, before the\n*190|        // two-origin merge + window fold, so a checkpoint/resume row surfaces once\n 191|        // (under its live generation) not twice. [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n 192|        supersede_cross_generation(spanned)\n 193|    } else {\n...\n 321|/// text, tool)`, the doyle ruling's supersede key (docs/W3-DIGEST-GENERATION-UNION-RULING.md).\n*322|/// A checkpoint/resume replays an ancestor generation's transcript verbatim, so a\n 323|/// replayed row is BYTE-identical on exactly these fields; NUL-joined so distinct\n 324|/// field boundaries never collide.\n 325|// [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n...\n 341|/// Collapse cross-generation replay dupes in a spanned activity backbone (flynn\n*342|/// digest gen-union, doyle Option-1 Supersede). A checkpoint/resume makes the\n 343|/// harness REPLAY the prior generation's transcript into the new session file, so\n 344|/// one logical row surfaces under two `seq`-generations — `(gen_old, localseq)`\n 345|/// and `(gen_new, localseq)` — and exact-seq consumer dedup can't collapse them.\n...\n 681|\n*682|    // [unit->REQ-DIGEST-GENERATION-SUPERSEDE] a checkpoint/resume replays gen A's\n 683|    // rows into gen B at the same localseq → the span carries each logical row\n 684|    // twice (two ordinals). Supersede keeps ONE, under B's (newer) generation, and\n 685|    // the surviving seq is B's.\n## digesthub.rs\n 4|//! The hub holds the latest projected snapshot per endpoint and fans every change\n*5|//! out to live subscribers. The digest is **projected on demand** from the\n 6|//! endpoint's normalized records ([`project_endpoint_digest`]) — there is no live\n 7|//! feed; a pull or a `digest-entry` push triggers a projection, and\n 8|//! [`DigestHub::project_and_publish`] diffs it against the prior snapshot to\n...\n 14|//! - **delta-stream** — `spt endpoint digest <id> --follow` ([`follow`])\n*15|//!   subscribes, receives a base snapshot, then **only the changes**\n 16|//!   ([`DigestUpdate`]s) as pulls / `digest-entry` pushes re-project. The stream\n 17|//!   contract ships; autonomous file-watch freshness is deferred to the consuming\n 18|//!   frontend milestone (CONTEXT.md \"session digest\").\n...\n 45|pub const KIND_DIGEST_RESULT: &str = \"digest_result\";\n*46|/// Client→daemon: subscribe to an endpoint's delta-stream from a version floor.\n*47|pub const KIND_DIGEST_SUBSCRIBE: &str = \"digest_subscribe\";\n 48|/// Daemon→client: one structured digest change (the streamed delta / base).\n 49|pub const KIND_DIGEST_DELTA: &str = \"digest_delta\";\n 50|/// Client→daemon: re-project an endpoint now and publish any change to live\n*51|/// subscribers — the push-driven delta after an `api digest-entry` append (no\n*52|/// reply; the delta reaches subscribers via the fan-out, REQ-TERM-4).\n 53|pub const KIND_DIGEST_REPROJECT: &str = \"digest_reproject\";\n 54|\n*55|/// `digest_snapshot` / `digest_subscribe` request payload.\n 56|#[derive(Debug, Clone, Serialize, Deserialize)]\n 57|pub struct DigestReq {\n 58|    /// The (local) endpoint id whose digest to read.\n 59|    pub id: String,\n*60|    /// Subscribe floor: only stream changes with `version > from_version`. The\n*61|    /// base snapshot is sent regardless so a fresh subscriber has an apply base.\n 62|    #[serde(default)]\n 63|    pub from_version: u64,\n 64|    /// Consumer presentation override (ADR-0019) — overrides the adapter's\n*65|    /// `[digest]` defaults for this pull/subscribe. Empty ⇒ adapter defaults.\n 66|    #[serde(default)]\n 67|    pub over: DigestOverride,\n 68|}\n...\n 81|/// One endpoint's live digest state in the hub: the latest snapshot + version\n*82|/// (for pulls) and the set of live subscriber senders (for the delta-stream).\n 83|#[derive(Default)]\n 84|struct EndpointState {\n 85|    snapshot: Digest,\n 86|    version: u64,\n*87|    subscribers: Vec<Sender<DigestUpdate>>,\n 88|}\n 89|\n 90|/// The daemon's per-endpoint live-digest registry. The brain publishes; the\n*91|/// control channel reads + subscribes. Holds only the projected digest state —\n 92|/// the same ephemeral-projection category as the broker's accepted-socket\n 93|/// bookkeeping, not routing/registry/parse logic (the parse already happened in\n 94|/// the brain's engine).\n...\n 109|    /// snapshot to compute the [`DigestUpdate`] delta, fan that out to every live\n*110|    /// subscriber (pruning any disconnected `--follow` client), and return the\n 111|    /// stored snapshot + its version. An unchanged projection is a no-op: no\n 112|    /// version bump, no fan-out. The projection itself ([`project_endpoint_digest`])\n 113|    /// runs **outside** this lock — only the diff + publish are serialized.\n...\n 130|        st.snapshot = new.clone();\n*131|        st.subscribers.retain(|tx| tx.send(update.clone()).is_ok());\n 132|        (new, st.version)\n 133|    }\n 134|\n...\n 140|\n*141|    /// Register a subscriber for `id`, returning the current base snapshot+version\n 142|    /// (to apply later deltas against) and the receiver future changes arrive on.\n*143|    /// The base is always returned so a fresh subscriber has an apply anchor.\n*144|    fn subscribe(&self, id: &str) -> (Digest, u64, Receiver<DigestUpdate>) {\n 145|        let (tx, rx) = channel();\n 146|        let mut map = self.map.lock().unwrap();\n 147|        let st = map.entry(id.to_string()).or_default();\n*148|        st.subscribers.push(tx);\n 149|        (st.snapshot.clone(), st.version, rx)\n 150|    }\n 151|\n*152|    /// Number of live subscribers for `id` (tests / introspection).\n## dispatch.rs\n 48|//! every E2E test uses). The peek and the serve use **separate** connections:\n*49|//! the peek brain subscribes from 0 to read the first line and is dropped;\n*50|//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n 51|//! stream's event feed alone (no double-subscription interleave). Origin is\n 52|//! the table's `remote_id_hex` — handshake-proven, never payload\n 53|//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n...\n 257|    let mut brain = connect(broker_name)?;\n*258|    brain.net_stream_subscribe(stream_id, 0)?;\n 259|    let mut buf: Vec<u8> = Vec::new();\n 260|    loop {\n 261|        match brain.read_event()? {\n...\n 351|            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n*352|            // before serve_attach subscribes — never mid-serve, which would swallow\n 353|            // interleaved attach records) and `None` on the local path.\n 354|            let (endpoint, remote_session) = match line\n 355|                .get(\"endpoint_id\")\n...\n 424|            // origin stays handshake-proven. The serve applies the event\n*425|            // through the LOCAL transition host — echo gate, wake effects,\n 426|            // and shell cascade run on this node, where the instance lives.\n 427|            // [impl->REQ-INST-6]\n 428|            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n...\n 466|/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n*467|/// dormant). The rest edge's transition echo is gate-armed\n*468|/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n 469|/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n 470|/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n 471|/// calls serialized here).\n...\n 479|    let policy = RegistryGatePolicy::load();\n*480|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 481|        return DispatchOutcome::Failed(e.to_string());\n 482|    }\n 483|    let mut decoder = RegistryDecoder::new();\n...\n 564|        .unwrap_or_default();\n*565|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 566|        return DispatchOutcome::Failed(e.to_string());\n 567|    }\n 568|    let mut decoder = NotifDecoder::new();\n...\n 637|) -> DispatchOutcome {\n*638|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 639|        return DispatchOutcome::Failed(e.to_string());\n 640|    }\n 641|    let mut decoder = WanDecoder::new();\n## drivehub.rs\n 5|//! channel\" — the opaque control surface). An owner writes one drive frame for a\n*6|//! shell; the shell binary drains it with an ephemeral `api drive-poll --link`.\n 7|//! Unlike the durable command channel (spooled on the shell perch), a drive\n 8|//! frame is held in a **single in-memory slot per shell** and overwritten on\n 9|//! every write — a second write before the poll clobbers the first.\n...\n 55|/// matches the slot's stamp (no-replay-across-relink), and the slot is cleared\n*56|/// regardless (exactly-once drain).\n 57|pub const KIND_DRIVE_TAKE: &str = \"drive_take\";\n 58|/// Daemon→shell: the take reply (the pending frame, or absent ⇒ nothing to serve).\n 59|pub const KIND_DRIVE_RESULT: &str = \"drive_result\";\n...\n 149|    /// Take-and-clear the shell's slot. The slot is removed unconditionally\n*150|    /// (exactly-once drain); the frame is returned ONLY when `token` matches the\n 151|    /// slot's stamp — a frame written under a now-dead link generation is evicted\n 152|    /// without being served (no-replay-across-relink).\n 153|    // [impl->REQ-SHELL-3]\n...\n 337|            None,\n*338|            \"the frame is drained exactly once\"\n 339|        );\n 340|        assert!(!hub.has_slot(\"doyle\", \"mock-shell-0\"));\n 341|    }\n## effect.rs\n 5|//!\n*6|//! The brain restarts freely (ADR-0004): a routine self-update swaps it, a crash\n 7|//! kills it. Effects it drives cross the broker↔brain boundary — a PTY write, a\n 8|//! spool row, a registry update. A brain that crashes *around* one of those\n 9|//! effects must, on restart, neither duplicate it (a re-injected keystroke, a\n...\n 394|    /// closure, so a blocking `PtyWrite` (the operator's keystroke into a\n*395|    /// not-draining ConPTY/forkpty) parked the lock forever → the single-threaded\n 396|    /// dispatch could open no attaches (`spt rc` → \"brain IPC read deadline\"), and\n 397|    /// every keystroke paid two fsyncs serialized (the stutter). Now: reserve the\n 398|    /// key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the\n...\n 810|    // user `effect()` closure (and across two fsyncs). A PtyWrite effect that\n*811|    // blocks (a non-draining child's full PTY input buffer) therefore holds the\n 812|    // GLOBAL journal lock forever — so EVERY other journaled effect (another\n 813|    // keystroke, another attach's journaled input) wedges, and the single-\n 814|    // threaded dispatch can no longer service `spt rc` attaches. Both units below\n...\n 840|                entered_tx.send(()).expect(\"signal entered\");\n*841|                // Block inside the effect — the wedge a non-draining PTY induces.\n 842|                release_rx.recv().expect(\"wait for release\");\n 843|                Ok(())\n 844|            })\n## endpoint.rs\n 35|\n*36|/// The broker listener name (B1/B2): PTY hosting + gapless handoff.\n 37|pub fn broker_socket_name() -> String {\n 38|    format!(\"spt-daemon-broker-{}.sock\", home_tag())\n 39|}\n...\n 41|/// The digest-control listener name (B8): the channel `spt digest <id>` connects\n*42|/// to for a snapshot pull or a delta-stream subscribe (ADR-0008 / REQ-TERM-4).\n 43|pub fn digest_socket_name() -> String {\n 44|    format!(\"spt-daemon-digest-{}.sock\", home_tag())\n 45|}\n## frame.rs\n 3|//! This is the single most update-frequency-sensitive contract in spt-core\n*4|//! (KNOWN-HAZARDS 2.3): self-update (ADR-0004) swaps the brain routinely while\n 5|//! the broker persists, so **a newer brain must talk to an older broker**. Two\n 6|//! mechanisms make that hold by construction:\n 7|//!\n...\n 19|//!    rather than a silent misparse.\n*20|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 21|\n 22|use serde::{Deserialize, Serialize};\n 23|\n...\n 99|/// policy — kept in one place so the rule is auditable.\n*100|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 101|pub fn accept_hello(hello: &Hello, expected_peer: Role) -> Result<(), HandshakeError> {\n 102|    if hello.protocol_version < MIN_COMPATIBLE_VERSION {\n 103|        return Err(HandshakeError::IncompatibleVersion {\n...\n 151|\n*152|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 153|    // The load-bearing forward-compat property: a frame a *newer* brain emits —\n 154|    // carrying an unknown trailing field — must decode cleanly on an *older*\n 155|    // broker that has never heard of that field. We simulate the newer wire form\n...\n 174|\n*175|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 176|    // An unknown *kind* is not a decode error — the envelope parses and the\n 177|    // logic layer is what decides it cannot handle the kind.\n 178|    #[test]\n...\n 185|\n*186|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 187|    // A newer brain (higher protocol version, extra Hello fields) is accepted by\n 188|    // an older broker — the exact \"newer brain ↔ older broker\" direction 2.3\n 189|    // demands.\n...\n 201|\n*202|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 203|    // A peer below the compatibility floor is rejected loudly, not misparsed.\n 204|    #[test]\n 205|    fn handshake_rejects_below_floor() {\n...\n 215|\n*216|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT] role mismatch is a typed refusal.\n 217|    #[test]\n 218|    fn handshake_rejects_role_mismatch() {\n 219|        let hello = Hello::current(Role::Broker);\n## harnesshost.rs\n 56|/// \"post_spawn\"`); this only fills the `{session_id}` placeholder at spawn so the\n*57|/// command template resolves. A fresh create mints one; `--resume` supplies the\n 58|/// prior session id instead.\n 59|pub fn mint_session_id() -> String {\n 60|    let seed = spt_proto::identity::Identity::generate().seed();\n...\n 68|/// specific knowledge — used as the B2 defensive belt so a provisional never\n*69|/// leaks into a resume command (`claude -r <provisional>` hangs the harness at a\n*70|/// \"no sessions match\" picker; REQ-RESUME-HARNESS-SESSION-ID).\n 71|pub fn is_provisional_session_id(s: &str) -> bool {\n 72|    s.len() == 16\n 73|        && s.bytes()\n...\n 82|///\n*83|/// `is_resume`: select the NATIVE-resume role over the fresh one — when `true`\n*84|/// AND the adapter declares `[session.resume]`, the resume template is filled\n 85|/// (the harness runs e.g. `claude -r {session_id}` instead of a fresh\n 86|/// `claude --session-id {session_id}`, which would start a blank transcript). A\n*87|/// `true` with NO `[session.resume]` declared FALLS BACK to `[session.self]`\n 88|/// (full back-compat); a `false` always uses `[session.self]`.\n 89|// [impl->REQ-HOST-RUN-1]\n*90|// [impl->REQ-SESSION-RESUME-TEMPLATE]\n 91|pub fn prepare_harness_spawn(\n 92|    endpoint_id: &str,\n 93|    adapter_name: &str,\n...\n 95|    manifest: &Manifest,\n*96|    is_resume: bool,\n 97|    // The adapter install dir (record `source_dir`) — fills `{adapter_dir}` and\n 98|    // resolves the translation binary's program token against it (ADR-0029 /\n 99|    // REQ-INSTALL-11). `None` keeps PATH-only resolution + an unset `{adapter_dir}`.\n...\n 113|    }\n*114|    // Resume selects `[session.resume]` when declared, else falls back to\n*115|    // `[session.self]` (an adapter with no resume sibling behaves exactly as\n 116|    // today). A fresh bringup always uses `[session.self]`.\n*117|    let role = if is_resume {\n 118|        manifest\n 119|            .session\n*120|            .resume\n 121|            .as_ref()\n 122|            .or(manifest.session.self_.as_ref())\n 123|    } else {\n...\n 218|/// `session_id`: the spawn-time `{session_id}` fill — a provisional [`mint_session_id`]\n*219|/// for a fresh create, or the prior session for `--resume`.\n 220|///\n 221|/// `cwd`: the project-scoped working directory the harness PTY lands in\n 222|/// (REQ-HOST-RUN-2). `None` inherits the broker's cwd (the W1 bringup shortcut);\n...\n 225|///\n*226|/// `is_resume`: forwarded to [`prepare_harness_spawn`] — `true` selects the\n*227|/// adapter's `[session.resume]` native-resume command (REQ-SESSION-RESUME-TEMPLATE)\n 228|/// when declared, else falls back to `[session.self]`.\n 229|// [impl->REQ-HOST-RUN-1]\n 230|// [impl->REQ-HOST-RUN-2]\n*231|// [impl->REQ-SESSION-RESUME-TEMPLATE]\n 232|#[allow(clippy::too_many_arguments)] // bringup inputs (W3a added install_dir); a struct would obscure the call\n 233|pub fn launch_harness_brokered_in(\n 234|    broker_name: &str,\n...\n 239|    cwd: Option<&str>,\n*240|    is_resume: bool,\n 241|    // The adapter's install dir (W3a, ADR-0025) — recorded on the broker session\n\n[Showing lines 1-911 of 994 (50.0KB limit). Use :912 to continue]"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src, spt-core/crates/spt/src, spt-core/crates/spt-daemon/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":227,"fileCount":20,"files":["spt-core/crates/spt-daemon/src/applyhost.rs","spt-core/crates/spt-daemon/src/attach.rs","spt-core/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","spt-core/crates/spt-daemon/src/brain.rs","spt-core/crates/spt-daemon/src/brainproc.rs","spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/config.rs","spt-core/crates/spt-daemon/src/conn.rs","spt-core/crates/spt-daemon/src/crc_swap.rs","spt-core/crates/spt-daemon/src/daemon.rs","spt-core/crates/spt-daemon/src/deadline.rs","spt-core/crates/spt-daemon/src/digest.rs","spt-core/crates/spt-daemon/src/digesthub.rs","spt-core/crates/spt-daemon/src/dispatch.rs","spt-core/crates/spt-daemon/src/drivehub.rs","spt-core/crates/spt-daemon/src/effect.rs","spt-core/crates/spt-daemon/src/endpoint.rs","spt-core/crates/spt-daemon/src/frame.rs","spt-core/crates/spt-daemon/src/harnesshost.rs","spt-core/crates/spt-daemon/src/inject.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/applyhost.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/attach.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","count":7},{"path":"spt-core/crates/spt-daemon/src/brain.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/brainproc.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/config.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/conn.rs","count":3},{"path":"spt-core/crates/spt-daemon/src/crc_swap.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/daemon.rs","count":10},{"path":"spt-core/crates/spt-daemon/src/deadline.rs","count":4},{"path":"spt-core/crates/spt-daemon/src/digest.rs","count":8},{"path":"spt-core/crates/spt-daemon/src/digesthub.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/dispatch.rs","count":10},{"path":"spt-core/crates/spt-daemon/src/drivehub.rs","count":4},{"path":"spt-core/crates/spt-daemon/src/effect.rs","count":4},{"path":"spt-core/crates/spt-daemon/src/endpoint.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/frame.rs","count":8},{"path":"spt-core/crates/spt-daemon/src/harnesshost.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/inject.rs","count":6}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# spt-core/crates/spt-daemon/src/\n## applyhost.rs\n  23│//!    orchestration is post-M5; ADR-0004 §A).\n *24│//! 3. **Swap the binary**: the current executable steps aside\n  25│//!    (`<exe>.old-<ver>` — a running image survives its own rename on both\n  26│//!    OSes) and the verified artifact lands at the executable path\n  27│//!    atomically. A failed landing rolls the original back — never a\n *28│//!    half-swapped seat.\n  29│//! 4. **Trigger a brain-PROCESS restart** ([`Brain::request_brain_restart`],\n  30│//!    ADR-0018 D3-3): the broker signals its supervisor to cycle the brain\n *31│//!    child onto the just-swapped binary with `start-reason=update`. No hosted\n  32│//!    endpoint terminates or suspends (REQ-UPD-3, absolute for this class — the\n  33│//!    broker holds them all across the cycle). This replaces the old in-process\n *34│//!    `apply_brain_only` handoff, the regression that re-attached a CLI-side\n *35│//!    subscriber while the live brain kept running the OLD code.\n  36│//!\n  37│//! New code runs **immediately** — the supervised respawn lands on the new\n  38│//! executable, no manual bounce, no logon. Readiness-gated promotion +\n    │...\n  58│    /// exit-0 no-op (F-025). Short-circuits BEFORE the binary-aside rename, whose\n *59│    /// re-run on an already-swapped exe is the 'Access is denied (os error 5)'\n  60│    /// this fixes. `version`/`product_version` name what is already installed.\n  61│    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n  62│    AlreadyApplied {\n    │...\n  66│    /// The at-rest re-verification refused (tamper / expiry / key trust) —\n *67│    /// nothing swapped, the reason says why.\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    │...\n  76│    Quarantined { version: u64 },\n *77│    /// Swapped + brain-restart **triggered** — the new code is now **on trial**,\n  78│    /// not yet promoted (ADR-0018 Q7, D6-2): the supervisor promotes it on the\n  79│    /// brain's `ready` signal or auto-rolls-back on failure. `apply` returns\n  80│    /// provisional immediately (it cannot observe the asynchronous verdict — the\n    │...\n  90│    },\n *91│    /// Swapped the binary on disk with NO running daemon touched — a\n  92│    /// **daemonless** apply (REQ-UPDATE-ONE-SHOT-FINISH). No broker was booted to\n *93│    /// hand off to (the old-broker-boot-pre-swap wart) and no brain was cycled;\n  94│    /// the `AppliedPending` trial is recorded so the NEXT daemon start boots the\n  95│    /// new bytes and promotes it on the fresh brain's `ready`. `version` is the\n  96│    /// monotonic counter; `product_version` the human semver (empty pre-v0.3.2).\n    │...\n 103│\n*104│/// Either an early terminal apply outcome (nothing swaps) or the go-ahead to\n*105│/// swap, carrying the verified bytes. The shared verify/classify preamble both\n 106│/// [`apply_staged`] and [`apply_staged_daemonless`] run before they diverge on\n 107│/// whether a live broker is handed off to.\n 108│enum Prepared {\n*109│    /// A terminal outcome reached before any swap (NoUpdate / AlreadyApplied /\n 110│    /// Quarantined / Rejected / RefusedClass) — return it verbatim.\n 111│    Done(ApplyStagedOutcome),\n 112│    /// Verified brain-only bytes ready to land at the exe path.\n    │...\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 126│fn prepare_apply(cache: &ReleaseCache, release_keys: &Path) -> Result<Prepared, String> {\n    │...\n 153│    // applied image must NOT reach the binary-aside rename below — re-renaming an\n*154│    // already-swapped exe is today's 'Access is denied (os error 5)'. Short-circuit\n 155│    // to a friendly exit-0 no-op instead. Checked before verify/connect: an\n 156│    // already-applied node is up to date regardless of the staged bytes' state.\n 157│    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n    │...\n 182│            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses\n*183│            // — exactly the path that swapped a Linux ELF in as `spt.exe`.\n 184│            let here = current_platform();\n 185│            match cache.staged_platform() {\n 186│                Some(p) if p == here => {}\n    │...\n 247│\n*248│/// Swap the binary at `exe_path` (step aside → land → roll back on failure) and\n*249│/// record the open `AppliedPending` trial. Shared by the broker-handoff apply\n 250│/// and the daemonless apply — both land the SAME bytes and record the SAME trial\n 251│/// (the supervisor promotes it on the next brain's `ready`); they differ only in\n 252│/// whether a running brain is cycled afterwards.\n 253│// [impl->REQ-UPD-6]\n*254│fn swap_and_record(\n 255│    cache: &ReleaseCache,\n 256│    exe_path: &Path,\n 257│    version: u64,\n    │...\n 259│) -> Result<(), String> {\n*260│    // Swap the binary: step aside, land the artifact, roll back on failure.\n## attach.rs\n  39│//! A restarted target brain re-attaches the session from its output cursor,\n *40│//! resubscribes the stream from its net cursor, and re-drives journaled sends;\n *41│//! a restarted operator brain resubscribes and dedups rendered chunks by\n  42│//! `Output.seq`.\n  43│\n  44│use std::io;\n    │...\n  56│/// served viewport is a **driver** — attaching wakes the endpoint, the\n *57│/// viewport ending detaches it (active → dormant; the transition echo is\n  58│/// gate-armed for the endpoint's own pulse loop). Best-effort exactly like\n  59│/// [`mark_driven_by`]: a session served without a perch has no resting\n  60│/// record to move.\n    │...\n  85│\n *86│/// The role a serve loop resolved to, once the broker's [`SubscribeOutcome`]\n  87│/// arrives — gates wake (controller-only) and PTY input.\n  88│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n  89│enum ServeRole {\n    │...\n  95│\n *96│/// Minimum spacing between skip-to-live re-subscribes after a viewer eviction\n  97│/// (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). HARD constraint: an evicted viewer that is\n  98│/// hopelessly behind under a sustained flood will be re-evicted immediately after\n *99│/// each re-subscribe; without spacing that is an evict→resubscribe CPU spin. With\n 100│/// it, the operator sees an intermittent LIVE burst each interval (forward\n 101│/// progress), never a busy-loop. ~200ms = several bursts/sec, imperceptible lag.\n*102│const RESUBSCRIBE_INTERVAL: Duration = Duration::from_millis(200);\n 103│\n 104│/// The CONTROLLER fell behind the live ring further than the ring holds — the\n 105│/// dropped frames are GONE (`tail - delivered_through > cap_chunks`), so an\n    │...\n 109│/// DEFERRED to `REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND`.\n*110│// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n 111│#[derive(Debug)]\n 112│pub struct ControllerIrrecoverablyBehind {\n 113│    /// The frozen `delivered_through` floor the ring has already rolled past.\n    │...\n 129│/// Is `e` the brain's strict reject-gap fatal (`output gap: got seq N want M`)?\n*130│/// That is the only error the controller gap-resume recovers from; every other\n 131│/// error propagates unchanged.\n 132│fn is_output_gap(e: &io::Error) -> bool {\n 133│    e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"output gap\")\n    │...\n 139│/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n*140│/// bounded channel is full (a controller that falls behind its own echo under a\n 141│/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n 142│/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n 143│/// controller CANNOT snap-above like a viewer (B2: it is authoritative — it\n 144│/// advances `delivered_through`; skipping rolled frames would be a not-exactly-once\n*145│/// resume). So instead it RE-FETCHES: re-subscribe from\n*146│/// [`Brain::controller_resume_floor`] (`= delivered_through`, frozen at the drop by\n 147│/// `contiguous_advance`) and the broker replays the dropped frames from the ring.\n 148│///\n 149│/// Exactly-once holds IFF the ring still retains the floor (`tail - delivered_through\n 150│/// <= cap_chunks`). If it does not — the controller fell behind further than the\n*151│/// ring holds — the re-subscribe's first frame is STILL a forward gap (floor\n*152│/// unchanged across two resumes). That is [`ControllerIrrecoverablyBehind`]: the\n 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 158│pub fn read_controller_event_resuming(\n 159│    brain: &mut Brain,\n 160│    session_id: u64,\n    │...\n 162│) -> io::Result<BrokerEvent> {\n*163│    let mut last_resume_floor: Option<u64> = None;\n*164│    let mut swallow_resume_ack = false;\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## brain.rs\n  3│//! The brain holds all the volatile logic; it may be killed and restarted at\n *4│//! any moment (a routine self-update swaps it; a crash kills it). It connects to\n  5│//! the stable [`crate::broker::Broker`] over the versioned IPC, drives sessions\n  6│//! (spawn / input / resize / kill), and consumes their output. Two invariants\n  7│//! this type encodes:\n  8│//!\n *9│//! - **`gen_start = now()` on cold-start *and* handoff (KNOWN-HAZARDS 2.4).**\n 10│//!   The wall-clock generation stamp is taken fresh on every start. A handed-off\n*11│//!   brain rehydrates its resume cursor and generation *counter* from prior\n 12│//!   state, but its `gen_start` is `now()`, **never** the stale value from that\n 13│//!   state — a rehydrated `gen_start` would fire time-based discriminators on\n*14│//!   the new generation. [`Brain::handoff`] takes the prior state by value yet\n 15│//!   ignores its `gen_start_ms`.\n*16│//! - **Gapless, exactly-once resume.** The brain tracks `next_seq` — the next\n*17│//!   output sequence it still needs. On handoff it `subscribe`s from that cursor;\n 18│//!   the broker replays buffered output from there. [`Brain::read_event`] accepts\n 19│//!   only the contiguous next `seq` (advancing the cursor), silently drops an\n 20│//!   already-seen `seq` (a replay duplicate), and treats a forward gap as a hard\n   │...\n 40│    InputReq,\n*41│    KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,\n 42│    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,\n*43│    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq,\n*44│    SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,\n 45│    ViewerEvictedEvent,\n 46│    KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY, KIND_VIEWER_EVICTED,\n 47│    KIND_BRAIN_RESTARTED, KIND_DISPLACED, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL,\n 48│    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT, KIND_NET_DIAL_SUBMITTED,\n*49│    KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,\n 50│    KIND_NET_SENT,\n 51│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n 52│    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,\n*53│    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 54│    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n*55│    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n 56│};\n 57│use spt_net::net::attach::AttachIntent;\n 58│use crate::transport::{send_hello, LocalSocketTransport};\n   │...\n 68│\n*69│/// The brain's durable state across a handoff: enough to re-attach gaplessly and\n 70│/// advance the generation. `gen_start_ms` is recorded for observability but is\n*71│/// **deliberately not authoritative on restart** — [`Brain::handoff`] sets a\n 72│/// fresh `gen_start` from `now()` (2.4).\n 73│///\n 74│/// **Test-only continuity (restoration D4-3, ADR-0018 Q6).** This brain→brain\n   │...\n 76│/// respawns reconstructs continuity by querying the broker\n*77│/// ([`Brain::resume_sessions`] over the broker's cursor-of-record), never this\n*78│/// message. `BrainState` / [`Brain::handoff`] / [`Brain::snapshot`] stay `pub`\n 79│/// solely because the integration tests (separate test target — `#[cfg(test)]`\n*80│/// would hide them) still drive the handoff-frame shape directly; there is **no\n 81│/// non-test caller** (KNOWN-HAZARDS 2.4 grep-clean close-out).\n 82│#[derive(Debug, Clone)]\n 83│pub struct BrainState {\n   │...\n 85│    pub session_id: u64,\n*86│    /// The generation counter (incremented on each handoff).\n 87│    pub generation: u64,\n*88│    /// The next output sequence still needed (the resume cursor).\n 89│    pub next_seq: u64,\n 90│    /// The prior generation's wall-clock start (ignored as authoritative on\n*91│    /// handoff — kept only to demonstrate it is *not* carried forward).\n 92│    pub gen_start_ms: u64,\n 93│}\n 94│\n## brainproc.rs\n   4│//! ADR-0004 specified the broker/brain split as two **processes**; production\n  *5│//! drifted to an in-process thread (`daemon.rs`), so `spt update apply` swaps\n   6│//! the binary on disk but never restarts the running code (the regression\n   7│//! ADR-0018 corrects). D1 restores the process boundary as a *true minimal\n   8│//! skeleton*: `spt daemon run` is the broker process and spawns a supervised\n    │...\n  21│//! supervisor respawns it from the executable path, which on a real update is\n *22│//! the swapped binary.\n  23│// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n  24│\n  25│use std::io;\n    │...\n  72│/// never a parse failure that would brick the brain before it rehydrates.\n *73│// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n  74│#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n  75│pub enum StartReason {\n  76│    /// The broker's first brain spawn this broker lifetime.\n    │...\n  79│    Crash,\n *80│    /// A respawn the broker itself initiated for a binary swap (wired in D3-3).\n  81│    Update,\n  82│}\n  83│\n    │...\n 108│/// its next wait tick, kills the current brain child, and respawns it with\n*109│/// `start-reason=update` onto the (freshly-swapped) binary. Shared (`Arc`)\n 110│/// between the broker serve path and the supervisor — both run in the broker\n 111│/// process. This is the seam that makes a routine `apply` actually run new code,\n 112│/// the pillar the in-process-collapse regression silently unrealized.\n    │...\n 133│    pub fn take(&self) -> bool {\n*134│        self.requested.swap(false, Ordering::SeqCst)\n 135│    }\n 136│}\n 137│\n    │...\n 142│/// returns `Err`, exiting the process so the broker-side supervisor respawns it\n*143│/// from the (possibly swapped) binary.\n 144│///\n 145│/// **D2-1 — net consumers run here.** The inbound dispatcher + outbound peer\n 146│/// pump are pure IPC clients (each `Brain::cold_start`s its own connection; no\n 147│/// shared `Arc<Broker>`), so they belong with the restartable brain, not the\n*148│/// broker — a brain respawn re-spawns them from the swapped binary. They are\n 149│/// meaningful only once the broker-owned NetHost is up, so we gate on the\n 150│/// existing `net-status` verb (which also serves as the broker-liveness probe)\n 151│/// and start them exactly once, on the first `enabled` reply. That single gate\n    │...\n 180│\n*181│    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\n*182│    // continuity from the broker — re-attach EVERY hosted session in resume mode\n 183│    // from its delivered cursor, the production replacement for the retired\n*184│    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\n 185│    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\n 186│    // so this is a no-op now and forward-correct when daemon-hosted sessions land\n 187│    // (the live-agent adapter). A failure here is non-fatal — the brain still\n    │...\n 191│    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\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 204│            eprintln!(\n*205│                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\n 206│                ids.len()\n 207│            )\n 208│        }\n## broker.rs\n   9│//!\n *10│//! ## Gapless handoff (B2 — the load-bearing invariant)\n  11│//!\n  12│//! A brain may be killed and restarted at any moment (a routine self-update, a\n  13│//! crash). The broker — and every hosted child — must survive that untouched,\n    │...\n  18│//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n *19│//!   in a bounded ring. The current subscriber (a brain connection) receives\n *20│//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n  21│//!   reach the brain in strict `seq` order with no interleave.\n *22│//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n  23│//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n  24│//!   live. The brain dedups by `seq`. Output produced *while no brain was\n *25│//!   attached* sits in the ring and is replayed on the next subscribe — so a\n  26│//!   brain restart loses nothing.\n  27│//!\n  28│//! Sessions are therefore **broker-owned and outlive any one connection**: a\n *29│//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n  30│//! output log persist. A session leaves the table only when its child exits\n  31│//! (reaped by the exit waiter) or is explicitly killed.\n  32│// [impl->REQ-DAEMON-2]\n    │...\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  46│use spt_net::net::endpoint::EndpointAddr;\n    │...\n  55│    evicted_envelope, net_dialed_envelope, output_envelope,\n *56│    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n *57│    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n *58│    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n  59│    NetStreamsReply, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\n *60│    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,\n  61│    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT,\n *62│    KIND_NET_DIAL_SUBMITTED, KIND_NET_PRESENCE_SUBSCRIBE,\n  63│    KIND_NET_SENT,\n  64│    KIND_ADAPTER_APPLY, KIND_APPLIED,\n  65│    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\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_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  71│use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n  72│use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n    │...\n  90│/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n *91│/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n  92│/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n  93│/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n  94│/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n    │...\n 105│/// the live stream) or a `Disconnected` one (its writer thread ended after a\n*106│/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n 107│/// is the pure decision the fan-out applies under the log lock.\n 108│// [impl->REQ-HAZARD-VIEWER-ISOLATION]\n 109│fn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n    │...\n 112│\n*113│/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n 114│/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n 115│/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n*116│/// only its own queue — never the drain. When the queue overflows the sink is\n 117│/// dropped (the `tx` closes → the writer ends) and removed from the map.\n 118│struct ViewerSink {\n 119│    /// The bounded live-frame queue to this viewer's writer thread.\n    │...\n 138│/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n*139│/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\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    │...\n 145│\n*146│/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n## config.rs\n 30│/// liveness and signs off (KNOWN-HAZARDS 1.1). Sized to cover a transient recovery\n*31│/// — a binary handoff relaunch or a brief stale poll — without delaying a real\n 32│/// teardown for long. Configurable per machine via `crash_grace_ms`.\n 33│pub const DEFAULT_CRASH_GRACE: Duration = Duration::from_secs(2);\n 34│\n## conn.rs\n 176│    fn poison_and_cancel(&self) {\n*177│        if !self.poisoned.swap(true, Ordering::AcqRel) {\n 178│            // First poison of this conn: loud, once — the observable fact the\n 179│            // field-acceptance harness (blackhole-controller.ps1) watches for.\n 180│            eprintln!(\n    │...\n 214│    // guard's early `return` becomes the function tail — needless_return\n*215│    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n 216│    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\n 217│    #[cfg_attr(unix, allow(clippy::needless_return))]\n 218│    fn sever(&self) {\n*219│        if self.severed.swap(true, Ordering::AcqRel) {\n 220│            return;\n 221│        }\n 222│        #[cfg(windows)]\n## crc_swap.rs\n  *1│//! W3b — content-hash-gated atomic per-file swap for a live adapter update\n   2│//! (ADR-0025, REQ-ADAPTER-LIVE-UPDATE).\n   3│//!\n   4│//! The legacy `spt adapter update` path does `remove_dir_all(install_dir)` then a\n    │...\n   6│//! the moment a single install-dir binary is still running (the field bug). This\n  *7│//! module replaces that with a **content-diff swap**: extract the staged archive\n   8│//! to a temp tree, replace in the install dir **only** the files whose content\n   9│//! hash differs (so identical files — and their still-running binaries — are\n  10│//! never touched), and commit via **stage-all-`.new`-then-one-tight-rename-loop**\n *11│//! so a mid-swap failure never strands the install half-new (doyle W3 gate,\n  12│//! binding partial-failure refinement).\n  13│//!\n  14│//! CRC here is plain content hashing (reuse [`crate::release::sha256_hex`], no new\n    │...\n  20│\n *21│/// One planned file replacement discovered by [`plan_crc_swap`].\n  22│#[derive(Debug, Clone, PartialEq, Eq)]\n *23│pub struct PlannedSwap {\n  24│    /// Path relative to the tree root (for diagnostics / determinism).\n  25│    pub rel: PathBuf,\n  26│    /// The staged source file (`<staging>/<rel>`).\n    │...\n  37│// [impl->REQ-ADAPTER-LIVE-UPDATE]\n *38│pub fn plan_crc_swap(staging: &Path, install_dir: &Path) -> std::io::Result<Vec<PlannedSwap>> {\n  39│    let mut plan = Vec::new();\n  40│    collect_diffs(staging, staging, install_dir, &mut plan)?;\n  41│    plan.sort_by(|a, b| a.rel.cmp(&b.rel));\n    │...\n  50│    install_dir: &Path,\n *51│    plan: &mut Vec<PlannedSwap>,\n  52│) -> std::io::Result<()> {\n  53│    for entry in std::fs::read_dir(dir)? {\n  54│        let entry = entry?;\n    │...\n  70│        if differs {\n *71│            plan.push(PlannedSwap {\n  72│                rel,\n  73│                staged: path,\n  74│                target,\n    │...\n  80│\n *81│/// Apply a [`plan_crc_swap`] result with a never-strand commit protocol:\n  82│///\n  83│/// 0. **GC** — sweep any stale `<target>.old*` litter left by a PRIOR bricked run\n  84│///    (best-effort; a still-mapped one stays but is uniquely named).\n    │...\n  90│///\n *91│/// **C1 (REQ-CRC-SWAP-OLD-DISPLACE): displace, never replace.** The prior impl\n  92│/// renamed the original to a FIXED `<target>.old`. On a live box a surviving\n  93│/// endpoint child still image-maps the old binary, so Phase-3's `remove_file(.old)`\n  94│/// silently fails and the `.old` lingers; the NEXT update's `rename(target, .old)`\n    │...\n 106│// [impl->REQ-ADAPTER-LIVE-UPDATE]\n*107│// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*108│pub fn apply_crc_swap(plan: &[PlannedSwap]) -> std::io::Result<()> {\n*109│    apply_crc_swap_with(plan, &|from, to| std::fs::rename(from, to))\n 110│}\n 111│\n*112│/// [`apply_crc_swap`] over an injectable `rename` so the never-strand rollback is\n 113│/// unit-testable deterministically: the displace fix removes every rename-OVER-an-\n 114│/// existing-file (fresh names only), so no filesystem fixture can force a mid-loop\n 115│/// commit failure any more — the seam supplies one instead. Production passes\n 116│/// `std::fs::rename`.\n*117│// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*118│fn apply_crc_swap_with(\n*119│    plan: &[PlannedSwap],\n 120│    rename: &dyn Fn(&Path, &Path) -> std::io::Result<()>,\n 121│) -> std::io::Result<()> {\n 122│    // Phase 0 — GC stale `.old*` litter from a prior (bricked) run, but ONLY when\n    │...\n 130│    // repopulates the target, and the NEXT run (target now present) sweeps it.\n*131│    // [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*132│    for swap in plan {\n*133│        if swap.target.exists() {\n## daemon.rs\n  221│        // the digest-control channel serves `spt digest <id>` snapshot pulls +\n *222│        // delta-stream subscribes against it. Served on a background thread like\n  223│        // the broker. The per-session feed that *populates* the hub is driven by\n  224│        // the brain (the parse is logic — ADR-0004 §B), wired as daemon-hosted\n  225│        // sessions are spawned through the broker; a bare daemon serves the\n     │...\n  282│        // `BrainRestart` signal is wired into the broker so the `KIND_BRAIN_RESTART`\n *283│        // verb (sent by `spt update apply` after the binary swap) cycles the brain\n  284│        // onto the new binary with `start-reason=update`. The stop flag is held\n  285│        // for symmetry, never raised (the daemon runs until killed). The brain\n  286│        // child inherits this process's already-unelevated token.\n     │...\n  303│                Arc::clone(&reaper),\n *304│                // The DRAINED promotion gate (REQ-UPDATE-PROMOTE-DRAINED) reads this\n  305│                // broker's live control-plane truth in-process; None on a bind-failed\n  306│                // daemon degrades the gate to readiness-only.\n  307│                broker_handle.clone(),\n     │...\n  978│/// defense (REQ-HAZARD-DETACHED-DAEMON-STDIO). The decision is EXACTLY \"is stderr an\n *979│/// undrained PIPE\": a pipe nobody drains is the whole hazard — its write BLOCKS the\n  980│/// daemon once full. Every OTHER sink is safe and MUST survive: a FILE (`2>run.log`,\n  981│/// AND every int test's `Stdio::from(file)` brain-log capture) is a disk handle that\n  982│/// never blocks; a CONSOLE scrolls; a NULL/absent handle (the DETACHED_PROCESS rungs)\n  983│/// already discards. So we DELIBERATELY do NOT gate on \"no console\" (`GetConsoleWindow`):\n *984│/// a CREATE_NO_WINDOW daemon has no console window yet a perfectly-drained FILE stderr\n  985│/// — nulling it would blank the capture (the test-harness brain-log assertions) for\n  986│/// ZERO safety gain, since a file never blocks. `stderr_file_type` is the raw\n  987│/// `GetFileType(stderr)`. Pure so both CI legs unit-test it. // [impl->REQ-HAZARD-DETACHED-DAEMON-STDIO]\n     │...\n  992│/// Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the 3 std handles when\n *993│/// this `daemon run`'s stderr is an undrained PIPE (`GetFileType == FILE_TYPE_PIPE`)\n  994│/// — the only sink that would BLOCK the daemon when it fills — so stdio degrades to a\n  995│/// safe discard REGARDLESS of whether the launching rung remembered `--detached`.\n  996│/// Unlike [`detach_console`] it does NOT `FreeConsole` (that is the `--detached`\n     │...\n 1000│/// FOREGROUND-CAPTURE NOTE: a piped foreground diagnostic (`spt daemon run 2>&1 | tee\n*1001│/// log`) IS discarded (its stderr is a pipe) — the undrained-pipe defense outweighs\n 1002│/// pipe-tee ergonomics. The SANCTIONED foreground-capture form is a FILE redirect\n 1003│/// (`spt daemon run 2>run.log`): a disk handle is not a pipe, so it survives (this is\n 1004│/// exactly why the int-test brain-log capture is safe). Called unconditionally at\n     │...\n 1237│    // NULL/absent handle (DETACHED_PROCESS rungs) all SURVIVE — nulling them is\n*1238│    // wrong (blanks a drained capture) and pointless (they never block). RED-FIRST:\n 1239│    // returning `true` unconditionally makes the non-pipe cases go red — which is\n 1240│    // exactly the mass int-test brain-log-blanking regression the file-type gate avoids.\n 1241│    #[test]\n     │...\n 1245│        const FILE_TYPE_CHAR: u32 = 0x0002;\n*1246│        assert!(should_null_std_handles(FILE_TYPE_PIPE), \"an undrained pipe blocks ⇒ null\");\n 1247│        assert!(\n 1248│            !should_null_std_handles(FILE_TYPE_DISK),\n 1249│            \"a FILE redirect (2>run.log / the int-test brain-log) is disk ⇒ survives\"\n     │...\n 1261│    // [unit->REQ-DAEMON-9] the net self-heal backoff ladder doubles each retry\n*1262│    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n 1263│    // 30s poll, never an ever-growing or zero-length spin.\n 1264│    #[test]\n 1265│    fn net_retry_backoff_doubles_then_caps() {\n## deadline.rs\n   3│//! Phase-significant periodic timing (the pulse loop) and one-shot deadlines\n  *4│//! (alarms) must survive a brain swap: the supervisor respawns a fresh brain\n   5│//! (`Brain::cold_start`) and the outgoing brain is gone before the new one starts\n   6│//! (D3-3), so continuity cannot ride a brain→brain frame — it must come from the\n   7│//! persistent side (disk), exactly as D4 moved session continuity to the broker.\n    │...\n 166│    /// - **skips past `now`** so a long stall — a `pulse_tick` that outran the\n*167│    ///   interval, or the real killer, a laptop suspend/resume with the daemon\n 168│    ///   resident — collapses to **exactly one** catch-up fire instead of N rapid\n 169│    ///   back-to-back fires. (`next_after(deadline)` alone steps a single grid per\n 170│    ///   fire, so a deadline N grids behind `now` would fire N times in a tight\n 171│    ///   loop — the exact storm the loop-entry `next_fire(now)` collapse promises\n*172│    ///   to avoid; this composes with that collapse so a suspend/resume fires once\n 173│    ///   total.)\n 174│    pub fn advance_after_fire(&self, fired_ms: u64, now_ms: u64) -> u64 {\n 175│        self.next_after(fired_ms.max(now_ms))\n    │...\n 242│        assert_eq!(a.advance_after_fire(1_300, 1_300), 1_400);\n*243│        // Stall (suspend/resume or an overrunning tick): now sits 10+ grids past\n 244│        // the fired point → ONE catch-up fire at the next grid AFTER now, NOT a\n 245│        // storm of one-grid steps. fire@1_000, now=2_050 → 2_100 (not 1_100).\n 246│        assert_eq!(a.advance_after_fire(1_000, 2_050), 2_100);\n## digest.rs\n   8│//! on every output chunk. The digest is now a **projection of the same normalized\n  *9│//! history the echo-commune consumes** — so there is nothing to feed: a snapshot\n  10│//! pull (or a `digest-entry` push) **projects on demand** from the endpoint's\n  11│//! records via [`project_endpoint_digest`], and the hub publishes the result.\n  12│//! Render surfaces read the PTY (the raw byte ring, unchanged); content surfaces\n    │...\n  31│//! ([`crate::digesthub::DigestHub::project_and_publish`]) to produce a\n *32│//! [`DigestUpdate`]: `from` is where the changed turns begin, and the subscriber\n  33│//! applies it by **truncating its view to `from`, then appending `turns`**. The\n  34│//! delta-stream **contract** ships; its deltas are driven by pull / digest-entry\n  35│//! publishes (autonomous file-watch freshness is deferred to the consuming\n    │...\n  59│/// `[digest]` presentation defaults that **any consumer may override** at\n *60│/// pull/subscribe). Every field is optional — unset falls through to the\n  61│/// adapter default, then the spt-core fallback ([`DigestConfig::default`]).\n  62│#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]\n  63│pub struct DigestOverride {\n    │...\n 100│\n*101│/// A structured digest change to push to a subscriber. Sent only when the digest\n 102│/// actually changed (so the stream carries deltas, not heartbeats).\n 103│#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n 104│pub struct DigestUpdate {\n    │...\n 189│        // Collapse cross-generation replay dupes on the RAW span, before the\n*190│        // two-origin merge + window fold, so a checkpoint/resume row surfaces once\n 191│        // (under its live generation) not twice. [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n 192│        supersede_cross_generation(spanned)\n 193│    } else {\n    │...\n 321│/// text, tool)`, the doyle ruling's supersede key (docs/W3-DIGEST-GENERATION-UNION-RULING.md).\n*322│/// A checkpoint/resume replays an ancestor generation's transcript verbatim, so a\n 323│/// replayed row is BYTE-identical on exactly these fields; NUL-joined so distinct\n 324│/// field boundaries never collide.\n 325│// [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n    │...\n 341│/// Collapse cross-generation replay dupes in a spanned activity backbone (flynn\n*342│/// digest gen-union, doyle Option-1 Supersede). A checkpoint/resume makes the\n 343│/// harness REPLAY the prior generation's transcript into the new session file, so\n 344│/// one logical row surfaces under two `seq`-generations — `(gen_old, localseq)`\n 345│/// and `(gen_new, localseq)` — and exact-seq consumer dedup can't collapse them.\n    │...\n 681│\n*682│    // [unit->REQ-DIGEST-GENERATION-SUPERSEDE] a checkpoint/resume replays gen A's\n 683│    // rows into gen B at the same localseq → the span carries each logical row\n 684│    // twice (two ordinals). Supersede keeps ONE, under B's (newer) generation, and\n 685│    // the surviving seq is B's.\n## digesthub.rs\n   4│//! The hub holds the latest projected snapshot per endpoint and fans every change\n  *5│//! out to live subscribers. The digest is **projected on demand** from the\n   6│//! endpoint's normalized records ([`project_endpoint_digest`]) — there is no live\n   7│//! feed; a pull or a `digest-entry` push triggers a projection, and\n   8│//! [`DigestHub::project_and_publish`] diffs it against the prior snapshot to\n    │...\n  14│//! - **delta-stream** — `spt endpoint digest <id> --follow` ([`follow`])\n *15│//!   subscribes, receives a base snapshot, then **only the changes**\n  16│//!   ([`DigestUpdate`]s) as pulls / `digest-entry` pushes re-project. The stream\n  17│//!   contract ships; autonomous file-watch freshness is deferred to the consuming\n  18│//!   frontend milestone (CONTEXT.md \"session digest\").\n    │...\n  45│pub const KIND_DIGEST_RESULT: &str = \"digest_result\";\n *46│/// Client→daemon: subscribe to an endpoint's delta-stream from a version floor.\n *47│pub const KIND_DIGEST_SUBSCRIBE: &str = \"digest_subscribe\";\n  48│/// Daemon→client: one structured digest change (the streamed delta / base).\n  49│pub const KIND_DIGEST_DELTA: &str = \"digest_delta\";\n  50│/// Client→daemon: re-project an endpoint now and publish any change to live\n *51│/// subscribers — the push-driven delta after an `api digest-entry` append (no\n *52│/// reply; the delta reaches subscribers via the fan-out, REQ-TERM-4).\n  53│pub const KIND_DIGEST_REPROJECT: &str = \"digest_reproject\";\n  54│\n *55│/// `digest_snapshot` / `digest_subscribe` request payload.\n  56│#[derive(Debug, Clone, Serialize, Deserialize)]\n  57│pub struct DigestReq {\n  58│    /// The (local) endpoint id whose digest to read.\n  59│    pub id: String,\n *60│    /// Subscribe floor: only stream changes with `version > from_version`. The\n *61│    /// base snapshot is sent regardless so a fresh subscriber has an apply base.\n  62│    #[serde(default)]\n  63│    pub from_version: u64,\n  64│    /// Consumer presentation override (ADR-0019) — overrides the adapter's\n *65│    /// `[digest]` defaults for this pull/subscribe. Empty ⇒ adapter defaults.\n  66│    #[serde(default)]\n  67│    pub over: DigestOverride,\n  68│}\n    │...\n  81│/// One endpoint's live digest state in the hub: the latest snapshot + version\n *82│/// (for pulls) and the set of live subscriber senders (for the delta-stream).\n  83│#[derive(Default)]\n  84│struct EndpointState {\n  85│    snapshot: Digest,\n  86│    version: u64,\n *87│    subscribers: Vec<Sender<DigestUpdate>>,\n  88│}\n  89│\n  90│/// The daemon's per-endpoint live-digest registry. The brain publishes; the\n *91│/// control channel reads + subscribes. Holds only the projected digest state —\n  92│/// the same ephemeral-projection category as the broker's accepted-socket\n  93│/// bookkeeping, not routing/registry/parse logic (the parse already happened in\n  94│/// the brain's engine).\n    │...\n 109│    /// snapshot to compute the [`DigestUpdate`] delta, fan that out to every live\n*110│    /// subscriber (pruning any disconnected `--follow` client), and return the\n 111│    /// stored snapshot + its version. An unchanged projection is a no-op: no\n 112│    /// version bump, no fan-out. The projection itself ([`project_endpoint_digest`])\n 113│    /// runs **outside** this lock — only the diff + publish are serialized.\n    │...\n 130│        st.snapshot = new.clone();\n*131│        st.subscribers.retain(|tx| tx.send(update.clone()).is_ok());\n 132│        (new, st.version)\n 133│    }\n 134│\n    │...\n 140│\n*141│    /// Register a subscriber for `id`, returning the current base snapshot+version\n 142│    /// (to apply later deltas against) and the receiver future changes arrive on.\n*143│    /// The base is always returned so a fresh subscriber has an apply anchor.\n*144│    fn subscribe(&self, id: &str) -> (Digest, u64, Receiver<DigestUpdate>) {\n 145│        let (tx, rx) = channel();\n 146│        let mut map = self.map.lock().unwrap();\n 147│        let st = map.entry(id.to_string()).or_default();\n*148│        st.subscribers.push(tx);\n 149│        (st.snapshot.clone(), st.version, rx)\n 150│    }\n 151│\n*152│    /// Number of live subscribers for `id` (tests / introspection).\n## dispatch.rs\n  48│//! every E2E test uses). The peek and the serve use **separate** connections:\n *49│//! the peek brain subscribes from 0 to read the first line and is dropped;\n *50│//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n  51│//! stream's event feed alone (no double-subscription interleave). Origin is\n  52│//! the table's `remote_id_hex` — handshake-proven, never payload\n  53│//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n    │...\n 257│    let mut brain = connect(broker_name)?;\n*258│    brain.net_stream_subscribe(stream_id, 0)?;\n 259│    let mut buf: Vec<u8> = Vec::new();\n 260│    loop {\n 261│        match brain.read_event()? {\n    │...\n 351│            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n*352│            // before serve_attach subscribes — never mid-serve, which would swallow\n 353│            // interleaved attach records) and `None` on the local path.\n 354│            let (endpoint, remote_session) = match line\n 355│                .get(\"endpoint_id\")\n    │...\n 424│            // origin stays handshake-proven. The serve applies the event\n*425│            // through the LOCAL transition host — echo gate, wake effects,\n 426│            // and shell cascade run on this node, where the instance lives.\n 427│            // [impl->REQ-INST-6]\n 428│            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n    │...\n 466│/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n*467│/// dormant). The rest edge's transition echo is gate-armed\n*468│/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n 469│/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n 470│/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n 471│/// calls serialized here).\n    │...\n 479│    let policy = RegistryGatePolicy::load();\n*480│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 481│        return DispatchOutcome::Failed(e.to_string());\n 482│    }\n 483│    let mut decoder = RegistryDecoder::new();\n    │...\n 564│        .unwrap_or_default();\n*565│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 566│        return DispatchOutcome::Failed(e.to_string());\n 567│    }\n 568│    let mut decoder = NotifDecoder::new();\n    │...\n 637│) -> DispatchOutcome {\n*638│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 639│        return DispatchOutcome::Failed(e.to_string());\n 640│    }\n 641│    let mut decoder = WanDecoder::new();\n## drivehub.rs\n   5│//! channel\" — the opaque control surface). An owner writes one drive frame for a\n  *6│//! shell; the shell binary drains it with an ephemeral `api drive-poll --link`.\n   7│//! Unlike the durable command channel (spooled on the shell perch), a drive\n   8│//! frame is held in a **single in-memory slot per shell** and overwritten on\n   9│//! every write — a second write before the poll clobbers the first.\n    │...\n  55│/// matches the slot's stamp (no-replay-across-relink), and the slot is cleared\n *56│/// regardless (exactly-once drain).\n  57│pub const KIND_DRIVE_TAKE: &str = \"drive_take\";\n  58│/// Daemon→shell: the take reply (the pending frame, or absent ⇒ nothing to serve).\n  59│pub const KIND_DRIVE_RESULT: &str = \"drive_result\";\n    │...\n 149│    /// Take-and-clear the shell's slot. The slot is removed unconditionally\n*150│    /// (exactly-once drain); the frame is returned ONLY when `token` matches the\n 151│    /// slot's stamp — a frame written under a now-dead link generation is evicted\n 152│    /// without being served (no-replay-across-relink).\n 153│    // [impl->REQ-SHELL-3]\n    │...\n 337│            None,\n*338│            \"the frame is drained exactly once\"\n 339│        );\n 340│        assert!(!hub.has_slot(\"doyle\", \"mock-shell-0\"));\n 341│    }\n## effect.rs\n   5│//!\n  *6│//! The brain restarts freely (ADR-0004): a routine self-update swaps it, a crash\n   7│//! kills it. Effects it drives cross the broker↔brain boundary — a PTY write, a\n   8│//! spool row, a registry update. A brain that crashes *around* one of those\n   9│//! effects must, on restart, neither duplicate it (a re-injected keystroke, a\n    │...\n 394│    /// closure, so a blocking `PtyWrite` (the operator's keystroke into a\n*395│    /// not-draining ConPTY/forkpty) parked the lock forever → the single-threaded\n 396│    /// dispatch could open no attaches (`spt rc` → \"brain IPC read deadline\"), and\n 397│    /// every keystroke paid two fsyncs serialized (the stutter). Now: reserve the\n 398│    /// key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the\n    │...\n 810│    // user `effect()` closure (and across two fsyncs). A PtyWrite effect that\n*811│    // blocks (a non-draining child's full PTY input buffer) therefore holds the\n 812│    // GLOBAL journal lock forever — so EVERY other journaled effect (another\n 813│    // keystroke, another attach's journaled input) wedges, and the single-\n 814│    // threaded dispatch can no longer service `spt rc` attaches. Both units below\n    │...\n 840│                entered_tx.send(()).expect(\"signal entered\");\n*841│                // Block inside the effect — the wedge a non-draining PTY induces.\n 842│                release_rx.recv().expect(\"wait for release\");\n 843│                Ok(())\n 844│            })\n## endpoint.rs\n 35│\n*36│/// The broker listener name (B1/B2): PTY hosting + gapless handoff.\n 37│pub fn broker_socket_name() -> String {\n 38│    format!(\"spt-daemon-broker-{}.sock\", home_tag())\n 39│}\n   │...\n 41│/// The digest-control listener name (B8): the channel `spt digest <id>` connects\n*42│/// to for a snapshot pull or a delta-stream subscribe (ADR-0008 / REQ-TERM-4).\n 43│pub fn digest_socket_name() -> String {\n 44│    format!(\"spt-daemon-digest-{}.sock\", home_tag())\n 45│}\n## frame.rs\n   3│//! This is the single most update-frequency-sensitive contract in spt-core\n  *4│//! (KNOWN-HAZARDS 2.3): self-update (ADR-0004) swaps the brain routinely while\n   5│//! the broker persists, so **a newer brain must talk to an older broker**. Two\n   6│//! mechanisms make that hold by construction:\n   7│//!\n    │...\n  19│//!    rather than a silent misparse.\n *20│// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n  21│\n  22│use serde::{Deserialize, Serialize};\n  23│\n    │...\n  99│/// policy — kept in one place so the rule is auditable.\n*100│// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 101│pub fn accept_hello(hello: &Hello, expected_peer: Role) -> Result<(), HandshakeError> {\n 102│    if hello.protocol_version < MIN_COMPATIBLE_VERSION {\n 103│        return Err(HandshakeError::IncompatibleVersion {\n    │...\n 151│\n*152│    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 153│    // The load-bearing forward-compat property: a frame a *newer* brain emits —\n 154│    // carrying an unknown trailing field — must decode cleanly on an *older*\n 155│    // broker that has never heard of that field. We simulate the newer wire form\n    │...\n 174│\n*175│    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 176│    // An unknown *kind* is not a decode error — the envelope parses and the\n 177│    // logic layer is what decides it cannot handle the kind.\n 178│    #[test]\n    │...\n 185│\n*186│    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 187│    // A newer brain (higher protocol version, extra Hello fields) is accepted by\n 188│    // an older broker — the exact \"newer brain ↔ older broker\" direction 2.3\n 189│    // demands.\n    │...\n 201│\n*202│    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 203│    // A peer below the compatibility floor is rejected loudly, not misparsed.\n 204│    #[test]\n 205│    fn handshake_rejects_below_floor() {\n    │...\n 215│\n*216│    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT] role mismatch is a typed refusal.\n 217│    #[test]\n 218│    fn handshake_rejects_role_mismatch() {\n 219│        let hello = Hello::current(Role::Broker);\n## harnesshost.rs\n  56│/// \"post_spawn\"`); this only fills the `{session_id}` placeholder at spawn so the\n *57│/// command template resolves. A fresh create mints one; `--resume` supplies the\n  58│/// prior session id instead.\n  59│pub fn mint_session_id() -> String {\n  60│    let seed = spt_proto::identity::Identity::generate().seed();\n    │...\n  68│/// specific knowledge — used as the B2 defensive belt so a provisional never\n *69│/// leaks into a resume command (`claude -r <provisional>` hangs the harness at a\n *70│/// \"no sessions match\" picker; REQ-RESUME-HARNESS-SESSION-ID).\n  71│pub fn is_provisional_session_id(s: &str) -> bool {\n  72│    s.len() == 16\n  73│        && s.bytes()\n    │...\n  82│///\n *83│/// `is_resume`: select the NATIVE-resume role over the fresh one — when `true`\n *84│/// AND the adapter declares `[session.resume]`, the resume template is filled\n  85│/// (the harness runs e.g. `claude -r {session_id}` instead of a fresh\n  86│/// `claude --session-id {session_id}`, which would start a blank transcript). A\n *87│/// `true` with NO `[session.resume]` declared FALLS BACK to `[session.self]`\n  88│/// (full back-compat); a `false` always uses `[session.self]`.\n  89│// [impl->REQ-HOST-RUN-1]\n *90│// [impl->REQ-SESSION-RESUME-TEMPLATE]\n  91│pub fn prepare_harness_spawn(\n  92│    endpoint_id: &str,\n  93│    adapter_name: &str,\n    │...\n  95│    manifest: &Manifest,\n *96│    is_resume: bool,\n  97│    // The adapter install dir (record `source_dir`) — fills `{adapter_dir}` and\n  98│    // resolves the translation binary's program token against it (ADR-0029 /\n  99│    // REQ-INSTALL-11). `None` keeps PATH-only resolution + an unset `{adapter_dir}`.\n    │...\n 113│    }\n*114│    // Resume selects `[session.resume]` when declared, else falls back to\n*115│    // `[session.self]` (an adapter with no resume sibling behaves exactly as\n 116│    // today). A fresh bringup always uses `[session.self]`.\n*117│    let role = if is_resume {\n 118│        manifest\n 119│            .session\n*120│            .resume\n 121│            .as_ref()\n 122│            .or(manifest.session.self_.as_ref())\n 123│    } else {\n    │...\n 218│/// `session_id`: the spawn-time `{session_id}` fill — a provisional [`mint_session_id`]\n*219│/// for a fresh create, or the prior session for `--resume`.\n 220│///\n 221│/// `cwd`: the project-scoped working directory the harness PTY lands in\n 222│/// (REQ-HOST-RUN-2). `None` inherits the broker's cwd (the W1 bringup shortcut);\n    │...\n 225│///\n*226│/// `is_resume`: forwarded to [`prepare_harness_spawn`] — `true` selects the\n*227│/// adapter's `[session.resume]` native-resume command (REQ-SESSION-RESUME-TEMPLATE)\n 228│/// when declared, else falls back to `[session.self]`.\n 229│// [impl->REQ-HOST-RUN-1]\n 230│// [impl->REQ-HOST-RUN-2]\n*231│// [impl->REQ-SESSION-RESUME-TEMPLATE]\n 232│#[allow(clippy::too_many_arguments)] // bringup inputs (W3a added install_dir); a struct would obscure the call\n 233│pub fn launch_harness_brokered_in(\n 234│    broker_name: &str,\n    │...\n 239│    cwd: Option<&str>,\n*240│    is_resume: bool,\n 241│    // The adapter's install dir (W3a, ADR-0025) — recorded on the broker session\n 242│    // so a live adapter update can target this endpoint's resident children.\n 243│    install_dir: Option<&str>,\n    │...\n 256│        manifest,\n*257│        is_resume,\n 258│        install_dir,\n 259│        node_label,\n 260│    )?;\n    │...\n 309│\n*310│    // [unit->REQ-RESUME-HARNESS-SESSION-ID] B2 belt: a spt-core spawn provisional\n 311│    // (mint_session_id — 16 lowercase hex) is recognized as provisional; a real\n 312│    // harness session id (Claude's dashed UUID, or any non-16-hex) is NOT — so the\n*313│    // resume resolver never mistakes a real session for a leakable provisional.\n 314│    #[test]\n 315│    fn provisional_session_id_recognizes_own_mint_only() {\n 316│        // A freshly minted id round-trips as provisional.\n## inject.rs\n  92│\n *93│/// Drain an already-idle spt-hosted endpoint's pending spool through the inject leg\n  94│/// (W5, REQ-IDLE-PARKED-DELIVERY) — the shared core behind BOTH the send-time\n  95│/// already-idle path AND the pulse-tick re-offer belt, so a message QUEUED to a session\n  96│/// that is ALREADY parked idle (no ACTIVE→IDLE edge will ever come) is delivered without\n *97│/// an operator poke. Identical mechanic to the F-023 idle-EDGE drain: CLAIM the pending\n  98│/// rows (audited, idle-inject leg), offer each through [`try_spt_hosted_inject`]\n  99│/// (`native=false` — the genuine idle window), and on the first miss (no live\n 100│/// translation binary, or the broker still views the endpoint ACTIVE) RELEASE the row\n    │...\n 107│// [impl->REQ-HAZARD-DELIVERY-STARVATION]\n*108│pub fn drain_idle_spool(id: &str, owlery: &Path) -> usize {\n 109│    if !is_spt_hosted_no_relay(id, owlery) {\n 110│        return 0;\n 111│    }\n    │...\n 114│    // Resting gate (REQ-INST-6 / KH 1.4): hold the deferred (`active_only`) rows while\n*115│    // the owner rests — the non-deferred set always drains on the idle window.\n 116│    let include_deferred = !crate::resting::deferred_held(&perch_path);\n 117│    // [impl->REQ-SPOOL-TAKE-AUDIT] idle-inject leg — stamp the claim's provenance.\n 118│    let audit = spt_store::spool::TakerAudit::new(\n    │...\n 141│    if delivered > 0 {\n*142│        eprintln!(\"IDLE_PARKED_DRAIN:{id}: injected {delivered} parked message(s) via translation binary\");\n 143│    }\n 144│    if !release_ids.is_empty() {\n 145│        let _ = spt_store::spool::release_at(&perch_path, &release_ids);\n    │...\n 150│        if n > 0 {\n*151│            eprintln!(\"IDLE_PARKED_DRAIN:{id}: evaporated {n} ephemeral message(s) (no carrier)\");\n 152│        }\n 153│    }\n 154│    delivered\n\n## bin/\n### xlate_choreo_fixture.rs\n  27│//!     REQ-INJECT-MULTILINE-INTEGRITY): a single big typed payload the broker's\n *28│//!     settle-gate + chunked write must deliver byte-complete (head AND tail intact)\n  29│//!     across a LOOP of injects.\n  30│//!\n  31│//! Privacy assertion support (doyle gate #1): every stdin line we receive is\n    │...\n 107│            // `ctrl+s` (XOFF, 0x13) emitted above PAUSED terminal output. On a\n*108│            // forkpty PTY (IXON honored) that pause STRANDS every subsequent echo —\n 109│            // the deadline-flushed operator keystrokes (op_flushed), observed via the\n*110│            // child's echo, never reach the output ring → the g2 op_flushed read\n 111│            // times out (forkpty-ONLY; Windows ConPTY ignores XOFF, so g2 passed\n 112│            // there and the strand stayed hidden until the forkpty matrix). The happy path already\n 113│            // emits this XON before `{commit}`; an adapter that ABORTS mid-sequence\n    │...\n 121│        emit(&mut out, r#\"{\"key\":\"ctrl+s\"}\"#);\n*122│        // XON (`ctrl+q` = 0x11): resume the terminal output flow the `ctrl+s` (XOFF)\n*123│        // chord paused, so the payload's echo flushes deterministically on a ConPTY\n*124│        // (the int test observes byte-receipt via that echo). A real stash/restore\n 125│        // adapter restores flow at the end of its sequence just the same; this keeps\n 126│        // the `ctrl+s` chord (and its real 0x13 mapping + floor exercise) intact while\n 127│        // making the payload's arrival observable, per the test-specialist's latitude.\n    │...\n 142│/// would false-pass). ~12 lines of ~70 cols ≈ 850 B — larger than the field's ~322 B\n*143│/// truncation and many settle-chunks wide, yet it fits inside a 24-row screen grid so a\n 144│/// cold-attach repaint (ADR-0031 = current screen) still carries the HEAD (a taller\n 145│/// payload scrolls HEADSTART off the visible grid).\n 146│fn big_multiline_payload() -> String {","truncation":{"content":"# spt-core/crates/spt-daemon/src/\n## applyhost.rs\n 23|//!    orchestration is post-M5; ADR-0004 §A).\n*24|//! 3. **Swap the binary**: the current executable steps aside\n 25|//!    (`<exe>.old-<ver>` — a running image survives its own rename on both\n 26|//!    OSes) and the verified artifact lands at the executable path\n 27|//!    atomically. A failed landing rolls the original back — never a\n*28|//!    half-swapped seat.\n 29|//! 4. **Trigger a brain-PROCESS restart** ([`Brain::request_brain_restart`],\n 30|//!    ADR-0018 D3-3): the broker signals its supervisor to cycle the brain\n*31|//!    child onto the just-swapped binary with `start-reason=update`. No hosted\n 32|//!    endpoint terminates or suspends (REQ-UPD-3, absolute for this class — the\n 33|//!    broker holds them all across the cycle). This replaces the old in-process\n*34|//!    `apply_brain_only` handoff, the regression that re-attached a CLI-side\n*35|//!    subscriber while the live brain kept running the OLD code.\n 36|//!\n 37|//! New code runs **immediately** — the supervised respawn lands on the new\n 38|//! executable, no manual bounce, no logon. Readiness-gated promotion +\n...\n 58|    /// exit-0 no-op (F-025). Short-circuits BEFORE the binary-aside rename, whose\n*59|    /// re-run on an already-swapped exe is the 'Access is denied (os error 5)'\n 60|    /// this fixes. `version`/`product_version` name what is already installed.\n 61|    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n 62|    AlreadyApplied {\n...\n 66|    /// The at-rest re-verification refused (tamper / expiry / key trust) —\n*67|    /// nothing swapped, the reason says why.\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...\n 76|    Quarantined { version: u64 },\n*77|    /// Swapped + brain-restart **triggered** — the new code is now **on trial**,\n 78|    /// not yet promoted (ADR-0018 Q7, D6-2): the supervisor promotes it on the\n 79|    /// brain's `ready` signal or auto-rolls-back on failure. `apply` returns\n 80|    /// provisional immediately (it cannot observe the asynchronous verdict — the\n...\n 90|    },\n*91|    /// Swapped the binary on disk with NO running daemon touched — a\n 92|    /// **daemonless** apply (REQ-UPDATE-ONE-SHOT-FINISH). No broker was booted to\n*93|    /// hand off to (the old-broker-boot-pre-swap wart) and no brain was cycled;\n 94|    /// the `AppliedPending` trial is recorded so the NEXT daemon start boots the\n 95|    /// new bytes and promotes it on the fresh brain's `ready`. `version` is the\n 96|    /// monotonic counter; `product_version` the human semver (empty pre-v0.3.2).\n...\n 103|\n*104|/// Either an early terminal apply outcome (nothing swaps) or the go-ahead to\n*105|/// swap, carrying the verified bytes. The shared verify/classify preamble both\n 106|/// [`apply_staged`] and [`apply_staged_daemonless`] run before they diverge on\n 107|/// whether a live broker is handed off to.\n 108|enum Prepared {\n*109|    /// A terminal outcome reached before any swap (NoUpdate / AlreadyApplied /\n 110|    /// Quarantined / Rejected / RefusedClass) — return it verbatim.\n 111|    Done(ApplyStagedOutcome),\n 112|    /// Verified brain-only bytes ready to land at the exe path.\n...\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 126|fn prepare_apply(cache: &ReleaseCache, release_keys: &Path) -> Result<Prepared, String> {\n...\n 153|    // applied image must NOT reach the binary-aside rename below — re-renaming an\n*154|    // already-swapped exe is today's 'Access is denied (os error 5)'. Short-circuit\n 155|    // to a friendly exit-0 no-op instead. Checked before verify/connect: an\n 156|    // already-applied node is up to date regardless of the staged bytes' state.\n 157|    // [impl->REQ-UPDATE-APPLY-ALREADY-APPLIED]\n...\n 182|            // pre-v0.3.2 stage) or one stamped for another OS fail-safe refuses\n*183|            // — exactly the path that swapped a Linux ELF in as `spt.exe`.\n 184|            let here = current_platform();\n 185|            match cache.staged_platform() {\n 186|                Some(p) if p == here => {}\n...\n 247|\n*248|/// Swap the binary at `exe_path` (step aside → land → roll back on failure) and\n*249|/// record the open `AppliedPending` trial. Shared by the broker-handoff apply\n 250|/// and the daemonless apply — both land the SAME bytes and record the SAME trial\n 251|/// (the supervisor promotes it on the next brain's `ready`); they differ only in\n 252|/// whether a running brain is cycled afterwards.\n 253|// [impl->REQ-UPD-6]\n*254|fn swap_and_record(\n 255|    cache: &ReleaseCache,\n 256|    exe_path: &Path,\n 257|    version: u64,\n...\n 259|) -> Result<(), String> {\n*260|    // Swap the binary: step aside, land the artifact, roll back on failure.\n## attach.rs\n 39|//! A restarted target brain re-attaches the session from its output cursor,\n*40|//! resubscribes the stream from its net cursor, and re-drives journaled sends;\n*41|//! a restarted operator brain resubscribes and dedups rendered chunks by\n 42|//! `Output.seq`.\n 43|\n 44|use std::io;\n...\n 56|/// served viewport is a **driver** — attaching wakes the endpoint, the\n*57|/// viewport ending detaches it (active → dormant; the transition echo is\n 58|/// gate-armed for the endpoint's own pulse loop). Best-effort exactly like\n 59|/// [`mark_driven_by`]: a session served without a perch has no resting\n 60|/// record to move.\n...\n 85|\n*86|/// The role a serve loop resolved to, once the broker's [`SubscribeOutcome`]\n 87|/// arrives — gates wake (controller-only) and PTY input.\n 88|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 89|enum ServeRole {\n...\n 95|\n*96|/// Minimum spacing between skip-to-live re-subscribes after a viewer eviction\n 97|/// (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). HARD constraint: an evicted viewer that is\n 98|/// hopelessly behind under a sustained flood will be re-evicted immediately after\n*99|/// each re-subscribe; without spacing that is an evict→resubscribe CPU spin. With\n 100|/// it, the operator sees an intermittent LIVE burst each interval (forward\n 101|/// progress), never a busy-loop. ~200ms = several bursts/sec, imperceptible lag.\n*102|const RESUBSCRIBE_INTERVAL: Duration = Duration::from_millis(200);\n 103|\n 104|/// The CONTROLLER fell behind the live ring further than the ring holds — the\n 105|/// dropped frames are GONE (`tail - delivered_through > cap_chunks`), so an\n...\n 109|/// DEFERRED to `REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND`.\n*110|// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n 111|#[derive(Debug)]\n 112|pub struct ControllerIrrecoverablyBehind {\n 113|    /// The frozen `delivered_through` floor the ring has already rolled past.\n...\n 129|/// Is `e` the brain's strict reject-gap fatal (`output gap: got seq N want M`)?\n*130|/// That is the only error the controller gap-resume recovers from; every other\n 131|/// error propagates unchanged.\n 132|fn is_output_gap(e: &io::Error) -> bool {\n 133|    e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"output gap\")\n...\n 139|/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n*140|/// bounded channel is full (a controller that falls behind its own echo under a\n 141|/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n 142|/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n 143|/// controller CANNOT snap-above like a viewer (B2: it is authoritative — it\n 144|/// advances `delivered_through`; skipping rolled frames would be a not-exactly-once\n*145|/// resume). So instead it RE-FETCHES: re-subscribe from\n*146|/// [`Brain::controller_resume_floor`] (`= delivered_through`, frozen at the drop by\n 147|/// `contiguous_advance`) and the broker replays the dropped frames from the ring.\n 148|///\n 149|/// Exactly-once holds IFF the ring still retains the floor (`tail - delivered_through\n 150|/// <= cap_chunks`). If it does not — the controller fell behind further than the\n*151|/// ring holds — the re-subscribe's first frame is STILL a forward gap (floor\n*152|/// unchanged across two resumes). That is [`ControllerIrrecoverablyBehind`]: the\n 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 158|pub fn read_controller_event_resuming(\n 159|    brain: &mut Brain,\n 160|    session_id: u64,\n...\n 162|) -> io::Result<BrokerEvent> {\n*163|    let mut last_resume_floor: Option<u64> = None;\n*164|    let mut swallow_resume_ack = false;\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## brain.rs\n 3|//! The brain holds all the volatile logic; it may be killed and restarted at\n*4|//! any moment (a routine self-update swaps it; a crash kills it). It connects to\n 5|//! the stable [`crate::broker::Broker`] over the versioned IPC, drives sessions\n 6|//! (spawn / input / resize / kill), and consumes their output. Two invariants\n 7|//! this type encodes:\n 8|//!\n*9|//! - **`gen_start = now()` on cold-start *and* handoff (KNOWN-HAZARDS 2.4).**\n 10|//!   The wall-clock generation stamp is taken fresh on every start. A handed-off\n*11|//!   brain rehydrates its resume cursor and generation *counter* from prior\n 12|//!   state, but its `gen_start` is `now()`, **never** the stale value from that\n 13|//!   state — a rehydrated `gen_start` would fire time-based discriminators on\n*14|//!   the new generation. [`Brain::handoff`] takes the prior state by value yet\n 15|//!   ignores its `gen_start_ms`.\n*16|//! - **Gapless, exactly-once resume.** The brain tracks `next_seq` — the next\n*17|//!   output sequence it still needs. On handoff it `subscribe`s from that cursor;\n 18|//!   the broker replays buffered output from there. [`Brain::read_event`] accepts\n 19|//!   only the contiguous next `seq` (advancing the cursor), silently drops an\n 20|//!   already-seen `seq` (a replay duplicate), and treats a forward gap as a hard\n...\n 40|    InputReq,\n*41|    KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,\n 42|    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,\n*43|    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq,\n*44|    SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,\n 45|    ViewerEvictedEvent,\n 46|    KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY, KIND_VIEWER_EVICTED,\n 47|    KIND_BRAIN_RESTARTED, KIND_DISPLACED, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL,\n 48|    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT, KIND_NET_DIAL_SUBMITTED,\n*49|    KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,\n 50|    KIND_NET_SENT,\n 51|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n 52|    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,\n*53|    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 54|    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n*55|    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n 56|};\n 57|use spt_net::net::attach::AttachIntent;\n 58|use crate::transport::{send_hello, LocalSocketTransport};\n...\n 68|\n*69|/// The brain's durable state across a handoff: enough to re-attach gaplessly and\n 70|/// advance the generation. `gen_start_ms` is recorded for observability but is\n*71|/// **deliberately not authoritative on restart** — [`Brain::handoff`] sets a\n 72|/// fresh `gen_start` from `now()` (2.4).\n 73|///\n 74|/// **Test-only continuity (restoration D4-3, ADR-0018 Q6).** This brain→brain\n...\n 76|/// respawns reconstructs continuity by querying the broker\n*77|/// ([`Brain::resume_sessions`] over the broker's cursor-of-record), never this\n*78|/// message. `BrainState` / [`Brain::handoff`] / [`Brain::snapshot`] stay `pub`\n 79|/// solely because the integration tests (separate test target — `#[cfg(test)]`\n*80|/// would hide them) still drive the handoff-frame shape directly; there is **no\n 81|/// non-test caller** (KNOWN-HAZARDS 2.4 grep-clean close-out).\n 82|#[derive(Debug, Clone)]\n 83|pub struct BrainState {\n...\n 85|    pub session_id: u64,\n*86|    /// The generation counter (incremented on each handoff).\n 87|    pub generation: u64,\n*88|    /// The next output sequence still needed (the resume cursor).\n 89|    pub next_seq: u64,\n 90|    /// The prior generation's wall-clock start (ignored as authoritative on\n*91|    /// handoff — kept only to demonstrate it is *not* carried forward).\n 92|    pub gen_start_ms: u64,\n 93|}\n 94|\n## brainproc.rs\n 4|//! ADR-0004 specified the broker/brain split as two **processes**; production\n*5|//! drifted to an in-process thread (`daemon.rs`), so `spt update apply` swaps\n 6|//! the binary on disk but never restarts the running code (the regression\n 7|//! ADR-0018 corrects). D1 restores the process boundary as a *true minimal\n 8|//! skeleton*: `spt daemon run` is the broker process and spawns a supervised\n...\n 21|//! supervisor respawns it from the executable path, which on a real update is\n*22|//! the swapped binary.\n 23|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 24|\n 25|use std::io;\n...\n 72|/// never a parse failure that would brick the brain before it rehydrates.\n*73|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 74|#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n 75|pub enum StartReason {\n 76|    /// The broker's first brain spawn this broker lifetime.\n...\n 79|    Crash,\n*80|    /// A respawn the broker itself initiated for a binary swap (wired in D3-3).\n 81|    Update,\n 82|}\n 83|\n...\n 108|/// its next wait tick, kills the current brain child, and respawns it with\n*109|/// `start-reason=update` onto the (freshly-swapped) binary. Shared (`Arc`)\n 110|/// between the broker serve path and the supervisor — both run in the broker\n 111|/// process. This is the seam that makes a routine `apply` actually run new code,\n 112|/// the pillar the in-process-collapse regression silently unrealized.\n...\n 133|    pub fn take(&self) -> bool {\n*134|        self.requested.swap(false, Ordering::SeqCst)\n 135|    }\n 136|}\n 137|\n...\n 142|/// returns `Err`, exiting the process so the broker-side supervisor respawns it\n*143|/// from the (possibly swapped) binary.\n 144|///\n 145|/// **D2-1 — net consumers run here.** The inbound dispatcher + outbound peer\n 146|/// pump are pure IPC clients (each `Brain::cold_start`s its own connection; no\n 147|/// shared `Arc<Broker>`), so they belong with the restartable brain, not the\n*148|/// broker — a brain respawn re-spawns them from the swapped binary. They are\n 149|/// meaningful only once the broker-owned NetHost is up, so we gate on the\n 150|/// existing `net-status` verb (which also serves as the broker-liveness probe)\n 151|/// and start them exactly once, on the first `enabled` reply. That single gate\n...\n 180|\n*181|    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\n*182|    // continuity from the broker — re-attach EVERY hosted session in resume mode\n 183|    // from its delivered cursor, the production replacement for the retired\n*184|    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\n 185|    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\n 186|    // so this is a no-op now and forward-correct when daemon-hosted sessions land\n 187|    // (the live-agent adapter). A failure here is non-fatal — the brain still\n...\n 191|    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\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 204|            eprintln!(\n*205|                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\n 206|                ids.len()\n 207|            )\n 208|        }\n## broker.rs\n 9|//!\n*10|//! ## Gapless handoff (B2 — the load-bearing invariant)\n 11|//!\n 12|//! A brain may be killed and restarted at any moment (a routine self-update, a\n 13|//! crash). The broker — and every hosted child — must survive that untouched,\n...\n 18|//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n*19|//!   in a bounded ring. The current subscriber (a brain connection) receives\n*20|//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n 21|//!   reach the brain in strict `seq` order with no interleave.\n*22|//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n 23|//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n 24|//!   live. The brain dedups by `seq`. Output produced *while no brain was\n*25|//!   attached* sits in the ring and is replayed on the next subscribe — so a\n 26|//!   brain restart loses nothing.\n 27|//!\n 28|//! Sessions are therefore **broker-owned and outlive any one connection**: a\n*29|//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n 30|//! output log persist. A session leaves the table only when its child exits\n 31|//! (reaped by the exit waiter) or is explicitly killed.\n 32|// [impl->REQ-DAEMON-2]\n...\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 46|use spt_net::net::endpoint::EndpointAddr;\n...\n 55|    evicted_envelope, net_dialed_envelope, output_envelope,\n*56|    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n*57|    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n*58|    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n 59|    NetStreamsReply, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\n*60|    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,\n 61|    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT,\n*62|    KIND_NET_DIAL_SUBMITTED, KIND_NET_PRESENCE_SUBSCRIBE,\n 63|    KIND_NET_SENT,\n 64|    KIND_ADAPTER_APPLY, KIND_APPLIED,\n 65|    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\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_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 71|use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n 72|use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n...\n 90|/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n*91|/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n 92|/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n 93|/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n 94|/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n...\n 105|/// the live stream) or a `Disconnected` one (its writer thread ended after a\n*106|/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n 107|/// is the pure decision the fan-out applies under the log lock.\n 108|// [impl->REQ-HAZARD-VIEWER-ISOLATION]\n 109|fn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n...\n 112|\n*113|/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n 114|/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n 115|/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n*116|/// only its own queue — never the drain. When the queue overflows the sink is\n 117|/// dropped (the `tx` closes → the writer ends) and removed from the map.\n 118|struct ViewerSink {\n 119|    /// The bounded live-frame queue to this viewer's writer thread.\n...\n 138|/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n*139|/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\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...\n 145|\n*146|/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n## config.rs\n 30|/// liveness and signs off (KNOWN-HAZARDS 1.1). Sized to cover a transient recovery\n*31|/// — a binary handoff relaunch or a brief stale poll — without delaying a real\n 32|/// teardown for long. Configurable per machine via `crash_grace_ms`.\n 33|pub const DEFAULT_CRASH_GRACE: Duration = Duration::from_secs(2);\n 34|\n## conn.rs\n 176|    fn poison_and_cancel(&self) {\n*177|        if !self.poisoned.swap(true, Ordering::AcqRel) {\n 178|            // First poison of this conn: loud, once — the observable fact the\n 179|            // field-acceptance harness (blackhole-controller.ps1) watches for.\n 180|            eprintln!(\n...\n 214|    // guard's early `return` becomes the function tail — needless_return\n*215|    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n 216|    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\n 217|    #[cfg_attr(unix, allow(clippy::needless_return))]\n 218|    fn sever(&self) {\n*219|        if self.severed.swap(true, Ordering::AcqRel) {\n 220|            return;\n 221|        }\n 222|        #[cfg(windows)]\n## crc_swap.rs\n*1|//! W3b — content-hash-gated atomic per-file swap for a live adapter update\n 2|//! (ADR-0025, REQ-ADAPTER-LIVE-UPDATE).\n 3|//!\n 4|//! The legacy `spt adapter update` path does `remove_dir_all(install_dir)` then a\n...\n 6|//! the moment a single install-dir binary is still running (the field bug). This\n*7|//! module replaces that with a **content-diff swap**: extract the staged archive\n 8|//! to a temp tree, replace in the install dir **only** the files whose content\n 9|//! hash differs (so identical files — and their still-running binaries — are\n 10|//! never touched), and commit via **stage-all-`.new`-then-one-tight-rename-loop**\n*11|//! so a mid-swap failure never strands the install half-new (doyle W3 gate,\n 12|//! binding partial-failure refinement).\n 13|//!\n 14|//! CRC here is plain content hashing (reuse [`crate::release::sha256_hex`], no new\n...\n 20|\n*21|/// One planned file replacement discovered by [`plan_crc_swap`].\n 22|#[derive(Debug, Clone, PartialEq, Eq)]\n*23|pub struct PlannedSwap {\n 24|    /// Path relative to the tree root (for diagnostics / determinism).\n 25|    pub rel: PathBuf,\n 26|    /// The staged source file (`<staging>/<rel>`).\n...\n 37|// [impl->REQ-ADAPTER-LIVE-UPDATE]\n*38|pub fn plan_crc_swap(staging: &Path, install_dir: &Path) -> std::io::Result<Vec<PlannedSwap>> {\n 39|    let mut plan = Vec::new();\n 40|    collect_diffs(staging, staging, install_dir, &mut plan)?;\n 41|    plan.sort_by(|a, b| a.rel.cmp(&b.rel));\n...\n 50|    install_dir: &Path,\n*51|    plan: &mut Vec<PlannedSwap>,\n 52|) -> std::io::Result<()> {\n 53|    for entry in std::fs::read_dir(dir)? {\n 54|        let entry = entry?;\n...\n 70|        if differs {\n*71|            plan.push(PlannedSwap {\n 72|                rel,\n 73|                staged: path,\n 74|                target,\n...\n 80|\n*81|/// Apply a [`plan_crc_swap`] result with a never-strand commit protocol:\n 82|///\n 83|/// 0. **GC** — sweep any stale `<target>.old*` litter left by a PRIOR bricked run\n 84|///    (best-effort; a still-mapped one stays but is uniquely named).\n...\n 90|///\n*91|/// **C1 (REQ-CRC-SWAP-OLD-DISPLACE): displace, never replace.** The prior impl\n 92|/// renamed the original to a FIXED `<target>.old`. On a live box a surviving\n 93|/// endpoint child still image-maps the old binary, so Phase-3's `remove_file(.old)`\n 94|/// silently fails and the `.old` lingers; the NEXT update's `rename(target, .old)`\n...\n 106|// [impl->REQ-ADAPTER-LIVE-UPDATE]\n*107|// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*108|pub fn apply_crc_swap(plan: &[PlannedSwap]) -> std::io::Result<()> {\n*109|    apply_crc_swap_with(plan, &|from, to| std::fs::rename(from, to))\n 110|}\n 111|\n*112|/// [`apply_crc_swap`] over an injectable `rename` so the never-strand rollback is\n 113|/// unit-testable deterministically: the displace fix removes every rename-OVER-an-\n 114|/// existing-file (fresh names only), so no filesystem fixture can force a mid-loop\n 115|/// commit failure any more — the seam supplies one instead. Production passes\n 116|/// `std::fs::rename`.\n*117|// [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*118|fn apply_crc_swap_with(\n*119|    plan: &[PlannedSwap],\n 120|    rename: &dyn Fn(&Path, &Path) -> std::io::Result<()>,\n 121|) -> std::io::Result<()> {\n 122|    // Phase 0 — GC stale `.old*` litter from a prior (bricked) run, but ONLY when\n...\n 130|    // repopulates the target, and the NEXT run (target now present) sweeps it.\n*131|    // [impl->REQ-CRC-SWAP-OLD-DISPLACE]\n*132|    for swap in plan {\n*133|        if swap.target.exists() {\n## daemon.rs\n 221|        // the digest-control channel serves `spt digest <id>` snapshot pulls +\n*222|        // delta-stream subscribes against it. Served on a background thread like\n 223|        // the broker. The per-session feed that *populates* the hub is driven by\n 224|        // the brain (the parse is logic — ADR-0004 §B), wired as daemon-hosted\n 225|        // sessions are spawned through the broker; a bare daemon serves the\n...\n 282|        // `BrainRestart` signal is wired into the broker so the `KIND_BRAIN_RESTART`\n*283|        // verb (sent by `spt update apply` after the binary swap) cycles the brain\n 284|        // onto the new binary with `start-reason=update`. The stop flag is held\n 285|        // for symmetry, never raised (the daemon runs until killed). The brain\n 286|        // child inherits this process's already-unelevated token.\n...\n 303|                Arc::clone(&reaper),\n*304|                // The DRAINED promotion gate (REQ-UPDATE-PROMOTE-DRAINED) reads this\n 305|                // broker's live control-plane truth in-process; None on a bind-failed\n 306|                // daemon degrades the gate to readiness-only.\n 307|                broker_handle.clone(),\n...\n 978|/// defense (REQ-HAZARD-DETACHED-DAEMON-STDIO). The decision is EXACTLY \"is stderr an\n*979|/// undrained PIPE\": a pipe nobody drains is the whole hazard — its write BLOCKS the\n 980|/// daemon once full. Every OTHER sink is safe and MUST survive: a FILE (`2>run.log`,\n 981|/// AND every int test's `Stdio::from(file)` brain-log capture) is a disk handle that\n 982|/// never blocks; a CONSOLE scrolls; a NULL/absent handle (the DETACHED_PROCESS rungs)\n 983|/// already discards. So we DELIBERATELY do NOT gate on \"no console\" (`GetConsoleWindow`):\n*984|/// a CREATE_NO_WINDOW daemon has no console window yet a perfectly-drained FILE stderr\n 985|/// — nulling it would blank the capture (the test-harness brain-log assertions) for\n 986|/// ZERO safety gain, since a file never blocks. `stderr_file_type` is the raw\n 987|/// `GetFileType(stderr)`. Pure so both CI legs unit-test it. // [impl->REQ-HAZARD-DETACHED-DAEMON-STDIO]\n...\n 992|/// Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the 3 std handles when\n*993|/// this `daemon run`'s stderr is an undrained PIPE (`GetFileType == FILE_TYPE_PIPE`)\n 994|/// — the only sink that would BLOCK the daemon when it fills — so stdio degrades to a\n 995|/// safe discard REGARDLESS of whether the launching rung remembered `--detached`.\n 996|/// Unlike [`detach_console`] it does NOT `FreeConsole` (that is the `--detached`\n...\n 1000|/// FOREGROUND-CAPTURE NOTE: a piped foreground diagnostic (`spt daemon run 2>&1 | tee\n*1001|/// log`) IS discarded (its stderr is a pipe) — the undrained-pipe defense outweighs\n 1002|/// pipe-tee ergonomics. The SANCTIONED foreground-capture form is a FILE redirect\n 1003|/// (`spt daemon run 2>run.log`): a disk handle is not a pipe, so it survives (this is\n 1004|/// exactly why the int-test brain-log capture is safe). Called unconditionally at\n...\n 1237|    // NULL/absent handle (DETACHED_PROCESS rungs) all SURVIVE — nulling them is\n*1238|    // wrong (blanks a drained capture) and pointless (they never block). RED-FIRST:\n 1239|    // returning `true` unconditionally makes the non-pipe cases go red — which is\n 1240|    // exactly the mass int-test brain-log-blanking regression the file-type gate avoids.\n 1241|    #[test]\n...\n 1245|        const FILE_TYPE_CHAR: u32 = 0x0002;\n*1246|        assert!(should_null_std_handles(FILE_TYPE_PIPE), \"an undrained pipe blocks ⇒ null\");\n 1247|        assert!(\n 1248|            !should_null_std_handles(FILE_TYPE_DISK),\n 1249|            \"a FILE redirect (2>run.log / the int-test brain-log) is disk ⇒ survives\"\n...\n 1261|    // [unit->REQ-DAEMON-9] the net self-heal backoff ladder doubles each retry\n*1262|    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n 1263|    // 30s poll, never an ever-growing or zero-length spin.\n 1264|    #[test]\n 1265|    fn net_retry_backoff_doubles_then_caps() {\n## deadline.rs\n 3|//! Phase-significant periodic timing (the pulse loop) and one-shot deadlines\n*4|//! (alarms) must survive a brain swap: the supervisor respawns a fresh brain\n 5|//! (`Brain::cold_start`) and the outgoing brain is gone before the new one starts\n 6|//! (D3-3), so continuity cannot ride a brain→brain frame — it must come from the\n 7|//! persistent side (disk), exactly as D4 moved session continuity to the broker.\n...\n 166|    /// - **skips past `now`** so a long stall — a `pulse_tick` that outran the\n*167|    ///   interval, or the real killer, a laptop suspend/resume with the daemon\n 168|    ///   resident — collapses to **exactly one** catch-up fire instead of N rapid\n 169|    ///   back-to-back fires. (`next_after(deadline)` alone steps a single grid per\n 170|    ///   fire, so a deadline N grids behind `now` would fire N times in a tight\n 171|    ///   loop — the exact storm the loop-entry `next_fire(now)` collapse promises\n*172|    ///   to avoid; this composes with that collapse so a suspend/resume fires once\n 173|    ///   total.)\n 174|    pub fn advance_after_fire(&self, fired_ms: u64, now_ms: u64) -> u64 {\n 175|        self.next_after(fired_ms.max(now_ms))\n...\n 242|        assert_eq!(a.advance_after_fire(1_300, 1_300), 1_400);\n*243|        // Stall (suspend/resume or an overrunning tick): now sits 10+ grids past\n 244|        // the fired point → ONE catch-up fire at the next grid AFTER now, NOT a\n 245|        // storm of one-grid steps. fire@1_000, now=2_050 → 2_100 (not 1_100).\n 246|        assert_eq!(a.advance_after_fire(1_000, 2_050), 2_100);\n## digest.rs\n 8|//! on every output chunk. The digest is now a **projection of the same normalized\n*9|//! history the echo-commune consumes** — so there is nothing to feed: a snapshot\n 10|//! pull (or a `digest-entry` push) **projects on demand** from the endpoint's\n 11|//! records via [`project_endpoint_digest`], and the hub publishes the result.\n 12|//! Render surfaces read the PTY (the raw byte ring, unchanged); content surfaces\n...\n 31|//! ([`crate::digesthub::DigestHub::project_and_publish`]) to produce a\n*32|//! [`DigestUpdate`]: `from` is where the changed turns begin, and the subscriber\n 33|//! applies it by **truncating its view to `from`, then appending `turns`**. The\n 34|//! delta-stream **contract** ships; its deltas are driven by pull / digest-entry\n 35|//! publishes (autonomous file-watch freshness is deferred to the consuming\n...\n 59|/// `[digest]` presentation defaults that **any consumer may override** at\n*60|/// pull/subscribe). Every field is optional — unset falls through to the\n 61|/// adapter default, then the spt-core fallback ([`DigestConfig::default`]).\n 62|#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]\n 63|pub struct DigestOverride {\n...\n 100|\n*101|/// A structured digest change to push to a subscriber. Sent only when the digest\n 102|/// actually changed (so the stream carries deltas, not heartbeats).\n 103|#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n 104|pub struct DigestUpdate {\n...\n 189|        // Collapse cross-generation replay dupes on the RAW span, before the\n*190|        // two-origin merge + window fold, so a checkpoint/resume row surfaces once\n 191|        // (under its live generation) not twice. [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n 192|        supersede_cross_generation(spanned)\n 193|    } else {\n...\n 321|/// text, tool)`, the doyle ruling's supersede key (docs/W3-DIGEST-GENERATION-UNION-RULING.md).\n*322|/// A checkpoint/resume replays an ancestor generation's transcript verbatim, so a\n 323|/// replayed row is BYTE-identical on exactly these fields; NUL-joined so distinct\n 324|/// field boundaries never collide.\n 325|// [impl->REQ-DIGEST-GENERATION-SUPERSEDE]\n...\n 341|/// Collapse cross-generation replay dupes in a spanned activity backbone (flynn\n*342|/// digest gen-union, doyle Option-1 Supersede). A checkpoint/resume makes the\n 343|/// harness REPLAY the prior generation's transcript into the new session file, so\n 344|/// one logical row surfaces under two `seq`-generations — `(gen_old, localseq)`\n 345|/// and `(gen_new, localseq)` — and exact-seq consumer dedup can't collapse them.\n...\n 681|\n*682|    // [unit->REQ-DIGEST-GENERATION-SUPERSEDE] a checkpoint/resume replays gen A's\n 683|    // rows into gen B at the same localseq → the span carries each logical row\n 684|    // twice (two ordinals). Supersede keeps ONE, under B's (newer) generation, and\n 685|    // the surviving seq is B's.\n## digesthub.rs\n 4|//! The hub holds the latest projected snapshot per endpoint and fans every change\n*5|//! out to live subscribers. The digest is **projected on demand** from the\n 6|//! endpoint's normalized records ([`project_endpoint_digest`]) — there is no live\n 7|//! feed; a pull or a `digest-entry` push triggers a projection, and\n 8|//! [`DigestHub::project_and_publish`] diffs it against the prior snapshot to\n...\n 14|//! - **delta-stream** — `spt endpoint digest <id> --follow` ([`follow`])\n*15|//!   subscribes, receives a base snapshot, then **only the changes**\n 16|//!   ([`DigestUpdate`]s) as pulls / `digest-entry` pushes re-project. The stream\n 17|//!   contract ships; autonomous file-watch freshness is deferred to the consuming\n 18|//!   frontend milestone (CONTEXT.md \"session digest\").\n...\n 45|pub const KIND_DIGEST_RESULT: &str = \"digest_result\";\n*46|/// Client→daemon: subscribe to an endpoint's delta-stream from a version floor.\n*47|pub const KIND_DIGEST_SUBSCRIBE: &str = \"digest_subscribe\";\n 48|/// Daemon→client: one structured digest change (the streamed delta / base).\n 49|pub const KIND_DIGEST_DELTA: &str = \"digest_delta\";\n 50|/// Client→daemon: re-project an endpoint now and publish any change to live\n*51|/// subscribers — the push-driven delta after an `api digest-entry` append (no\n*52|/// reply; the delta reaches subscribers via the fan-out, REQ-TERM-4).\n 53|pub const KIND_DIGEST_REPROJECT: &str = \"digest_reproject\";\n 54|\n*55|/// `digest_snapshot` / `digest_subscribe` request payload.\n 56|#[derive(Debug, Clone, Serialize, Deserialize)]\n 57|pub struct DigestReq {\n 58|    /// The (local) endpoint id whose digest to read.\n 59|    pub id: String,\n*60|    /// Subscribe floor: only stream changes with `version > from_version`. The\n*61|    /// base snapshot is sent regardless so a fresh subscriber has an apply base.\n 62|    #[serde(default)]\n 63|    pub from_version: u64,\n 64|    /// Consumer presentation override (ADR-0019) — overrides the adapter's\n*65|    /// `[digest]` defaults for this pull/subscribe. Empty ⇒ adapter defaults.\n 66|    #[serde(default)]\n 67|    pub over: DigestOverride,\n 68|}\n...\n 81|/// One endpoint's live digest state in the hub: the latest snapshot + version\n*82|/// (for pulls) and the set of live subscriber senders (for the delta-stream).\n 83|#[derive(Default)]\n 84|struct EndpointState {\n 85|    snapshot: Digest,\n 86|    version: u64,\n*87|    subscribers: Vec<Sender<DigestUpdate>>,\n 88|}\n 89|\n 90|/// The daemon's per-endpoint live-digest registry. The brain publishes; the\n*91|/// control channel reads + subscribes. Holds only the projected digest state —\n 92|/// the same ephemeral-projection category as the broker's accepted-socket\n 93|/// bookkeeping, not routing/registry/parse logic (the parse already happened in\n 94|/// the brain's engine).\n...\n 109|    /// snapshot to compute the [`DigestUpdate`] delta, fan that out to every live\n*110|    /// subscriber (pruning any disconnected `--follow` client), and return the\n 111|    /// stored snapshot + its version. An unchanged projection is a no-op: no\n 112|    /// version bump, no fan-out. The projection itself ([`project_endpoint_digest`])\n 113|    /// runs **outside** this lock — only the diff + publish are serialized.\n...\n 130|        st.snapshot = new.clone();\n*131|        st.subscribers.retain(|tx| tx.send(update.clone()).is_ok());\n 132|        (new, st.version)\n 133|    }\n 134|\n...\n 140|\n*141|    /// Register a subscriber for `id`, returning the current base snapshot+version\n 142|    /// (to apply later deltas against) and the receiver future changes arrive on.\n*143|    /// The base is always returned so a fresh subscriber has an apply anchor.\n*144|    fn subscribe(&self, id: &str) -> (Digest, u64, Receiver<DigestUpdate>) {\n 145|        let (tx, rx) = channel();\n 146|        let mut map = self.map.lock().unwrap();\n 147|        let st = map.entry(id.to_string()).or_default();\n*148|        st.subscribers.push(tx);\n 149|        (st.snapshot.clone(), st.version, rx)\n 150|    }\n 151|\n*152|    /// Number of live subscribers for `id` (tests / introspection).\n## dispatch.rs\n 48|//! every E2E test uses). The peek and the serve use **separate** connections:\n*49|//! the peek brain subscribes from 0 to read the first line and is dropped;\n*50|//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n 51|//! stream's event feed alone (no double-subscription interleave). Origin is\n 52|//! the table's `remote_id_hex` — handshake-proven, never payload\n 53|//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n...\n 257|    let mut brain = connect(broker_name)?;\n*258|    brain.net_stream_subscribe(stream_id, 0)?;\n 259|    let mut buf: Vec<u8> = Vec::new();\n 260|    loop {\n 261|        match brain.read_event()? {\n...\n 351|            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n*352|            // before serve_attach subscribes — never mid-serve, which would swallow\n 353|            // interleaved attach records) and `None` on the local path.\n 354|            let (endpoint, remote_session) = match line\n 355|                .get(\"endpoint_id\")\n...\n 424|            // origin stays handshake-proven. The serve applies the event\n*425|            // through the LOCAL transition host — echo gate, wake effects,\n 426|            // and shell cascade run on this node, where the instance lives.\n 427|            // [impl->REQ-INST-6]\n 428|            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n...\n 466|/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n*467|/// dormant). The rest edge's transition echo is gate-armed\n*468|/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n 469|/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n 470|/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n 471|/// calls serialized here).\n...\n 479|    let policy = RegistryGatePolicy::load();\n*480|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 481|        return DispatchOutcome::Failed(e.to_string());\n 482|    }\n 483|    let mut decoder = RegistryDecoder::new();\n...\n 564|        .unwrap_or_default();\n*565|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 566|        return DispatchOutcome::Failed(e.to_string());\n 567|    }\n 568|    let mut decoder = NotifDecoder::new();\n...\n 637|) -> DispatchOutcome {\n*638|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 639|        return DispatchOutcome::Failed(e.to_string());\n 640|    }\n 641|    let mut decoder = WanDecoder::new();\n## drivehub.rs\n 5|//! channel\" — the opaque control surface). An owner writes one drive frame for a\n*6|//! shell; the shell binary drains it with an ephemeral `api drive-poll --link`.\n 7|//! Unlike the durable command channel (spooled on the shell perch), a drive\n 8|//! frame is held in a **single in-memory slot per shell** and overwritten on\n 9|//! every write — a second write before the poll clobbers the first.\n...\n 55|/// matches the slot's stamp (no-replay-across-relink), and the slot is cleared\n*56|/// regardless (exactly-once drain).\n 57|pub const KIND_DRIVE_TAKE: &str = \"drive_take\";\n 58|/// Daemon→shell: the take reply (the pending frame, or absent ⇒ nothing to serve).\n 59|pub const KIND_DRIVE_RESULT: &str = \"drive_result\";\n...\n 149|    /// Take-and-clear the shell's slot. The slot is removed unconditionally\n*150|    /// (exactly-once drain); the frame is returned ONLY when `token` matches the\n 151|    /// slot's stamp — a frame written under a now-dead link generation is evicted\n 152|    /// without being served (no-replay-across-relink).\n 153|    // [impl->REQ-SHELL-3]\n...\n 337|            None,\n*338|            \"the frame is drained exactly once\"\n 339|        );\n 340|        assert!(!hub.has_slot(\"doyle\", \"mock-shell-0\"));\n 341|    }\n## effect.rs\n 5|//!\n*6|//! The brain restarts freely (ADR-0004): a routine self-update swaps it, a crash\n 7|//! kills it. Effects it drives cross the broker↔brain boundary — a PTY write, a\n 8|//! spool row, a registry update. A brain that crashes *around* one of those\n 9|//! effects must, on restart, neither duplicate it (a re-injected keystroke, a\n...\n 394|    /// closure, so a blocking `PtyWrite` (the operator's keystroke into a\n*395|    /// not-draining ConPTY/forkpty) parked the lock forever → the single-threaded\n 396|    /// dispatch could open no attaches (`spt rc` → \"brain IPC read deadline\"), and\n 397|    /// every keystroke paid two fsyncs serialized (the stutter). Now: reserve the\n 398|    /// key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the\n...\n 810|    // user `effect()` closure (and across two fsyncs). A PtyWrite effect that\n*811|    // blocks (a non-draining child's full PTY input buffer) therefore holds the\n 812|    // GLOBAL journal lock forever — so EVERY other journaled effect (another\n 813|    // keystroke, another attach's journaled input) wedges, and the single-\n 814|    // threaded dispatch can no longer service `spt rc` attaches. Both units below\n...\n 840|                entered_tx.send(()).expect(\"signal entered\");\n*841|                // Block inside the effect — the wedge a non-draining PTY induces.\n 842|                release_rx.recv().expect(\"wait for release\");\n 843|                Ok(())\n 844|            })\n## endpoint.rs\n 35|\n*36|/// The broker listener name (B1/B2): PTY hosting + gapless handoff.\n 37|pub fn broker_socket_name() -> String {\n 38|    format!(\"spt-daemon-broker-{}.sock\", home_tag())\n 39|}\n...\n 41|/// The digest-control listener name (B8): the channel `spt digest <id>` connects\n*42|/// to for a snapshot pull or a delta-stream subscribe (ADR-0008 / REQ-TERM-4).\n 43|pub fn digest_socket_name() -> String {\n 44|    format!(\"spt-daemon-digest-{}.sock\", home_tag())\n 45|}\n## frame.rs\n 3|//! This is the single most update-frequency-sensitive contract in spt-core\n*4|//! (KNOWN-HAZARDS 2.3): self-update (ADR-0004) swaps the brain routinely while\n 5|//! the broker persists, so **a newer brain must talk to an older broker**. Two\n 6|//! mechanisms make that hold by construction:\n 7|//!\n...\n 19|//!    rather than a silent misparse.\n*20|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 21|\n 22|use serde::{Deserialize, Serialize};\n 23|\n...\n 99|/// policy — kept in one place so the rule is auditable.\n*100|// [impl->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 101|pub fn accept_hello(hello: &Hello, expected_peer: Role) -> Result<(), HandshakeError> {\n 102|    if hello.protocol_version < MIN_COMPATIBLE_VERSION {\n 103|        return Err(HandshakeError::IncompatibleVersion {\n...\n 151|\n*152|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 153|    // The load-bearing forward-compat property: a frame a *newer* brain emits —\n 154|    // carrying an unknown trailing field — must decode cleanly on an *older*\n 155|    // broker that has never heard of that field. We simulate the newer wire form\n...\n 174|\n*175|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 176|    // An unknown *kind* is not a decode error — the envelope parses and the\n 177|    // logic layer is what decides it cannot handle the kind.\n 178|    #[test]\n...\n 185|\n*186|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 187|    // A newer brain (higher protocol version, extra Hello fields) is accepted by\n 188|    // an older broker — the exact \"newer brain ↔ older broker\" direction 2.3\n 189|    // demands.\n...\n 201|\n*202|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n 203|    // A peer below the compatibility floor is rejected loudly, not misparsed.\n 204|    #[test]\n 205|    fn handshake_rejects_below_floor() {\n...\n 215|\n*216|    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT] role mismatch is a typed refusal.\n 217|    #[test]\n 218|    fn handshake_rejects_role_mismatch() {\n 219|        let hello = Hello::current(Role::Broker);\n## harnesshost.rs\n 56|/// \"post_spawn\"`); this only fills the `{session_id}` placeholder at spawn so the\n*57|/// command template resolves. A fresh create mints one; `--resume` supplies the\n 58|/// prior session id instead.\n 59|pub fn mint_session_id() -> String {\n 60|    let seed = spt_proto::identity::Identity::generate().seed();\n...\n 68|/// specific knowledge — used as the B2 defensive belt so a provisional never\n*69|/// leaks into a resume command (`claude -r <provisional>` hangs the harness at a\n*70|/// \"no sessions match\" picker; REQ-RESUME-HARNESS-SESSION-ID).\n 71|pub fn is_provisional_session_id(s: &str) -> bool {\n 72|    s.len() == 16\n 73|        && s.bytes()\n...\n 82|///\n*83|/// `is_resume`: select the NATIVE-resume role over the fresh one — when `true`\n*84|/// AND the adapter declares `[session.resume]`, the resume template is filled\n 85|/// (the harness runs e.g. `claude -r {session_id}` instead of a fresh\n 86|/// `claude --session-id {session_id}`, which would start a blank transcript). A\n*87|/// `true` with NO `[session.resume]` declared FALLS BACK to `[session.self]`\n 88|/// (full back-compat); a `false` always uses `[session.self]`.\n 89|// [impl->REQ-HOST-RUN-1]\n*90|// [impl->REQ-SESSION-RESUME-TEMPLATE]\n 91|pub fn prepare_harness_spawn(\n 92|    endpoint_id: &str,\n 93|    adapter_name: &str,\n...\n 95|    manifest: &Manifest,\n*96|    is_resume: bool,\n 97|    // The adapter install dir (record `source_dir`) — fills `{adapter_dir}` and\n 98|    // resolves the translation binary's program token against it (ADR-0029 /\n 99|    // REQ-INSTALL-11). `None` keeps PATH-only resolution + an unset `{adapter_dir}`.\n...\n 113|    }\n*114|    // Resume selects `[session.resume]` when declared, else falls back to\n*115|    // `[session.self]` (an adapter with no resume sibling behaves exactly as\n 116|    // today). A fresh bringup always uses `[session.self]`.\n*117|    let role = if is_resume {\n 118|        manifest\n 119|            .session\n*120|            .resume\n 121|            .as_ref()\n 122|            .or(manifest.session.self_.as_ref())\n 123|    } else {\n...\n 218|/// `session_id`: the spawn-time `{session_id}` fill — a provisional [`mint_session_id`]\n*219|/// for a fresh create, or the prior session for `--resume`.\n 220|///\n 221|/// `cwd`: the project-scoped working directory the harness PTY lands in\n 222|/// (REQ-HOST-RUN-2). `None` inherits the broker's cwd (the W1 bringup shortcut);\n...\n 225|///\n*226|/// `is_resume`: forwarded to [`prepare_harness_spawn`] — `true` selects the\n*227|/// adapter's `[session.resume]` native-resume command (REQ-SESSION-RESUME-TEMPLATE)\n 228|/// when declared, else falls back to `[session.self]`.\n 229|// [impl->REQ-HOST-RUN-1]\n 230|// [impl->REQ-HOST-RUN-2]\n*231|// [impl->REQ-SESSION-RESUME-TEMPLATE]\n 232|#[allow(clippy::too_many_arguments)] // bringup inputs (W3a added install_dir); a struct would obscure the call\n 233|pub fn launch_harness_brokered_in(\n 234|    broker_name: &str,\n...\n 239|    cwd: Option<&str>,\n*240|    is_resume: bool,\n 241|    // The adapter's install dir (W3a, ADR-0025) — recorded on the broker session","truncated":true,"truncatedBy":"bytes","totalLines":994,"totalBytes":55745,"outputLines":911,"outputBytes":51152,"lastLinePartial":false,"firstLineExceedsLimit":false},"meta":{"truncation":{"direction":"head","truncatedBy":"bytes","totalLines":994,"totalBytes":55745,"outputLines":911,"outputBytes":51152,"shownRange":{"start":1,"end":911},"nextOffset":912}}},"isError":false,"timestamp":1783730470807}}
{"type":"message","id":"64db3580","parentId":"81e4df57","timestamp":"2026-07-11T00:41:20.944Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Fetching historical diffs from GitHub**\n\n<!-- -->"},{"type":"toolCall","id":"call_yV5ruWYGWZLkTLn4sgcuftuQ|fc_08496348794f7e09016a5191317ee0819889b400e40523b639","name":"read","arguments":{"i":"Compare release tags","path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3","selector":"raw"}},{"type":"toolCall","id":"call_NowzKIxBPmgvKqXTHWJ2tx6x|fc_08496348794f7e09016a5191317ef48198999d88446fc370ca","name":"read","arguments":{"i":"Compare resume release","path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4","selector":"raw"}},{"type":"toolCall","id":"call_3F9di1wZILqvVzn0NXp7xilL|fc_08496348794f7e09016a5191317efc8198bec062691ab29f73","name":"read","arguments":{"i":"Compare flood release","path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5","selector":"raw"}},{"type":"toolCall","id":"call_Vfgfk7DEQ5i4uCUbMmPmPMYJ|fc_08496348794f7e09016a5191317f08819890adcfcb2d17438d","name":"read","arguments":{"i":"Compare bounded release","path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":27720,"output":251,"cacheRead":33280,"cacheWrite":0,"totalTokens":61251,"reasoningTokens":33,"cost":{"input":0.1386,"output":0.00753,"cacheRead":0.01664,"cacheWrite":0,"total":0.16277}},"stopReason":"toolUse","timestamp":1783730470854,"responseId":"resp_08496348794f7e09016a519129c348819892496898d2c2e7cd","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51912cf0dc8198baff172ce9f19676","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZExoYxpD-6cQfPcRgJY90ZdL302S1XOxdUyDCOjesqJAGdRfaTlpFtzscu00Iky5R9vvbZZJryX97V2TOCDV-7Yi8KCj-3TVqTxk-zCx4iympzRObHO7wfOlnu1vvYVfA7PmPwoHzTxHc1KzLv9FaAGnZOUGizcgC2kOGfLuMivMkHvhingkHiygKBGtwsB-DTijgNmevbZjmLjmiGcGFUgWP2mCmFYQ3AdIwJPzjD0l-7rdUar8gIITuzPOiNKSzfwGjrzaDrtIZx0KZsukJO8psgNIMkCGTKWyYklpbxVo3njo3l0MFEa_5WQkUN7ssVQBfxW3mz6Z_VUl-ydTUTgRmqQB7zuZljiqJlQYVrKuS0mkzGIjgd5ccY0uD-_CCtU_YycK5NOJouiQIA-6P4XgiuIvR7Xe4LiywgLwLSexDLPv-oQphQ-KYJEKUh5eqskcjBwQjxgtDXa1kqs_UDWbZm04S9_Cz8YxxgnhYsbs9i235APPjssIoksQ9wGddo92Hsq4OLdUd3EEasamt2QN4GHWiU2OJSdG-4lDiFvyKPUZBX0Ao5qY46pBpi1bPcITrbMqN0JwDGpX2peH826oYWJK1IiOxPQf8GSt6s7sngKM96gmYFxJ5Qmlsg5IzuwQ2GOhBa5eYyz3dG7GKNWI9Jo3JiI7SaoYxvMWwlSefeV6f4XRo3Vm6xbGIZUc0ZHEWpCLLVLEHuivgYJFNXxJeAT-PYcXAXHXHpDVC1aC0X9sF4wbyQqdtnokckE31Qk5AAdJxfR56mMmdEPn41de7MkBJGMzhlYQWyCz9nt6c_QrZ8NAvRZxOOa-dMY9EZmWKKcVTsa4KAyOvPIZDg0K-vkl0caKtWClt1yDb96xVl9M5AHntMMMKF73cEqzTot3QpGbMORbf23a8bVdphn3IqbU9HeH77jHS3LO6iT6vzAnzehHjNtMJPx73C3q62gGIMu5PLNHwGI1mUKa3CNgdJgswqI5R4Djp7HFaTsjdwx-LWqgy0WlRiLiNmikHqduPUQXFvSRW7yqXdAsf6ABfeXByC4XkTRToqURcMOn6yKFS-CrnvoAxcBPJmw2Hy6cgtahdyqiIY5s2OdUu_K_G2zsUQx-be3C2TYi3rkTUQ=","summary":[{"type":"summary_text","text":"**Fetching historical diffs from GitHub**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a5191317ee0819889b400e40523b639","type":"function_call","status":"completed","arguments":"{\"i\":\"Compare release tags\",\"path\":\"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3\",\"selector\":\"raw\"}","call_id":"call_yV5ruWYGWZLkTLn4sgcuftuQ","name":"read"},{"id":"fc_08496348794f7e09016a5191317ef48198999d88446fc370ca","type":"function_call","status":"completed","arguments":"{\"i\":\"Compare resume release\",\"path\":\"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4\",\"selector\":\"raw\"}","call_id":"call_NowzKIxBPmgvKqXTHWJ2tx6x","name":"read"},{"id":"fc_08496348794f7e09016a5191317efc8198bec062691ab29f73","type":"function_call","status":"completed","arguments":"{\"i\":\"Compare flood release\",\"path\":\"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5\",\"selector\":\"raw\"}","call_id":"call_3F9di1wZILqvVzn0NXp7xilL","name":"read"},{"id":"fc_08496348794f7e09016a5191317f08819890adcfcb2d17438d","type":"function_call","status":"completed","arguments":"{\"i\":\"Compare bounded release\",\"path\":\"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6\",\"selector\":\"raw\"}","call_id":"call_Vfgfk7DEQ5i4uCUbMmPmPMYJ","name":"read"}]},"duration":10087.510799999814,"ttft":5455.471400000155,"contextSnapshot":{"promptTokens":61000,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yV5ruWYGWZLkTLn4sgcuftuQ|fc_08496348794f7e09016a5191317ee0819889b400e40523b639","toolName":"read","startedAt":"2026-07-11T00:41:20.945Z","args":{"path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3"},"intent":"Compare release tags"},"id":"6bb9a7f3","parentId":"64db3580","timestamp":"2026-07-11T00:41:20.945Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_NowzKIxBPmgvKqXTHWJ2tx6x|fc_08496348794f7e09016a5191317ef48198999d88446fc370ca","toolName":"read","startedAt":"2026-07-11T00:41:20.945Z","args":{"path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4"},"intent":"Compare resume release"},"id":"8cfba6bf","parentId":"6bb9a7f3","timestamp":"2026-07-11T00:41:20.945Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_3F9di1wZILqvVzn0NXp7xilL|fc_08496348794f7e09016a5191317efc8198bec062691ab29f73","toolName":"read","startedAt":"2026-07-11T00:41:20.945Z","args":{"path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5"},"intent":"Compare flood release"},"id":"2f8b2b91","parentId":"8cfba6bf","timestamp":"2026-07-11T00:41:20.945Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Vfgfk7DEQ5i4uCUbMmPmPMYJ|fc_08496348794f7e09016a5191317f08819890adcfcb2d17438d","toolName":"read","startedAt":"2026-07-11T00:41:20.945Z","args":{"path":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6"},"intent":"Compare bounded release"},"id":"4ca24fb2","parentId":"2f8b2b91","timestamp":"2026-07-11T00:41:20.945Z"}
{"type":"message","id":"075137ac","parentId":"4ca24fb2","timestamp":"2026-07-11T00:41:21.183Z","message":{"role":"toolResult","toolCallId":"call_NowzKIxBPmgvKqXTHWJ2tx6x|fc_08496348794f7e09016a5191317ef48198999d88446fc370ca","toolName":"read","content":[{"type":"text","text":"URL: https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4\nContent-Type: application/json\nMethod: failed\nNotes: Failed to fetch URL (HTTP 404)\n\n---\n\n"}],"details":{"kind":"url","url":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4","finalUrl":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4","contentType":"application/json","method":"failed","truncated":false,"notes":["Failed to fetch URL (HTTP 404)"],"meta":{"source":{"type":"url","value":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.3...v0.30.4"}}},"isError":false,"timestamp":1783730481182}}
{"type":"message","id":"ae11cd07","parentId":"075137ac","timestamp":"2026-07-11T00:41:21.183Z","message":{"role":"toolResult","toolCallId":"call_3F9di1wZILqvVzn0NXp7xilL|fc_08496348794f7e09016a5191317efc8198bec062691ab29f73","toolName":"read","content":[{"type":"text","text":"URL: https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5\nContent-Type: application/json\nMethod: failed\nNotes: Failed to fetch URL (HTTP 404)\n\n---\n\n"}],"details":{"kind":"url","url":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5","finalUrl":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5","contentType":"application/json","method":"failed","truncated":false,"notes":["Failed to fetch URL (HTTP 404)"],"meta":{"source":{"type":"url","value":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.4...v0.30.5"}}},"isError":false,"timestamp":1783730481183}}
{"type":"message","id":"5e77dbba","parentId":"ae11cd07","timestamp":"2026-07-11T00:41:21.188Z","message":{"role":"toolResult","toolCallId":"call_Vfgfk7DEQ5i4uCUbMmPmPMYJ|fc_08496348794f7e09016a5191317f08819890adcfcb2d17438d","toolName":"read","content":[{"type":"text","text":"URL: https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6\nContent-Type: application/json\nMethod: failed\nNotes: Failed to fetch URL (HTTP 404)\n\n---\n\n"}],"details":{"kind":"url","url":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6","finalUrl":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6","contentType":"application/json","method":"failed","truncated":false,"notes":["Failed to fetch URL (HTTP 404)"],"meta":{"source":{"type":"url","value":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.5...v0.30.6"}}},"isError":false,"timestamp":1783730481188}}
{"type":"message","id":"efee45c0","parentId":"5e77dbba","timestamp":"2026-07-11T00:41:21.258Z","message":{"role":"toolResult","toolCallId":"call_yV5ruWYGWZLkTLn4sgcuftuQ|fc_08496348794f7e09016a5191317ee0819889b400e40523b639","toolName":"read","content":[{"type":"text","text":"URL: https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3\nContent-Type: application/json\nMethod: failed\nNotes: Failed to fetch URL (HTTP 404)\n\n---\n\n"}],"details":{"kind":"url","url":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3","finalUrl":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3","contentType":"application/json","method":"failed","truncated":false,"notes":["Failed to fetch URL (HTTP 404)"],"meta":{"source":{"type":"url","value":"https://api.github.com/repos/SaberMage/spt-core/compare/v0.30.2...v0.30.3"}}},"isError":false,"timestamp":1783730481258}}
{"type":"message","id":"755fb4bb","parentId":"efee45c0","timestamp":"2026-07-11T00:41:27.675Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Inspecting git commit logs**\n\n<!-- -->"},{"type":"toolCall","id":"call_vqrvodUwzUxr2VFKvpHcWRln|fc_08496348794f7e09016a5191383cf481989f885a22e1b4d823","name":"read","arguments":{"i":"Read main commit history","path":"spt-core/.git/logs/refs/heads/main","selector":"raw"}},{"type":"toolCall","id":"call_193FIQ9NciNG6GnittHKQEbI|fc_08496348794f7e09016a5191383d048198a0f2c5dbc9d319fb","name":"read","arguments":{"i":"Read bounded fix history","path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","selector":"raw"}},{"type":"toolCall","id":"call_YqoKeVTCZkoF3ITTsOsWn2N0|fc_08496348794f7e09016a5191383d0c8198b447d791e7e6578d","name":"read","arguments":{"i":"Read resume fix history","path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","selector":"raw"}},{"type":"toolCall","id":"call_3iEdI0uCvg8kR0S75DD3pJnq|fc_08496348794f7e09016a5191383d1481989ec641a34f5acfab","name":"read","arguments":{"i":"Read drain drive history","path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive","selector":"raw"}},{"type":"toolCall","id":"call_HAf1FTSSMkDOurxQmSQzU6v7|fc_08496348794f7e09016a5191383d1881988dbc4b1f76a53010","name":"read","arguments":{"i":"Read inject fix history","path":"spt-core/.git/logs/refs/heads/fix/inject-settle-rearm","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1589,"output":261,"cacheRead":59904,"cacheWrite":0,"totalTokens":61754,"reasoningTokens":36,"cost":{"input":0.007945,"output":0.00783,"cacheRead":0.029952,"cacheWrite":0,"total":0.045727000000000004}},"stopReason":"toolUse","timestamp":1783730481307,"responseId":"resp_08496348794f7e09016a5191325c908198bb88b636467962a6","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51913377048198b7ae7d340e4e1c04","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZE4pPxBIc_-WXZ_KfenKai6seMda-Fhp1K1hSvq1nIcTWAEZnazXg1QguMDfvCbYGSK2vaZoxD3pA3AP9AKYoINprz5tGPcfCNJe7aLJKyf8yMC256ORcZ0ETJNPAFdepH8DmKveRHzDgPXYdVe5f_MV8-owL5jzD8N2B_EGt81VSpRmgEfwZZYZkZEmwSlCaAYkBpy_UvQwSpujfc2HJm18cYnxH7UQ886Toi-vUAhGgjw0V1yP_9_Wkc9BWeAbNv-yRRSP8RGWxKQDOfOo1Qt-BdyHgNtANNFTrPHn9x3W4SdmnVGw_SvteSGdFWf_4CARwO7m3nMUPIDM9wrKAkeauH4eLr4xKGz1nFfGTriOeG-KdOtCKsVb9spHkbSxKwmSCOA8XAvPX7PH1GUSOl-c9PF_pDV6dXuW8okoviqZN7AZub5eTzr0lSm47nSfT4cr_ZRW-B8Is6tWYfQ8DRaYTDQUinHlF_dLJ37Q6YRPjj-_hZezufcOfY132KGMtR-sP-W0KHjNctTMggREPLcSr5oKeceSuQCZ5aJNfY4mNI0GtPYjbQBOipHpIb69L8UFp1vfiucKie-CZITRVLmVOXgrM_aYewupgWcDTfxIZUF7xALsxTx_odm0IHug8Vu1AEBLfqamZfRQdxRiK3Spb4Wu_MDJxCerngOfe5YzGMEGN5Zceax5yI4_a8se8x9iKrj2srQEkM64DPLpWra8xPa0JORUk_mYR4pjZ-fl2RUIZ4wFzaDEOufCFcTrnbPUXuUP8benBhaFuSZL8t1KM5Rp8eisjsaJw_qNnCKudLTn2pJGqr_p2idHjU2xV7_ohV96r5krvmyxyrkPvsEww8rBlQGDNE8w6kFTey2tiQKB4GvUfG1ppUdYrjpxOhIUkZHSPfG1Js_r35cFCAXwatG3FjcJcK9RLQGEbfmvAEPPmrBM6I_tiM7of70D_xwPo5FITMe5jbTwqrUhiTrpiHB5yfp5NaI41xVB32vFUX-5Ub-CYEeXyA0nmLyv9YqSdRzeeDTlTDjQ0tv_kK6s31IH13deFajuujnM_aOYjWfaO3A8Ig2FdwfN2HybV8SJJLB18OBQi49TkGBS3Mttl6SZ5zRC_XyySHmLt7rY_5Ojii7ZCWuwtO_sAVuKsgQ3c0EmL8QQDxmRteyVvAIzw==","summary":[{"type":"summary_text","text":"**Inspecting git commit logs**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a5191383cf481989f885a22e1b4d823","type":"function_call","status":"completed","arguments":"{\"i\":\"Read main commit history\",\"path\":\"spt-core/.git/logs/refs/heads/main\",\"selector\":\"raw\"}","call_id":"call_vqrvodUwzUxr2VFKvpHcWRln","name":"read"},{"id":"fc_08496348794f7e09016a5191383d048198a0f2c5dbc9d319fb","type":"function_call","status":"completed","arguments":"{\"i\":\"Read bounded fix history\",\"path\":\"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write\",\"selector\":\"raw\"}","call_id":"call_193FIQ9NciNG6GnittHKQEbI","name":"read"},{"id":"fc_08496348794f7e09016a5191383d0c8198b447d791e7e6578d","type":"function_call","status":"completed","arguments":"{\"i\":\"Read resume fix history\",\"path\":\"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer\",\"selector\":\"raw\"}","call_id":"call_YqoKeVTCZkoF3ITTsOsWn2N0","name":"read"},{"id":"fc_08496348794f7e09016a5191383d1481989ec641a34f5acfab","type":"function_call","status":"completed","arguments":"{\"i\":\"Read drain drive history\",\"path\":\"spt-core/.git/logs/refs/heads/update-wedge-drain-drive\",\"selector\":\"raw\"}","call_id":"call_3iEdI0uCvg8kR0S75DD3pJnq","name":"read"},{"id":"fc_08496348794f7e09016a5191383d1881988dbc4b1f76a53010","type":"function_call","status":"completed","arguments":"{\"i\":\"Read inject fix history\",\"path\":\"spt-core/.git/logs/refs/heads/fix/inject-settle-rearm\",\"selector\":\"raw\"}","call_id":"call_HAf1FTSSMkDOurxQmSQzU6v7","name":"read"}]},"duration":6367.069099999964,"ttft":1528.36689999979,"contextSnapshot":{"promptTokens":61493,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_vqrvodUwzUxr2VFKvpHcWRln|fc_08496348794f7e09016a5191383cf481989f885a22e1b4d823","toolName":"read","startedAt":"2026-07-11T00:41:27.676Z","args":{"path":"spt-core/.git/logs/refs/heads/main"},"intent":"Read main commit history"},"id":"8ce26893","parentId":"755fb4bb","timestamp":"2026-07-11T00:41:27.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_193FIQ9NciNG6GnittHKQEbI|fc_08496348794f7e09016a5191383d048198a0f2c5dbc9d319fb","toolName":"read","startedAt":"2026-07-11T00:41:27.676Z","args":{"path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write"},"intent":"Read bounded fix history"},"id":"cad72871","parentId":"8ce26893","timestamp":"2026-07-11T00:41:27.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YqoKeVTCZkoF3ITTsOsWn2N0|fc_08496348794f7e09016a5191383d0c8198b447d791e7e6578d","toolName":"read","startedAt":"2026-07-11T00:41:27.676Z","args":{"path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer"},"intent":"Read resume fix history"},"id":"a577f4de","parentId":"cad72871","timestamp":"2026-07-11T00:41:27.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_3iEdI0uCvg8kR0S75DD3pJnq|fc_08496348794f7e09016a5191383d1481989ec641a34f5acfab","toolName":"read","startedAt":"2026-07-11T00:41:27.676Z","args":{"path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive"},"intent":"Read drain drive history"},"id":"abb75d95","parentId":"a577f4de","timestamp":"2026-07-11T00:41:27.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_HAf1FTSSMkDOurxQmSQzU6v7|fc_08496348794f7e09016a5191383d1881988dbc4b1f76a53010","toolName":"read","startedAt":"2026-07-11T00:41:27.676Z","args":{"path":"spt-core/.git/logs/refs/heads/fix/inject-settle-rearm"},"intent":"Read inject fix history"},"id":"5fb8ab92","parentId":"abb75d95","timestamp":"2026-07-11T00:41:27.676Z"}
{"type":"message","id":"7333efb1","parentId":"5fb8ab92","timestamp":"2026-07-11T00:41:27.686Z","message":{"role":"toolResult","toolCallId":"call_3iEdI0uCvg8kR0S75DD3pJnq|fc_08496348794f7e09016a5191383d1481989ec641a34f5acfab","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n","startLine":1,"lineNumbers":[1,2,3]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\update-wedge-drain-drive"}}},"isError":false,"timestamp":1783730487685}}
{"type":"message","id":"6a520efb","parentId":"7333efb1","timestamp":"2026-07-11T00:41:27.686Z","message":{"role":"toolResult","toolCallId":"call_193FIQ9NciNG6GnittHKQEbI|fc_08496348794f7e09016a5191383d048198a0f2c5dbc9d319fb","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783664465 -0700\tbranch: Created from main\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 c0a3998d89f897233ca27085ab55a2312dc5c979 Reavo End <decidel@gmail.com> 1783665437 -0700\tcommit: reqs(uwedge-r4): mint REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK + KNOWN-HAZARDS 7.38\nc0a3998d89f897233ca27085ab55a2312dc5c979 ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 Reavo End <decidel@gmail.com> 1783665599 -0700\tcommit: broker(uwedge-r4): live conn-handler tally seam for the round-4 int gate\nff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\nd634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783664465 -0700\tbranch: Created from main\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 c0a3998d89f897233ca27085ab55a2312dc5c979 Reavo End <decidel@gmail.com> 1783665437 -0700\tcommit: reqs(uwedge-r4): mint REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK + KNOWN-HAZARDS 7.38\nc0a3998d89f897233ca27085ab55a2312dc5c979 ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 Reavo End <decidel@gmail.com> 1783665599 -0700\tcommit: broker(uwedge-r4): live conn-handler tally seam for the round-4 int gate\nff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\nd634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\fix\\update-wedge-bounded-conn-write"}}},"isError":false,"timestamp":1783730487686}}
{"type":"message","id":"0f229b32","parentId":"6a520efb","timestamp":"2026-07-11T00:41:27.686Z","message":{"role":"toolResult","toolCallId":"call_YqoKeVTCZkoF3ITTsOsWn2N0|fc_08496348794f7e09016a5191383d0c8198b447d791e7e6578d","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\nfb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\nfb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\uwedge2-resume-viewer"}}},"isError":false,"timestamp":1783730487686}}
{"type":"message","id":"6f71d6e1","parentId":"0f229b32","timestamp":"2026-07-11T00:41:27.687Z","message":{"role":"toolResult","toolCallId":"call_vqrvodUwzUxr2VFKvpHcWRln|fc_08496348794f7e09016a5191383cf481989f885a22e1b4d823","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 9515e2ea26ea2b081e1f51f4abb0be4f718a96ee Reavo End <decidel@gmail.com> 1780222436 -0700\tcommit (initial): docs: initial spt-core planning artifacts\n9515e2ea26ea2b081e1f51f4abb0be4f718a96ee 1d30876dbb90d1df77f434d26604528adaeeccb6 Reavo End <decidel@gmail.com> 1780224876 -0700\tcommit: docs: shell model, sleep/wake, and from-shell owner-shutdown\n1d30876dbb90d1df77f434d26604528adaeeccb6 293e33774dc77b4f2e80cd2a56a921624776ed72 Reavo End <decidel@gmail.com> 1780227620 -0700\tcommit: docs: cross-node context sync (P2P + opt-in GitHub hub)\n293e33774dc77b4f2e80cd2a56a921624776ed72 b73269976256fd4ecf67bd8222a9f71b32b7b775 Reavo End <decidel@gmail.com> 1780228153 -0700\tcommit: docs: dual-audience documentation strategy\nb73269976256fd4ecf67bd8222a9f71b32b7b775 9d064c2f5a9b44161b9c55fc1c1b74d14204b81a Reavo End <decidel@gmail.com> 1780229594 -0700\tcommit: docs: concrete doc templates from firsthand artifact pass\n9d064c2f5a9b44161b9c55fc1c1b74d14204b81a a71c14559c221b0841f53d6180d410e119e6c2c9 Reavo End <decidel@gmail.com> 1780273857 -0700\tcommit: docs: psyche context & memory model (17.3 + memformat redesign)\na71c14559c221b0841f53d6180d410e119e6c2c9 95353b7035858b79fb4cbd9cd3823d7195179fd1 Reavo End <decidel@gmail.com> 1780276646 -0700\tcommit: docs: dormancy policy, suspend surface, boundary communes, spt refresh (17.4)\n95353b7035858b79fb4cbd9cd3823d7195179fd1 51b485d106befb2a5607ca7ae13a30b695157f99 Reavo End <decidel@gmail.com> 1780278010 -0700\tcommit: docs: consent & security-gate model (17.5) + refresh boundary resolution\n51b485d106befb2a5607ca7ae13a30b695157f99 7e3826f305393790fcf608bac064403da01ec292 Reavo End <decidel@gmail.com> 1780278964 -0700\tcommit: docs: roadmap — path to fruition (review → spikes → M0-M5)\n7e3826f305393790fcf608bac064403da01ec292 a7128b6c33bdf83e3d2379926285250cadcfa41d Reavo End <decidel@gmail.com> 1780280308 -0700\tcommit: build: adopt traceable-reqs with a traceability contract\na7128b6c33bdf83e3d2379926285250cadcfa41d 94ff79d09693f0e86fdb69abec144b4f577c5f8e Reavo End <decidel@gmail.com> 1780283594 -0700\tcommit: docs: Stage A red-team + Spike #1 (broker/brain handoff validated)\n94ff79d09693f0e86fdb69abec144b4f577c5f8e dbb21e48f79e6334037ca7db678ead224b98a4bd Reavo End <decidel@gmail.com> 1780283971 -0700\tcommit: docs: Spike #2 (Iroh smoke test) PASS + roadmap progress\ndbb21e48f79e6334037ca7db678ead224b98a4bd 48ec6d42b1e141b36190c99c6480dfe1d3d27c61 Reavo End <decidel@gmail.com> 1780293734 -0700\tcommit: docs: Spike #2 cross-machine result — two-host Iroh QUIC PASS\n48ec6d42b1e141b36190c99c6480dfe1d3d27c61 0dfff6a650ffcd098c489bc55efd3d235982a8db Reavo End <decidel@gmail.com> 1780294353 -0700\tcommit: docs: Spike #2 — native iroh NAT traversal across two separate networks PASS\n0dfff6a650ffcd098c489bc55efd3d235982a8db 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780295246 -0700\tcommit: feat(spt-proto): M0 first brick — envelope codec + parser, 2 hazards covered\n4fa29498d2552f277e918219f74d9c75183a6921 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974019 -0700\tmerge dev-freeform: Fast-forward\n35d1672fdf999a4fc6e58fcd04ff5c953452beb0 7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f Reavo End <decidel@gmail.com> 1780975158 -0700\tmerge dev-freeform: Fast-forward\n7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 Reavo End <decidel@gmail.com> 1780976867 -0700\tmerge dev-freeform: Fast-forward\na81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780978106 -0700\tmerge dev-freeform: Fast-forward\n9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tmerge dev-freeform: Fast-forward\n516465c1cbf959ed89c77464b38e9c4ed5142ad9 c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f Reavo End <decidel@gmail.com> 1780982768 -0700\tmerge dev-freeform: Fast-forward\nc5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780997108 -0700\tmerge dev-freeform: Fast-forward\n4e774a391077a9bdce29906aaed6de804da5de1b cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780999401 -0700\tmerge dev-freeform: Fast-forward\ncabdc614fc0416e3a57de0a21b229db485089320 44497f466c89b104de2a10816c30f5b68c8fd28a Reavo End <decidel@gmail.com> 1781052390 -0700\tcommit: docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints\n44497f466c89b104de2a10816c30f5b68c8fd28a 2769c8801c533add7e1522f0bd8224d11688e8d2 Reavo End <decidel@gmail.com> 1781052473 -0700\tcommit: docs(deferred): two v0.3.2-verify deferrals — test broker socket flake + fetch single→set trap\n2769c8801c533add7e1522f0bd8224d11688e8d2 65c2b0ac4d9e7e386f7e49e10da04756f02ef2b8 Reavo End <decidel@gmail.com> 1781052653 -0700\tcommit: docs(plan): RESTORATION-PLAN.md — broker/brain process-isolation build (D1–D7)\n65c2b0ac4d9e7e386f7e49e10da04756f02ef2b8 f1f798352d548da49999fbbec28f106d98ec47cc Reavo End <decidel@gmail.com> 1781053337 -0700\tcommit: docs(restoration): doyle vet amendments 1-4 — D1 ordering fix + status ratification\nf1f798352d548da49999fbbec28f106d98ec47cc 0c954353b256f46e45a600be7a7fb1f3768e9d30 Reavo End <decidel@gmail.com> 1781055021 -0700\tcommit: feat(daemon): restoration D1 — broker/brain process-split skeleton (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\n0c954353b256f46e45a600be7a7fb1f3768e9d30 f2a17a9cfd51e80b39ad2238e36866f1bdac6a37 Reavo End <decidel@gmail.com> 1781057387 -0700\tcommit: feat(daemon): restoration D2-1 — net consumers move broker→brain (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\nf2a17a9cfd51e80b39ad2238e36866f1bdac6a37 a9fe443757267897bdbe3dc22e11ca22403df97d Reavo End <decidel@gmail.com> 1781058108 -0700\tcommit: feat(daemon): restoration D2-2 — shellwake wake-host moves broker→brain; D2 scope correction (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\na9fe443757267897bdbe3dc22e11ca22403df97d 7398d7c5f0f08032c00aca25e9e8d9946e1c8890 Reavo End <decidel@gmail.com> 1781058533 -0700\tcommit: docs(restoration): defer V6 N-1 compat-test scaffold D2→D3 (first additive verb lands in D3)\n7398d7c5f0f08032c00aca25e9e8d9946e1c8890 e3af674051c4394630f07f710cfdac344887a2d1 Reavo End <decidel@gmail.com> 1781059368 -0700\tcommit: docs(restoration): D3 task plan — supervision anchor + update trigger (Q2/Q3/V2)\ne3af674051c4394630f07f710cfdac344887a2d1 1f9fe89e4f09fcf38b3f2fca134d1d16ba2e14ba Reavo End <decidel@gmail.com> 1781059572 -0700\tcommit: feat(daemon): restoration D3-1 — seed anchor survives a brain cycle (Q2)\n1f9fe89e4f09fcf38b3f2fca134d1d16ba2e14ba d43c3a68584486705f3a6684e527af7de5ed89ee Reavo End <decidel@gmail.com> 1781059966 -0700\tcommit: feat(daemon): restoration D3-2 — broker generation custody + versioned brain argv (Q2/V2)\nd43c3a68584486705f3a6684e527af7de5ed89ee 4d8f34c815142c44c2329521284a06d67290b083 Reavo End <decidel@gmail.com> 1781060768 -0700\tcommit: feat(daemon): restoration D3-3 — real brain-process restart on update (Q3)\n4d8f34c815142c44c2329521284a06d67290b083 348a73919f61138c637cae33f27441cac1794a74 Reavo End <decidel@gmail.com> 1781060911 -0700\tcommit: feat(daemon): restoration D3-4 — V6 N-1 argv-compat harness scaffold\n348a73919f61138c637cae33f27441cac1794a74 c608227de29e22e60703cb2c39e60b97fef9bbab Reavo End <decidel@gmail.com> 1781071812 -0700\tcommit: docs(restoration): D4 task plan — multi-session cold-start resume (Q6)\nc608227de29e22e60703cb2c39e60b97fef9bbab 56d88b985c747648ca869f7614104d7bc0e509c4 Reavo End <decidel@gmail.com> 1781072516 -0700\tcommit: docs(restoration): fold doyle D4-plan vet amendments (cursor-advance precision)\n56d88b985c747648ca869f7614104d7bc0e509c4 fea8eaa3ef19ff72dc39041b356685a06fb5f6ec Reavo End <decidel@gmail.com> 1781073132 -0700\tcommit: feat(daemon): restoration D4-1 — broker cursor-of-record (Q6)\nfea8eaa3ef19ff72dc39041b356685a06fb5f6ec 5eb64ff0962e99652309f0e058a2c3e7a53fe57e Reavo End <decidel@gmail.com> 1781074322 -0700\tcommit: docs(restoration): fold doyle D4-2 watch-item — cursor clamp discipline\n5eb64ff0962e99652309f0e058a2c3e7a53fe57e 08afcb90df22bb6cf25ddf03b11be69bf1cbcdd2 Reavo End <decidel@gmail.com> 1781074337 -0700\tcommit: feat(daemon): restoration D4-2 — multi-session cold-start resume (Q6)\n08afcb90df22bb6cf25ddf03b11be69bf1cbcdd2 35b925e9307abbd5c997352226cbf684bde322a7 Reavo End <decidel@gmail.com> 1781074446 -0700\tcommit: docs(daemon): restoration D4-3 — confirm + lock the BrainState-message retire (Q6)\n35b925e9307abbd5c997352226cbf684bde322a7 9a0a42998b946ddced09917804e1ad4d49b35e49 Reavo End <decidel@gmail.com> 1781074485 -0700\tcommit: docs(restoration): mark D4-1/D4-2/D4-3 DONE — D4 milestone complete\n9a0a42998b946ddced09917804e1ad4d49b35e49 612b1dee3dbb7c7acf01717adc9601ee639f5b57 Reavo End <decidel@gmail.com> 1781074927 -0700\tcommit: feat(daemon): restoration D4-2b — close the mixed-mode resume gap (Q6, amd 5)\n612b1dee3dbb7c7acf01717adc9601ee639f5b57 794031805faf5c54a6fab5ca538b641238847d76 Reavo End <decidel@gmail.com> 1781075872 -0700\tcommit: fix(test): D4-2b resume harness — sequential spawn/read (Linux CI flake)\n794031805faf5c54a6fab5ca538b641238847d76 48977a210c0db0b5e669407041d273dd519d5b68 Reavo End <decidel@gmail.com> 1781076046 -0700\tcommit: docs(daemon): record KH 6.9 — resume-mode blocking-wait output discard\n48977a210c0db0b5e669407041d273dd519d5b68 3055eb7b59f01673d770152576a2fac1cb6d8f4b Reavo End <decidel@gmail.com> 1781076633 -0700\tcommit: test(daemon): fix unlocked SPT_HOME mutation racing with_home tests (CI flake)\n3055eb7b59f01673d770152576a2fac1cb6d8f4b 78bbdc10e36de90bd6ffda554fa87b0a8b124fe3 Reavo End <decidel@gmail.com> 1781079306 -0700\tcommit: feat(daemon): restoration D5-1 — durable absolute-deadline primitive (Q4)\n78bbdc10e36de90bd6ffda554fa87b0a8b124fe3 8206ca2f4096e60c962cb34f84db9591ba759e00 Reavo End <decidel@gmail.com> 1781079326 -0700\tcommit (amend): feat(daemon): restoration D5-1 — durable absolute-deadline primitive (Q4)\n8206ca2f4096e60c962cb34f84db9591ba759e00 4f107b6e0b4cf62a9cb5eb26b42661691e55bbca Reavo End <decidel@gmail.com> 1781079544 -0700\tcommit: feat(daemon): restoration D5-2 — pulse loop on disk-anchored deadline (Q4/V4)\n4f107b6e0b4cf62a9cb5eb26b42661691e55bbca c4af6cf3df03274c9707a74c78da9a0ea0779788 Reavo End <decidel@gmail.com> 1781079658 -0700\tcommit: docs(daemon): restoration D5-3 — lock V3 one-shot rule + KH 6.10 (Q4)\nc4af6cf3df03274c9707a74c78da9a0ea0779788 97282eb010d054394975db3b0501c7a75c60daba Reavo End <decidel@gmail.com> 1781079694 -0700\tcommit: docs(restoration): mark D5-1/D5-2/D5-3 DONE — D5 milestone complete\n97282eb010d054394975db3b0501c7a75c60daba 2ba4fd7428e702aef331a29160602f4003426f6a Reavo End <decidel@gmail.com> 1781080359 -0700\tcommit: fix(daemon): D5-2b — pulse advance skips past now, no fire-storm after a stall\n2ba4fd7428e702aef331a29160602f4003426f6a bd8db6ae1d29e0c72cd8c5dedc410cf4f44f42fb Reavo End <decidel@gmail.com> 1781089202 -0700\tcommit: docs(restoration): D6 plan — readiness-gated auto-rollback (Q7/V1)\nbd8db6ae1d29e0c72cd8c5dedc410cf4f44f42fb 8859c9b1d004e5ab824a089eef4c07b4027df56b Reavo End <decidel@gmail.com> 1781089219 -0700\tcommit: feat(daemon): restoration D6-1 — two-phase applied record + gen-stamped ready (Q7)\n8859c9b1d004e5ab824a089eef4c07b4027df56b 79efc113073cf58ddd8135e9eb4a1fdfb1b50238 Reavo End <decidel@gmail.com> 1781091168 -0700\tcommit: feat(daemon): restoration D6-2 — readiness-gated supervisor + auto-rollback (Q7)\n79efc113073cf58ddd8135e9eb4a1fdfb1b50238 d375bae0b70d0883e8ec74ce4825b18585e24c1c Reavo End <decidel@gmail.com> 1781091468 -0700\tcommit: docs(daemon): restoration D6-3 — rollback-state-compat tripwire + KH 6.8 guard (Q7-V1)\nd375bae0b70d0883e8ec74ce4825b18585e24c1c ad15a1e39a16c84164e08fde18c8ac0c4e2463c6 Reavo End <decidel@gmail.com> 1781092587 -0700\tcommit: fix(daemon): restoration D6-2b — fold doyle's 3 D6 vet findings (Q7)\nad15a1e39a16c84164e08fde18c8ac0c4e2463c6 c81f9dc06ce868faab4eb6098a28bc97ed14a023 Reavo End <decidel@gmail.com> 1781127509 -0700\tcommit: docs(restoration): D7 plan — proof + REQ re-point + fleet verify (V5/V6/§9)\nc81f9dc06ce868faab4eb6098a28bc97ed14a023 a52f8c6331e368777348a14c703363afc0585183 Reavo End <decidel@gmail.com> 1781128024 -0700\tcommit: feat(daemon): restoration D7-1a — exe_hash breadcrumb on brain.ready (V5)\na52f8c6331e368777348a14c703363afc0585183 1fdf99f352270b87914983a52fd783f2535cf852 Reavo End <decidel@gmail.com> 1781129168 -0700\tcommit: feat(daemon): restoration D7-1b — process-level survival E2E onto a swapped binary (V5)\n1fdf99f352270b87914983a52fd783f2535cf852 0cf5eaec508f08faf42fa92119cd99bc9630248a Reavo End <decidel@gmail.com> 1781129992 -0700\tcommit: feat(daemon): restoration D7-1c — functional (not just held) survival probe (V5, doyle F1)\n0cf5eaec508f08faf42fa92119cd99bc9630248a 6269e3a6292cd1f89fcd68dd5049bdc4ed73f74c Reavo End <decidel@gmail.com> 1781131599 -0700\tcommit: fix(windows): suppress ephemeral console windows on test + brain-child spawns\n6269e3a6292cd1f89fcd68dd5049bdc4ed73f74c 04fd322fbf2bc784237ce8289f316c35761a5979 Reavo End <decidel@gmail.com> 1781131617 -0700\tcommit: feat(daemon): restoration D7-2 — new-brain × old-broker N-1 verb-surface gate (V6)\n04fd322fbf2bc784237ce8289f316c35761a5979 d239ae418c6352f4f3a32d5ec6d365cc2a8a3180 Reavo End <decidel@gmail.com> 1781131732 -0700\tcommit: chore(traceability): restoration D7-3 — activate BROKER-PROCESS-ISOLATION int + reconcile re-points\nd239ae418c6352f4f3a32d5ec6d365cc2a8a3180 f0919d3c09b12cfbf8c3b9840d2bcee199984a7f Reavo End <decidel@gmail.com> 1781132342 -0700\tcommit: fix(ci): restoration D7-2b — n1-gate windows uses pwsh, not powershell 5.1\nf0919d3c09b12cfbf8c3b9840d2bcee199984a7f fa5b54e4f2c8f8179eedea01500598753f42cdf7 Reavo End <decidel@gmail.com> 1781137670 -0700\tcommit: @\nfa5b54e4f2c8f8179eedea01500598753f42cdf7 2162ae759ea5021ff75ea8ae783f11ba8c8329ef Reavo End <decidel@gmail.com> 1781137687 -0700\tcommit (amend): chore(release): restoration D7-4 — v0.4.0 release prep (version + changelog)\n2162ae759ea5021ff75ea8ae783f11ba8c8329ef b0fdc015fd37172ab3782fcc71878cfa67da7b3d Reavo End <decidel@gmail.com> 1781138199 -0700\tcommit: ci: [twohost] — exercise the 2-node rig before the v0.4.0 tag (D7-4 gate)\nb0fdc015fd37172ab3782fcc71878cfa67da7b3d 190ad106e81678006b885372ba33bd0341a4f81a Reavo End <decidel@gmail.com> 1781144896 -0700\tcommit: docs(restoration): D7-4 — fleet field-run evidence appendix (v0.4.0 roll)\n190ad106e81678006b885372ba33bd0341a4f81a abe9e3470eacfc6329731accca59bbce093b9321 Reavo End <decidel@gmail.com> 1781145031 -0700\tcommit: docs(architecture): pump worker seam — frozen design + execution plan (arch card 1)\nabe9e3470eacfc6329731accca59bbce093b9321 610c176fb3a7394be8a7c6953f6c45534ed4287a Reavo End <decidel@gmail.com> 1781150784 -0700\tpull --ff-only: Fast-forward\n610c176fb3a7394be8a7c6953f6c45534ed4287a e0dbca122b42a268ad720dce3d4672699adb44cf Reavo End <decidel@gmail.com> 1781157876 -0700\tpull --ff-only: Fast-forward\ne0dbca122b42a268ad720dce3d4672699adb44cf 4d8b810733a70b43adf5c055802ebf8d06dd7c20 Reavo End <decidel@gmail.com> 1781160396 -0700\tpull --ff-only origin main: Fast-forward\n4d8b810733a70b43adf5c055802ebf8d06dd7c20 038257bb9c4a405244a6724beaf2ececb30e7111 Reavo End <decidel@gmail.com> 1781162576 -0700\tpull --ff-only origin main: Fast-forward\n038257bb9c4a405244a6724beaf2ececb30e7111 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781173992 -0700\tpull --ff-only origin main: Fast-forward\n725aebf647f8ebe4415fcfd2274488b41cc561f0 55c4e5933ea7524831d6a160872f12a25d79a960 Reavo End <decidel@gmail.com> 1781176653 -0700\tpull --ff-only origin main: Fast-forward\n55c4e5933ea7524831d6a160872f12a25d79a960 8dc22ffcd3e83b3ff5bc6e0e491edc797ba59357 Reavo End <decidel@gmail.com> 1781270306 -0700\tcommit: docs(context): Gateway grill — endpoint type, Shell model extensions, adapter profiles, digest log-source revision\n8dc22ffcd3e83b3ff5bc6e0e491edc797ba59357 bd469b9b0f7645cd11239758cfc5a99feddb6f17 Reavo End <decidel@gmail.com> 1781273481 -0700\tcommit: docs(context): milestone-A mints — live role, keyword hints, spt rc; Gateway revival settled on existing machinery\nbd469b9b0f7645cd11239758cfc5a99feddb6f17 d338c2d8a36018ee5d21b548a40483c1cc07b1cc Reavo End <decidel@gmail.com> 1781274050 -0700\tcommit: docs(roadmap): M9 adapter customization & session surfaces — milestone entry + JIT plan\nd338c2d8a36018ee5d21b548a40483c1cc07b1cc 22a9b6fcbf2b11afc527d3847ee59c2045baee52 Reavo End <decidel@gmail.com> 1781391688 -0700\tpull --ff-only origin main: Fast-forward\n22a9b6fcbf2b11afc527d3847ee59c2045baee52 6a9ff4e9e12f010bbca954153330460260fd5151 Reavo End <decidel@gmail.com> 1781417917 -0700\tpull origin main: Fast-forward\n6a9ff4e9e12f010bbca954153330460260fd5151 0a2c377464ae317534c7391e0b29ea1c24670b78 Reavo End <decidel@gmail.com> 1781419650 -0700\tcommit: docs(changelog): drop internal ADR reference from v0.6.0 note (operator)\n0a2c377464ae317534c7391e0b29ea1c24670b78 06e61cdf9aee4bec860839c4a0a5c98bf090bd2d Reavo End <decidel@gmail.com> 1781438239 -0700\tcommit: docs(m12): intake candidates + spt endpoint run picker spec (spt-claude-code scoping)\n06e61cdf9aee4bec860839c4a0a5c98bf090bd2d 39fd73ef51eee1e27377b85746b3b6d61a4c1def Reavo End <decidel@gmail.com> 1781438755 -0700\tcommit: docs(m12): JIT plan — bringup/picker/rc/adapter-UX, 5 waves + gate\n39fd73ef51eee1e27377b85746b3b6d61a4c1def 2da139504d9abeaae4ad0430b47a0dc1f64648ef Reavo End <decidel@gmail.com> 1781502059 -0700\tmerge m12-w1-bringup-rc: Fast-forward\n2da139504d9abeaae4ad0430b47a0dc1f64648ef cdfd900f44e6cb04d393b4b14f0ddd13d91c25f9 Reavo End <decidel@gmail.com> 1781507742 -0700\tcommit: ci: raise `test` job timeout 25→50 min for the slow Linux runner\ncdfd900f44e6cb04d393b4b14f0ddd13d91c25f9 0ffc3dbfaee03f5471940ecbdd2abbe843a53363 Reavo End <decidel@gmail.com> 1781508466 -0700\tcommit: ci: parallel test execution (nextest) + mold linker on the Linux runner\n0ffc3dbfaee03f5471940ecbdd2abbe843a53363 b976c4d4a1e056cd363dbd80ed78682570a6cca7 Reavo End <decidel@gmail.com> 1781510412 -0700\tcommit: ci: share mold artifacts across both Linux jobs (n1-gate reuses test's build)\nb976c4d4a1e056cd363dbd80ed78682570a6cca7 48883b3dc43e0d620b3fbea0914affb67c2eca4b Reavo End <decidel@gmail.com> 1781511626 -0700\tcommit: test(attach): fix resize test hang under parallel nextest + add kill-timeout\n48883b3dc43e0d620b3fbea0914affb67c2eca4b e08cea0dd604d378c77233876ff9f9d895d6ff7e Reavo End <decidel@gmail.com> 1781512506 -0700\tcommit: fix(clippy): clear 4 Linux clippy lints (first Linux clippy run on M12 code)\ne08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\ncb0560052c7392d386d9e68db359bbbf3138dcfe 1fc700baa4fd92c86c43744469cbd074dc4c23fa Reavo End <decidel@gmail.com> 1781529085 -0700\tpull --ff-only origin main: Fast-forward\n1fc700baa4fd92c86c43744469cbd074dc4c23fa b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf Reavo End <decidel@gmail.com> 1781583167 -0700\tpull --ff-only: Fast-forward\nb5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf 44ed949ed8b890ed7002a76d4a8454489b6ffc64 Reavo End <decidel@gmail.com> 1781583592 -0700\tcommit: docs: add adapter patterns & pitfalls field guide\n44ed949ed8b890ed7002a76d4a8454489b6ffc64 93754408a9612092dc85597cbe11efa28642f2fa Reavo End <decidel@gmail.com> 1781585211 -0700\tcommit: docs: document the post-install adapter-activation step (F-005)\n93754408a9612092dc85597cbe11efa28642f2fa ede1932cebc04d2edc6952b931195796dfb9128b Reavo End <decidel@gmail.com> 1781585968 -0700\tcommit: docs: frame adapter guidance positively (state what to do)\nede1932cebc04d2edc6952b931195796dfb9128b 3b458b1c850bd7c372c4d4d8a3a967a4365408d4 Reavo End <decidel@gmail.com> 1781587881 -0700\tcommit: docs: document the --github distribution-repo shape (F-005 follow-up)\n3b458b1c850bd7c372c4d4d8a3a967a4365408d4 94bda1e4fcb6b33c7103dc1a6895a17e003438a1 Reavo End <decidel@gmail.com> 1781589748 -0700\tcommit: feat(adapter): spt adapter add --release <user/repo> — release-archive acquisition (REQ-INSTALL-9)\n94bda1e4fcb6b33c7103dc1a6895a17e003438a1 61340db628198ec1a585b24aeb6f2be43bef729d Reavo End <decidel@gmail.com> 1781592972 -0700\tcommit: plan: v0.7.4 build (gh_release update avenue + adapter-binary install-dir resolution)\n61340db628198ec1a585b24aeb6f2be43bef729d e6722b86a963b4a5c41a6fcd820b26617686fda7 Reavo End <decidel@gmail.com> 1781592996 -0700\tpull --rebase origin main (finish): refs/heads/main onto 22a9c58cfb7b5259dff99670012108095cb3e376\ne6722b86a963b4a5c41a6fcd820b26617686fda7 ac4dad58f0ec68ce7ee6f487a1f0e077223daaae Reavo End <decidel@gmail.com> 1781594462 -0700\tcommit: fix(installer): Windows at-logon task runs the daemon in the background (REQ-INSTALL-10)\nac4dad58f0ec68ce7ee6f487a1f0e077223daaae 6306d3390e2c9fa2b272acb33b18aa580bb2fe42 Reavo End <decidel@gmail.com> 1781598706 -0700\tcommit: feat(runtime): resolve adapter command program against the install dir before PATH (REQ-INSTALL-11)\n6306d3390e2c9fa2b272acb33b18aa580bb2fe42 2d6bcdf14cb573b3abe6a2b43387db77256f01ab Reavo End <decidel@gmail.com> 1781602351 -0700\tcommit: feat(update): gh_release adapter [update] avenue with optional signing (REQ-UPD-9)\n2d6bcdf14cb573b3abe6a2b43387db77256f01ab ec8a02e97eb503b81363195d81c948213f67275f Reavo End <decidel@gmail.com> 1781602467 -0700\tcommit: plan: record v0.7.4 D/B/A built+gated; C deferred; docs batch remaining\nec8a02e97eb503b81363195d81c948213f67275f 66dd239d3687e229c8bb30c7f3dcb60ebbaadd22 Reavo End <decidel@gmail.com> 1781604748 -0700\tcommit: feat(api): resolve manifest+install_dir from --adapter via the registry when --manifest omitted (REQ-API-4)\n66dd239d3687e229c8bb30c7f3dcb60ebbaadd22 c4337939803177e2ed0a67808960a67285747ca7 Reavo End <decidel@gmail.com> 1781606926 -0700\tcommit: docs: v0.8.0 feature docs — gh_release avenue, install-dir resolution, api manifest-from-adapter\nc4337939803177e2ed0a67808960a67285747ca7 c3bdccfdc82c7dc840d92a96004f1833c12e134c Reavo End <decidel@gmail.com> 1781608151 -0700\tmerge origin/main: Fast-forward\nc3bdccfdc82c7dc840d92a96004f1833c12e134c 70294ef2c20f3b07866eb97f9e9ac60ee61460e9 Reavo End <decidel@gmail.com> 1781610425 -0700\tpull --ff-only origin main: Fast-forward\n70294ef2c20f3b07866eb97f9e9ac60ee61460e9 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1782020232 -0700\tpull: fast-forward\n86f20ac831033a20bd5e096b1c318a75249179f3 c84daff6a172808749c0a63518300be116334ae8 Reavo End <decidel@gmail.com> 1782101195 -0700\tcommit (cherry-pick): docs(forum): ratify always-on endpoint kind + #-sigil addressing (ADR-0023)\nc84daff6a172808749c0a63518300be116334ae8 bf1c724236b51915ccdfb4079f170d821570c919 Reavo End <decidel@gmail.com> 1782128689 -0700\tcommit: Adjust instructions\nbf1c724236b51915ccdfb4079f170d821570c919 7de5cfbfa185a83e1dff420716d1db0acbdbff8c Reavo End <decidel@gmail.com> 1782128698 -0700\tpull --ff --recurse-submodules --progress origin: Merge made by the 'ort' strategy.\n7de5cfbfa185a83e1dff420716d1db0acbdbff8c 2bb9c8f7fd85551e5f19c191292c80f778e54346 Reavo End <decidel@gmail.com> 1782184692 -0700\tpull: fast-forward\n2bb9c8f7fd85551e5f19c191292c80f778e54346 d7738beba6bf9fa81999172ba579258d67587178 Reavo End <decidel@gmail.com> 1782213399 -0700\tpull: fast-forward\nd7738beba6bf9fa81999172ba579258d67587178 365fb0cbe8422795d03bbc2fe1efdd267a613b81 Reavo End <decidel@gmail.com> 1782254738 -0700\tpull: fast-forward\n365fb0cbe8422795d03bbc2fe1efdd267a613b81 90852bf1bfb8832bf33a27ee58541be2618ec592 Reavo End <decidel@gmail.com> 1782281891 -0700\tpull: fast-forward\n90852bf1bfb8832bf33a27ee58541be2618ec592 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)\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 9515e2ea26ea2b081e1f51f4abb0be4f718a96ee Reavo End <decidel@gmail.com> 1780222436 -0700\tcommit (initial): docs: initial spt-core planning artifacts\n9515e2ea26ea2b081e1f51f4abb0be4f718a96ee 1d30876dbb90d1df77f434d26604528adaeeccb6 Reavo End <decidel@gmail.com> 1780224876 -0700\tcommit: docs: shell model, sleep/wake, and from-shell owner-shutdown\n1d30876dbb90d1df77f434d26604528adaeeccb6 293e33774dc77b4f2e80cd2a56a921624776ed72 Reavo End <decidel@gmail.com> 1780227620 -0700\tcommit: docs: cross-node context sync (P2P + opt-in GitHub hub)\n293e33774dc77b4f2e80cd2a56a921624776ed72 b73269976256fd4ecf67bd8222a9f71b32b7b775 Reavo End <decidel@gmail.com> 1780228153 -0700\tcommit: docs: dual-audience documentation strategy\nb73269976256fd4ecf67bd8222a9f71b32b7b775 9d064c2f5a9b44161b9c55fc1c1b74d14204b81a Reavo End <decidel@gmail.com> 1780229594 -0700\tcommit: docs: concrete doc templates from firsthand artifact pass\n9d064c2f5a9b44161b9c55fc1c1b74d14204b81a a71c14559c221b0841f53d6180d410e119e6c2c9 Reavo End <decidel@gmail.com> 1780273857 -0700\tcommit: docs: psyche context & memory model (17.3 + memformat redesign)\na71c14559c221b0841f53d6180d410e119e6c2c9 95353b7035858b79fb4cbd9cd3823d7195179fd1 Reavo End <decidel@gmail.com> 1780276646 -0700\tcommit: docs: dormancy policy, suspend surface, boundary communes, spt refresh (17.4)\n95353b7035858b79fb4cbd9cd3823d7195179fd1 51b485d106befb2a5607ca7ae13a30b695157f99 Reavo End <decidel@gmail.com> 1780278010 -0700\tcommit: docs: consent & security-gate model (17.5) + refresh boundary resolution\n51b485d106befb2a5607ca7ae13a30b695157f99 7e3826f305393790fcf608bac064403da01ec292 Reavo End <decidel@gmail.com> 1780278964 -0700\tcommit: docs: roadmap — path to fruition (review → spikes → M0-M5)\n7e3826f305393790fcf608bac064403da01ec292 a7128b6c33bdf83e3d2379926285250cadcfa41d Reavo End <decidel@gmail.com> 1780280308 -0700\tcommit: build: adopt traceable-reqs with a traceability contract\na7128b6c33bdf83e3d2379926285250cadcfa41d 94ff79d09693f0e86fdb69abec144b4f577c5f8e Reavo End <decidel@gmail.com> 1780283594 -0700\tcommit: docs: Stage A red-team + Spike #1 (broker/brain handoff validated)\n94ff79d09693f0e86fdb69abec144b4f577c5f8e dbb21e48f79e6334037ca7db678ead224b98a4bd Reavo End <decidel@gmail.com> 1780283971 -0700\tcommit: docs: Spike #2 (Iroh smoke test) PASS + roadmap progress\ndbb21e48f79e6334037ca7db678ead224b98a4bd 48ec6d42b1e141b36190c99c6480dfe1d3d27c61 Reavo End <decidel@gmail.com> 1780293734 -0700\tcommit: docs: Spike #2 cross-machine result — two-host Iroh QUIC PASS\n48ec6d42b1e141b36190c99c6480dfe1d3d27c61 0dfff6a650ffcd098c489bc55efd3d235982a8db Reavo End <decidel@gmail.com> 1780294353 -0700\tcommit: docs: Spike #2 — native iroh NAT traversal across two separate networks PASS\n0dfff6a650ffcd098c489bc55efd3d235982a8db 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780295246 -0700\tcommit: feat(spt-proto): M0 first brick — envelope codec + parser, 2 hazards covered\n4fa29498d2552f277e918219f74d9c75183a6921 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974019 -0700\tmerge dev-freeform: Fast-forward\n35d1672fdf999a4fc6e58fcd04ff5c953452beb0 7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f Reavo End <decidel@gmail.com> 1780975158 -0700\tmerge dev-freeform: Fast-forward\n7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 Reavo End <decidel@gmail.com> 1780976867 -0700\tmerge dev-freeform: Fast-forward\na81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780978106 -0700\tmerge dev-freeform: Fast-forward\n9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tmerge dev-freeform: Fast-forward\n516465c1cbf959ed89c77464b38e9c4ed5142ad9 c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f Reavo End <decidel@gmail.com> 1780982768 -0700\tmerge dev-freeform: Fast-forward\nc5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780997108 -0700\tmerge dev-freeform: Fast-forward\n4e774a391077a9bdce29906aaed6de804da5de1b cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780999401 -0700\tmerge dev-freeform: Fast-forward\ncabdc614fc0416e3a57de0a21b229db485089320 44497f466c89b104de2a10816c30f5b68c8fd28a Reavo End <decidel@gmail.com> 1781052390 -0700\tcommit: docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints\n44497f466c89b104de2a10816c30f5b68c8fd28a 2769c8801c533add7e1522f0bd8224d11688e8d2 Reavo End <decidel@gmail.com> 1781052473 -0700\tcommit: docs(deferred): two v0.3.2-verify deferrals — test broker socket flake + fetch single→set trap\n2769c8801c533add7e1522f0bd8224d11688e8d2 65c2b0ac4d9e7e386f7e49e10da04756f02ef2b8 Reavo End <decidel@gmail.com> 1781052653 -0700\tcommit: docs(plan): RESTORATION-PLAN.md — broker/brain process-isolation build (D1–D7)\n65c2b0ac4d9e7e386f7e49e10da04756f02ef2b8 f1f798352d548da49999fbbec28f106d98ec47cc Reavo End <decidel@gmail.com> 1781053337 -0700\tcommit: docs(restoration): doyle vet amendments 1-4 — D1 ordering fix + status ratification\nf1f798352d548da49999fbbec28f106d98ec47cc 0c954353b256f46e45a600be7a7fb1f3768e9d30 Reavo End <decidel@gmail.com> 1781055021 -0700\tcommit: feat(daemon): restoration D1 — broker/brain process-split skeleton (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\n0c954353b256f46e45a600be7a7fb1f3768e9d30 f2a17a9cfd51e80b39ad2238e36866f1bdac6a37 Reavo End <decidel@gmail.com> 1781057387 -0700\tcommit: feat(daemon): restoration D2-1 — net consumers move broker→brain (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\nf2a17a9cfd51e80b39ad2238e36866f1bdac6a37 a9fe443757267897bdbe3dc22e11ca22403df97d Reavo End <decidel@gmail.com> 1781058108 -0700\tcommit: feat(daemon): restoration D2-2 — shellwake wake-host moves broker→brain; D2 scope correction (REQ-HAZARD-BROKER-PROCESS-ISOLATION)\na9fe443757267897bdbe3dc22e11ca22403df97d 7398d7c5f0f08032c00aca25e9e8d9946e1c8890 Reavo End <decidel@gmail.com> 1781058533 -0700\tcommit: docs(restoration): defer V6 N-1 compat-test scaffold D2→D3 (first additive verb lands in D3)\n7398d7c5f0f08032c00aca25e9e8d9946e1c8890 e3af674051c4394630f07f710cfdac344887a2d1 Reavo End <decidel@gmail.com> 1781059368 -0700\tcommit: docs(restoration): D3 task plan — supervision anchor + update trigger (Q2/Q3/V2)\ne3af674051c4394630f07f710cfdac344887a2d1 1f9fe89e4f09fcf38b3f2fca134d1d16ba2e14ba Reavo End <decidel@gmail.com> 1781059572 -0700\tcommit: feat(daemon): restoration D3-1 — seed anchor survives a brain cycle (Q2)\n1f9fe89e4f09fcf38b3f2fca134d1d16ba2e14ba d43c3a68584486705f3a6684e527af7de5ed89ee Reavo End <decidel@gmail.com> 1781059966 -0700\tcommit: feat(daemon): restoration D3-2 — broker generation custody + versioned brain argv (Q2/V2)\nd43c3a68584486705f3a6684e527af7de5ed89ee 4d8f34c815142c44c2329521284a06d67290b083 Reavo End <decidel@gmail.com> 1781060768 -0700\tcommit: feat(daemon): restoration D3-3 — real brain-process restart on update (Q3)\n4d8f34c815142c44c2329521284a06d67290b083 348a73919f61138c637cae33f27441cac1794a74 Reavo End <decidel@gmail.com> 1781060911 -0700\tcommit: feat(daemon): restoration D3-4 — V6 N-1 argv-compat harness scaffold\n348a73919f61138c637cae33f27441cac1794a74 c608227de29e22e60703cb2c39e60b97fef9bbab Reavo End <decidel@gmail.com> 1781071812 -0700\tcommit: docs(restoration): D4 task plan — multi-session cold-start resume (Q6)\nc608227de29e22e60703cb2c39e60b97fef9bbab 56d88b985c747648ca869f7614104d7bc0e509c4 Reavo End <decidel@gmail.com> 1781072516 -0700\tcommit: docs(restoration): fold doyle D4-plan vet amendments (cursor-advance precision)\n56d88b985c747648ca869f7614104d7bc0e509c4 fea8eaa3ef19ff72dc39041b356685a06fb5f6ec Reavo End <decidel@gmail.com> 1781073132 -0700\tcommit: feat(daemon): restoration D4-1 — broker cursor-of-record (Q6)\nfea8eaa3ef19ff72dc39041b356685a06fb5f6ec 5eb64ff0962e99652309f0e058a2c3e7a53fe57e Reavo End <decidel@gmail.com> 1781074322 -0700\tcommit: docs(restoration): fold doyle D4-2 watch-item — cursor clamp discipline\n5eb64ff0962e99652309f0e058a2c3e7a53fe57e 08afcb90df22bb6cf25ddf03b11be69bf1cbcdd2 Reavo End <decidel@gmail.com> 1781074337 -0700\tcommit: feat(daemon): restoration D4-2 — multi-session cold-start resume (Q6)\n08afcb90df22bb6cf25ddf03b11be69bf1cbcdd2 35b925e9307abbd5c997352226cbf684bde322a7 Reavo End <decidel@gmail.com> 1781074446 -0700\tcommit: docs(daemon): restoration D4-3 — confirm + lock the BrainState-message retire (Q6)\n35b925e9307abbd5c997352226cbf684bde322a7 9a0a42998b946ddced09917804e1ad4d49b35e49 Reavo End <decidel@gmail.com> 1781074485 -0700\tcommit: docs(restoration): mark D4-1/D4-2/D4-3 DONE — D4 milestone complete\n9a0a42998b946ddced09917804e1ad4d49b35e49 612b1dee3dbb7c7acf01717adc9601ee639f5b57 Reavo End <decidel@gmail.com> 1781074927 -0700\tcommit: feat(daemon): restoration D4-2b — close the mixed-mode resume gap (Q6, amd 5)\n612b1dee3dbb7c7acf01717adc9601ee639f5b57 794031805faf5c54a6fab5ca538b641238847d76 Reavo End <decidel@gmail.com> 1781075872 -0700\tcommit: fix(test): D4-2b resume harness — sequential spawn/read (Linux CI flake)\n794031805faf5c54a6fab5ca538b641238847d76 48977a210c0db0b5e669407041d273dd519d5b68 Reavo End <decidel@gmail.com> 1781076046 -0700\tcommit: docs(daemon): record KH 6.9 — resume-mode blocking-wait output discard\n48977a210c0db0b5e669407041d273dd519d5b68 3055eb7b59f01673d770152576a2fac1cb6d8f4b Reavo End <decidel@gmail.com> 1781076633 -0700\tcommit: test(daemon): fix unlocked SPT_HOME mutation racing with_home tests (CI flake)\n3055eb7b59f01673d770152576a2fac1cb6d8f4b 78bbdc10e36de90bd6ffda554fa87b0a8b124fe3 Reavo End <decidel@gmail.com> 1781079306 -0700\tcommit: feat(daemon): restoration D5-1 — durable absolute-deadline primitive (Q4)\n78bbdc10e36de90bd6ffda554fa87b0a8b124fe3 8206ca2f4096e60c962cb34f84db9591ba759e00 Reavo End <decidel@gmail.com> 1781079326 -0700\tcommit (amend): feat(daemon): restoration D5-1 — durable absolute-deadline primitive (Q4)\n8206ca2f4096e60c962cb34f84db9591ba759e00 4f107b6e0b4cf62a9cb5eb26b42661691e55bbca Reavo End <decidel@gmail.com> 1781079544 -0700\tcommit: feat(daemon): restoration D5-2 — pulse loop on disk-anchored deadline (Q4/V4)\n4f107b6e0b4cf62a9cb5eb26b42661691e55bbca c4af6cf3df03274c9707a74c78da9a0ea0779788 Reavo End <decidel@gmail.com> 1781079658 -0700\tcommit: docs(daemon): restoration D5-3 — lock V3 one-shot rule + KH 6.10 (Q4)\nc4af6cf3df03274c9707a74c78da9a0ea0779788 97282eb010d054394975db3b0501c7a75c60daba Reavo End <decidel@gmail.com> 1781079694 -0700\tcommit: docs(restoration): mark D5-1/D5-2/D5-3 DONE — D5 milestone complete\n97282eb010d054394975db3b0501c7a75c60daba 2ba4fd7428e702aef331a29160602f4003426f6a Reavo End <decidel@gmail.com> 1781080359 -0700\tcommit: fix(daemon): D5-2b — pulse advance skips past now, no fire-storm after a stall\n2ba4fd7428e702aef331a29160602f4003426f6a bd8db6ae1d29e0c72cd8c5dedc410cf4f44f42fb Reavo End <decidel@gmail.com> 1781089202 -0700\tcommit: docs(restoration): D6 plan — readiness-gated auto-rollback (Q7/V1)\nbd8db6ae1d29e0c72cd8c5dedc410cf4f44f42fb 8859c9b1d004e5ab824a089eef4c07b4027df56b Reavo End <decidel@gmail.com> 1781089219 -0700\tcommit: feat(daemon): restoration D6-1 — two-phase applied record + gen-stamped ready (Q7)\n8859c9b1d004e5ab824a089eef4c07b4027df56b 79efc113073cf58ddd8135e9eb4a1fdfb1b50238 Reavo End <decidel@gmail.com> 1781091168 -0700\tcommit: feat(daemon): restoration D6-2 — readiness-gated supervisor + auto-rollback (Q7)\n79efc113073cf58ddd8135e9eb4a1fdfb1b50238 d375bae0b70d0883e8ec74ce4825b18585e24c1c Reavo End <decidel@gmail.com> 1781091468 -0700\tcommit: docs(daemon): restoration D6-3 — rollback-state-compat tripwire + KH 6.8 guard (Q7-V1)\nd375bae0b70d0883e8ec74ce4825b18585e24c1c ad15a1e39a16c84164e08fde18c8ac0c4e2463c6 Reavo End <decidel@gmail.com> 1781092587 -0700\tcommit: fix(daemon): restoration D6-2b — fold doyle's 3 D6 vet findings (Q7)\nad15a1e39a16c84164e08fde18c8ac0c4e2463c6 c81f9dc06ce868faab4eb6098a28bc97ed14a023 Reavo End <decidel@gmail.com> 1781127509 -0700\tcommit: docs(restoration): D7 plan — proof + REQ re-point + fleet verify (V5/V6/§9)\nc81f9dc06ce868faab4eb6098a28bc97ed14a023 a52f8c6331e368777348a14c703363afc0585183 Reavo End <decidel@gmail.com> 1781128024 -0700\tcommit: feat(daemon): restoration D7-1a — exe_hash breadcrumb on brain.ready (V5)\na52f8c6331e368777348a14c703363afc0585183 1fdf99f352270b87914983a52fd783f2535cf852 Reavo End <decidel@gmail.com> 1781129168 -0700\tcommit: feat(daemon): restoration D7-1b — process-level survival E2E onto a swapped binary (V5)\n1fdf99f352270b87914983a52fd783f2535cf852 0cf5eaec508f08faf42fa92119cd99bc9630248a Reavo End <decidel@gmail.com> 1781129992 -0700\tcommit: feat(daemon): restoration D7-1c — functional (not just held) survival probe (V5, doyle F1)\n0cf5eaec508f08faf42fa92119cd99bc9630248a 6269e3a6292cd1f89fcd68dd5049bdc4ed73f74c Reavo End <decidel@gmail.com> 1781131599 -0700\tcommit: fix(windows): suppress ephemeral console windows on test + brain-child spawns\n6269e3a6292cd1f89fcd68dd5049bdc4ed73f74c 04fd322fbf2bc784237ce8289f316c35761a5979 Reavo End <decidel@gmail.com> 1781131617 -0700\tcommit: feat(daemon): restoration D7-2 — new-brain × old-broker N-1 verb-surface gate (V6)\n04fd322fbf2bc784237ce8289f316c35761a5979 d239ae418c6352f4f3a32d5ec6d365cc2a8a3180 Reavo End <decidel@gmail.com> 1781131732 -0700\tcommit: chore(traceability): restoration D7-3 — activate BROKER-PROCESS-ISOLATION int + reconcile re-points\nd239ae418c6352f4f3a32d5ec6d365cc2a8a3180 f0919d3c09b12cfbf8c3b9840d2bcee199984a7f Reavo End <decidel@gmail.com> 1781132342 -0700\tcommit: fix(ci): restoration D7-2b — n1-gate windows uses pwsh, not powershell 5.1\nf0919d3c09b12cfbf8c3b9840d2bcee199984a7f fa5b54e4f2c8f8179eedea01500598753f42cdf7 Reavo End <decidel@gmail.com> 1781137670 -0700\tcommit: @\nfa5b54e4f2c8f8179eedea01500598753f42cdf7 2162ae759ea5021ff75ea8ae783f11ba8c8329ef Reavo End <decidel@gmail.com> 1781137687 -0700\tcommit (amend): chore(release): restoration D7-4 — v0.4.0 release prep (version + changelog)\n2162ae759ea5021ff75ea8ae783f11ba8c8329ef b0fdc015fd37172ab3782fcc71878cfa67da7b3d Reavo End <decidel@gmail.com> 1781138199 -0700\tcommit: ci: [twohost] — exercise the 2-node rig before the v0.4.0 tag (D7-4 gate)\nb0fdc015fd37172ab3782fcc71878cfa67da7b3d 190ad106e81678006b885372ba33bd0341a4f81a Reavo End <decidel@gmail.com> 1781144896 -0700\tcommit: docs(restoration): D7-4 — fleet field-run evidence appendix (v0.4.0 roll)\n190ad106e81678006b885372ba33bd0341a4f81a abe9e3470eacfc6329731accca59bbce093b9321 Reavo End <decidel@gmail.com> 1781145031 -0700\tcommit: docs(architecture): pump worker seam — frozen design + execution plan (arch card 1)\nabe9e3470eacfc6329731accca59bbce093b9321 610c176fb3a7394be8a7c6953f6c45534ed4287a Reavo End <decidel@gmail.com> 1781150784 -0700\tpull --ff-only: Fast-forward\n610c176fb3a7394be8a7c6953f6c45534ed4287a e0dbca122b42a268ad720dce3d4672699adb44cf Reavo End <decidel@gmail.com> 1781157876 -0700\tpull --ff-only: Fast-forward\ne0dbca122b42a268ad720dce3d4672699adb44cf 4d8b810733a70b43adf5c055802ebf8d06dd7c20 Reavo End <decidel@gmail.com> 1781160396 -0700\tpull --ff-only origin main: Fast-forward\n4d8b810733a70b43adf5c055802ebf8d06dd7c20 038257bb9c4a405244a6724beaf2ececb30e7111 Reavo End <decidel@gmail.com> 1781162576 -0700\tpull --ff-only origin main: Fast-forward\n038257bb9c4a405244a6724beaf2ececb30e7111 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781173992 -0700\tpull --ff-only origin main: Fast-forward\n725aebf647f8ebe4415fcfd2274488b41cc561f0 55c4e5933ea7524831d6a160872f12a25d79a960 Reavo End <decidel@gmail.com> 1781176653 -0700\tpull --ff-only origin main: Fast-forward\n55c4e5933ea7524831d6a160872f12a25d79a960 8dc22ffcd3e83b3ff5bc6e0e491edc797ba59357 Reavo End <decidel@gmail.com> 1781270306 -0700\tcommit: docs(context): Gateway grill — endpoint type, Shell model extensions, adapter profiles, digest log-source revision\n8dc22ffcd3e83b3ff5bc6e0e491edc797ba59357 bd469b9b0f7645cd11239758cfc5a99feddb6f17 Reavo End <decidel@gmail.com> 1781273481 -0700\tcommit: docs(context): milestone-A mints — live role, keyword hints, spt rc; Gateway revival settled on existing machinery\nbd469b9b0f7645cd11239758cfc5a99feddb6f17 d338c2d8a36018ee5d21b548a40483c1cc07b1cc Reavo End <decidel@gmail.com> 1781274050 -0700\tcommit: docs(roadmap): M9 adapter customization & session surfaces — milestone entry + JIT plan\nd338c2d8a36018ee5d21b548a40483c1cc07b1cc 22a9b6fcbf2b11afc527d3847ee59c2045baee52 Reavo End <decidel@gmail.com> 1781391688 -0700\tpull --ff-only origin main: Fast-forward\n22a9b6fcbf2b11afc527d3847ee59c2045baee52 6a9ff4e9e12f010bbca954153330460260fd5151 Reavo End <decidel@gmail.com> 1781417917 -0700\tpull origin main: Fast-forward\n6a9ff4e9e12f010bbca954153330460260fd5151 0a2c377464ae317534c7391e0b29ea1c24670b78 Reavo End <decidel@gmail.com> 1781419650 -0700\tcommit: docs(changelog): drop internal ADR reference from v0.6.0 note (operator)\n0a2c377464ae317534c7391e0b29ea1c24670b78 06e61cdf9aee4bec860839c4a0a5c98bf090bd2d Reavo End <decidel@gmail.com> 1781438239 -0700\tcommit: docs(m12): intake candidates + spt endpoint run picker spec (spt-claude-code scoping)\n06e61cdf9aee4bec860839c4a0a5c98bf090bd2d 39fd73ef51eee1e27377b85746b3b6d61a4c1def Reavo End <decidel@gmail.com> 1781438755 -0700\tcommit: docs(m12): JIT plan — bringup/picker/rc/adapter-UX, 5 waves + gate\n39fd73ef51eee1e27377b85746b3b6d61a4c1def 2da139504d9abeaae4ad0430b47a0dc1f64648ef Reavo End <decidel@gmail.com> 1781502059 -0700\tmerge m12-w1-bringup-rc: Fast-forward\n2da139504d9abeaae4ad0430b47a0dc1f64648ef cdfd900f44e6cb04d393b4b14f0ddd13d91c25f9 Reavo End <decidel@gmail.com> 1781507742 -0700\tcommit: ci: raise `test` job timeout 25→50 min for the slow Linux runner\ncdfd900f44e6cb04d393b4b14f0ddd13d91c25f9 0ffc3dbfaee03f5471940ecbdd2abbe843a53363 Reavo End <decidel@gmail.com> 1781508466 -0700\tcommit: ci: parallel test execution (nextest) + mold linker on the Linux runner\n0ffc3dbfaee03f5471940ecbdd2abbe843a53363 b976c4d4a1e056cd363dbd80ed78682570a6cca7 Reavo End <decidel@gmail.com> 1781510412 -0700\tcommit: ci: share mold artifacts across both Linux jobs (n1-gate reuses test's build)\nb976c4d4a1e056cd363dbd80ed78682570a6cca7 48883b3dc43e0d620b3fbea0914affb67c2eca4b Reavo End <decidel@gmail.com> 1781511626 -0700\tcommit: test(attach): fix resize test hang under parallel nextest + add kill-timeout\n48883b3dc43e0d620b3fbea0914affb67c2eca4b e08cea0dd604d378c77233876ff9f9d895d6ff7e Reavo End <decidel@gmail.com> 1781512506 -0700\tcommit: fix(clippy): clear 4 Linux clippy lints (first Linux clippy run on M12 code)\ne08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\ncb0560052c7392d386d9e68db359bbbf3138dcfe 1fc700baa4fd92c86c43744469cbd074dc4c23fa Reavo End <decidel@gmail.com> 1781529085 -0700\tpull --ff-only origin main: Fast-forward\n1fc700baa4fd92c86c43744469cbd074dc4c23fa b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf Reavo End <decidel@gmail.com> 1781583167 -0700\tpull --ff-only: Fast-forward\nb5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf 44ed949ed8b890ed7002a76d4a8454489b6ffc64 Reavo End <decidel@gmail.com> 1781583592 -0700\tcommit: docs: add adapter patterns & pitfalls field guide\n44ed949ed8b890ed7002a76d4a8454489b6ffc64 93754408a9612092dc85597cbe11efa28642f2fa Reavo End <decidel@gmail.com> 1781585211 -0700\tcommit: docs: document the post-install adapter-activation step (F-005)\n93754408a9612092dc85597cbe11efa28642f2fa ede1932cebc04d2edc6952b931195796dfb9128b Reavo End <decidel@gmail.com> 1781585968 -0700\tcommit: docs: frame adapter guidance positively (state what to do)\nede1932cebc04d2edc6952b931195796dfb9128b 3b458b1c850bd7c372c4d4d8a3a967a4365408d4 Reavo End <decidel@gmail.com> 1781587881 -0700\tcommit: docs: document the --github distribution-repo shape (F-005 follow-up)\n3b458b1c850bd7c372c4d4d8a3a967a4365408d4 94bda1e4fcb6b33c7103dc1a6895a17e003438a1 Reavo End <decidel@gmail.com> 1781589748 -0700\tcommit: feat(adapter): spt adapter add --release <user/repo> — release-archive acquisition (REQ-INSTALL-9)\n94bda1e4fcb6b33c7103dc1a6895a17e003438a1 61340db628198ec1a585b24aeb6f2be43bef729d Reavo End <decidel@gmail.com> 1781592972 -0700\tcommit: plan: v0.7.4 build (gh_release update avenue + adapter-binary install-dir resolution)\n61340db628198ec1a585b24aeb6f2be43bef729d e6722b86a963b4a5c41a6fcd820b26617686fda7 Reavo End <decidel@gmail.com> 1781592996 -0700\tpull --rebase origin main (finish): refs/heads/main onto 22a9c58cfb7b5259dff99670012108095cb3e376\ne6722b86a963b4a5c41a6fcd820b26617686fda7 ac4dad58f0ec68ce7ee6f487a1f0e077223daaae Reavo End <decidel@gmail.com> 1781594462 -0700\tcommit: fix(installer): Windows at-logon task runs the daemon in the background (REQ-INSTALL-10)\nac4dad58f0ec68ce7ee6f487a1f0e077223daaae 6306d3390e2c9fa2b272acb33b18aa580bb2fe42 Reavo End <decidel@gmail.com> 1781598706 -0700\tcommit: feat(runtime): resolve adapter command program against the install dir before PATH (REQ-INSTALL-11)\n6306d3390e2c9fa2b272acb33b18aa580bb2fe42 2d6bcdf14cb573b3abe6a2b43387db77256f01ab Reavo End <decidel@gmail.com> 1781602351 -0700\tcommit: feat(update): gh_release adapter [update] avenue with optional signing (REQ-UPD-9)\n2d6bcdf14cb573b3abe6a2b43387db77256f01ab ec8a02e97eb503b81363195d81c948213f67275f Reavo End <decidel@gmail.com> 1781602467 -0700\tcommit: plan: record v0.7.4 D/B/A built+gated; C deferred; docs batch remaining\nec8a02e97eb503b81363195d81c948213f67275f 66dd239d3687e229c8bb30c7f3dcb60ebbaadd22 Reavo End <decidel@gmail.com> 1781604748 -0700\tcommit: feat(api): resolve manifest+install_dir from --adapter via the registry when --manifest omitted (REQ-API-4)\n66dd239d3687e229c8bb30c7f3dcb60ebbaadd22 c4337939803177e2ed0a67808960a67285747ca7 Reavo End <decidel@gmail.com> 1781606926 -0700\tcommit: docs: v0.8.0 feature docs — gh_release avenue, install-dir resolution, api manifest-from-adapter\nc4337939803177e2ed0a67808960a67285747ca7 c3bdccfdc82c7dc840d92a96004f1833c12e134c Reavo End <decidel@gmail.com> 1781608151 -0700\tmerge origin/main: Fast-forward\nc3bdccfdc82c7dc840d92a96004f1833c12e134c 70294ef2c20f3b07866eb97f9e9ac60ee61460e9 Reavo End <decidel@gmail.com> 1781610425 -0700\tpull --ff-only origin main: Fast-forward\n70294ef2c20f3b07866eb97f9e9ac60ee61460e9 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1782020232 -0700\tpull: fast-forward\n86f20ac831033a20bd5e096b1c318a75249179f3 c84daff6a172808749c0a63518300be116334ae8 Reavo End <decidel@gmail.com> 1782101195 -0700\tcommit (cherry-pick): docs(forum): ratify always-on endpoint kind + #-sigil addressing (ADR-0023)\nc84daff6a172808749c0a63518300be116334ae8 bf1c724236b51915ccdfb4079f170d821570c919 Reavo End <decidel@gmail.com> 1782128689 -0700\tcommit: Adjust instructions\nbf1c724236b51915ccdfb4079f170d821570c919 7de5cfbfa185a83e1dff420716d1db0acbdbff8c Reavo End <decidel@gmail.com> 1782128698 -0700\tpull --ff --recurse-submodules --progress origin: Merge made by the 'ort' strategy.\n7de5cfbfa185a83e1dff420716d1db0acbdbff8c 2bb9c8f7fd85551e5f19c191292c80f778e54346 Reavo End <decidel@gmail.com> 1782184692 -0700\tpull: fast-forward\n2bb9c8f7fd85551e5f19c191292c80f778e54346 d7738beba6bf9fa81999172ba579258d67587178 Reavo End <decidel@gmail.com> 1782213399 -0700\tpull: fast-forward\nd7738beba6bf9fa81999172ba579258d67587178 365fb0cbe8422795d03bbc2fe1efdd267a613b81 Reavo End <decidel@gmail.com> 1782254738 -0700\tpull: fast-forward\n365fb0cbe8422795d03bbc2fe1efdd267a613b81 90852bf1bfb8832bf33a27ee58541be2618ec592 Reavo End <decidel@gmail.com> 1782281891 -0700\tpull: fast-forward\n90852bf1bfb8832bf33a27ee58541be2618ec592 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)\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\main"}}},"isError":false,"timestamp":1783730487687}}
{"type":"message","id":"6dedb847","parentId":"6f71d6e1","timestamp":"2026-07-11T00:41:27.688Z","message":{"role":"toolResult","toolCallId":"call_HAf1FTSSMkDOurxQmSQzU6v7|fc_08496348794f7e09016a5191383d1881988dbc4b1f76a53010","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 8aac39b7a20edb84101f05ad9514800592fd81c8 Reavo End <decidel@gmail.com> 1783504443 -0700\tbranch: Created from origin/main\n8aac39b7a20edb84101f05ad9514800592fd81c8 ddf7f8e39196fc93dfea06691e624a486d2bc308 Reavo End <decidel@gmail.com> 1783504778 -0700\tcommit: fix(inject): re-arm the Layer-1 settle-gate per delivery on an observable PTY\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 8aac39b7a20edb84101f05ad9514800592fd81c8 Reavo End <decidel@gmail.com> 1783504443 -0700\tbranch: Created from origin/main\n8aac39b7a20edb84101f05ad9514800592fd81c8 ddf7f8e39196fc93dfea06691e624a486d2bc308 Reavo End <decidel@gmail.com> 1783504778 -0700\tcommit: fix(inject): re-arm the Layer-1 settle-gate per delivery on an observable PTY\n","startLine":1,"lineNumbers":[1,2,3]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\fix\\inject-settle-rearm"}}},"isError":false,"timestamp":1783730487688}}
{"type":"message","id":"0c3d7562","parentId":"6dedb847","timestamp":"2026-07-11T00:41:35.720Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning to tag recent hashes**\n\n<!-- -->"},{"type":"toolCall","id":"call_LkpPHexAU87IMetIu8bp2ipS|fc_08496348794f7e09016a5191404bb08198860cde8896ced388","name":"read","arguments":{"i":"Read recent tag ref","path":"spt-core/.git/refs/tags/v0.30.2","selector":"raw"}},{"type":"toolCall","id":"call_5dk4owaZl2MU1jNHp7R0Pt93|fc_08496348794f7e09016a5191404bc081989724c962d0c86d3d","name":"read","arguments":{"i":"Read recent tag ref","path":"spt-core/.git/refs/tags/v0.30.3","selector":"raw"}},{"type":"toolCall","id":"call_IdbDtpuGrMUCOOqRGg8eRgAz|fc_08496348794f7e09016a5191404bc881989c721a45d04d584b","name":"read","arguments":{"i":"Read recent tag ref","path":"spt-core/.git/refs/tags/v0.30.4","selector":"raw"}},{"type":"toolCall","id":"call_UnxWlTafIHmjFAAwPoRHsJEv|fc_08496348794f7e09016a5191404bd08198a622cee1ec96e9a8","name":"read","arguments":{"i":"Read recent tag ref","path":"spt-core/.git/refs/tags/v0.30.5","selector":"raw"}},{"type":"toolCall","id":"call_OoyDkyNJotD44LImoCHa0LI2|fc_08496348794f7e09016a5191404bd48198b1410020c5a43c3a","name":"read","arguments":{"i":"Read recent tag ref","path":"spt-core/.git/refs/tags/v0.30.6","selector":"raw"}},{"type":"toolCall","id":"call_qC5CDICUmkQrxuMsd1hSiYgV|fc_08496348794f7e09016a5191404bdc8198890a246672d7c45c","name":"grep","arguments":{"i":"Find recent release commits","pattern":"v0\\.30\\.[2-6]|update-wedge|bounded\\+cancelable|resume re-attaches|cursor-only","path":"spt-core/.git/logs","selector":null,"case":false,"gitignore":false,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":15844,"output":300,"cacheRead":60928,"cacheWrite":0,"totalTokens":77072,"reasoningTokens":14,"cost":{"input":0.07922000000000001,"output":0.009000000000000001,"cacheRead":0.030463999999999998,"cacheWrite":0,"total":0.11868400000000001}},"stopReason":"toolUse","timestamp":1783730487728,"responseId":"resp_08496348794f7e09016a5191387fb08198a2a3745e652e7dcf","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51913ad80881988d931a4caa3ae518","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFAPG-KLCC6xI3tyamEVWI21QFWTyPWGiPgczI6Lc-wFNc8mvxj7ATykjnY0Siq1oq_YOGFgvfBielG9gPsl35N4m7wgiq3bpKC8rBhDduSoqho03fGxpphhbUrZzXzlikypiuPIBVUXVFMljrSESmj1tLWm0QPvCnrYPMP9VbOEngXwMijW1WOtVKjfIeDJAJLrXwDUvx-_zr4iqRmtWG1vX1WIYSXEtO544fj2FttlxMoi-fgNVtoFt7VXU8OzhRfTX1eifLF1AjghZUCz9rXacXQm39jJsHIhEfoS3mBVZyOvWR2ib8JQF5SB_cfqmRqLiTlj28z54utITTpKEF6y-NYya11b6IFPHFdYKmTtHkq6UUlBUbjSYzu94bWImKBl8sSirkV-edC0SccrZWO6uFP7oSLTRO0bLnBXAvi0oeSR_NgVvB63zs5iLHcDkba2698ShLaAe0K1_tUc9po4LVx4IGMgyCSWwQFJQKnn0I6_kQlMLuLfwuf-NIjGYuGSLHDz9pB0G_4lL8q2ZypWv8egKNDZHJ5nqAg9sDoSEtXZD_NrGD6mIshFlXSYGF-UNPzPepy-xY6Ee-W54ejCT7nImrwPYiYbszwlId2klsu6uP43lCLMTT33Q02cOYwZLQ5JAC1T1eZeu5IPjC1hulckK1Wg_fUdNrvauVvWk2aXz1v4Dhig3Fsali_bDopztQstg9nsXgWYa4efw0F8UvkJVG3Bc2fHjittGiX5tk7RXuGnIW12LMgskJZhUsccaYbAHl8mdW7D7xVV4vfnz8lWNwrjerd9u7Wrimr3hst4k-0UMuP96s5Sf5JPxegqcyDRNkXW0GyPnISC6mCE4H8XatHx8wBMDv1afRmujMOOtJlAyDFNyrFAo0rIwhtAEwqPT20IDu6i-VPorQ0BIw0HHvzqsfqBdAaojCD8VSpYE2lxsiHEq9Awo70tENel9K2Iukc8iDzDoLAjSU6R28g7mr9crSwnrAMJOJAG0I=","summary":[{"type":"summary_text","text":"**Planning to tag recent hashes**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a5191404bb08198860cde8896ced388","type":"function_call","status":"completed","arguments":"{\"i\":\"Read recent tag ref\",\"path\":\"spt-core/.git/refs/tags/v0.30.2\",\"selector\":\"raw\"}","call_id":"call_LkpPHexAU87IMetIu8bp2ipS","name":"read"},{"id":"fc_08496348794f7e09016a5191404bc081989724c962d0c86d3d","type":"function_call","status":"completed","arguments":"{\"i\":\"Read recent tag ref\",\"path\":\"spt-core/.git/refs/tags/v0.30.3\",\"selector\":\"raw\"}","call_id":"call_5dk4owaZl2MU1jNHp7R0Pt93","name":"read"},{"id":"fc_08496348794f7e09016a5191404bc881989c721a45d04d584b","type":"function_call","status":"completed","arguments":"{\"i\":\"Read recent tag ref\",\"path\":\"spt-core/.git/refs/tags/v0.30.4\",\"selector\":\"raw\"}","call_id":"call_IdbDtpuGrMUCOOqRGg8eRgAz","name":"read"},{"id":"fc_08496348794f7e09016a5191404bd08198a622cee1ec96e9a8","type":"function_call","status":"completed","arguments":"{\"i\":\"Read recent tag ref\",\"path\":\"spt-core/.git/refs/tags/v0.30.5\",\"selector\":\"raw\"}","call_id":"call_UnxWlTafIHmjFAAwPoRHsJEv","name":"read"},{"id":"fc_08496348794f7e09016a5191404bd48198b1410020c5a43c3a","type":"function_call","status":"completed","arguments":"{\"i\":\"Read recent tag ref\",\"path\":\"spt-core/.git/refs/tags/v0.30.6\",\"selector\":\"raw\"}","call_id":"call_OoyDkyNJotD44LImoCHa0LI2","name":"read"},{"id":"fc_08496348794f7e09016a5191404bdc8198890a246672d7c45c","type":"function_call","status":"completed","arguments":"{\"i\":\"Find recent release commits\",\"pattern\":\"v0\\\\.30\\\\.[2-6]|update-wedge|bounded\\\\+cancelable|resume re-attaches|cursor-only\",\"path\":\"spt-core/.git/logs\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_qC5CDICUmkQrxuMsd1hSiYgV","name":"grep"}]},"duration":7992.1408999999985,"ttft":2483.719299999997,"contextSnapshot":{"promptTokens":76772,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LkpPHexAU87IMetIu8bp2ipS|fc_08496348794f7e09016a5191404bb08198860cde8896ced388","toolName":"read","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/refs/tags/v0.30.2"},"intent":"Read recent tag ref"},"id":"9a931863","parentId":"0c3d7562","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5dk4owaZl2MU1jNHp7R0Pt93|fc_08496348794f7e09016a5191404bc081989724c962d0c86d3d","toolName":"read","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/refs/tags/v0.30.3"},"intent":"Read recent tag ref"},"id":"fc4ea5b6","parentId":"9a931863","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_IdbDtpuGrMUCOOqRGg8eRgAz|fc_08496348794f7e09016a5191404bc881989c721a45d04d584b","toolName":"read","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/refs/tags/v0.30.4"},"intent":"Read recent tag ref"},"id":"e05b149f","parentId":"fc4ea5b6","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UnxWlTafIHmjFAAwPoRHsJEv|fc_08496348794f7e09016a5191404bd08198a622cee1ec96e9a8","toolName":"read","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/refs/tags/v0.30.5"},"intent":"Read recent tag ref"},"id":"7d41d50b","parentId":"e05b149f","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_OoyDkyNJotD44LImoCHa0LI2|fc_08496348794f7e09016a5191404bd48198b1410020c5a43c3a","toolName":"read","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/refs/tags/v0.30.6"},"intent":"Read recent tag ref"},"id":"d6c94c4a","parentId":"7d41d50b","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_qC5CDICUmkQrxuMsd1hSiYgV|fc_08496348794f7e09016a5191404bdc8198890a246672d7c45c","toolName":"grep","startedAt":"2026-07-11T00:41:35.721Z","args":{"path":"spt-core/.git/logs"},"intent":"Find recent release commits"},"id":"bf7f2353","parentId":"d6c94c4a","timestamp":"2026-07-11T00:41:35.721Z"}
{"type":"message","id":"80a4362c","parentId":"bf7f2353","timestamp":"2026-07-11T00:41:35.733Z","message":{"role":"toolResult","toolCallId":"call_IdbDtpuGrMUCOOqRGg8eRgAz|fc_08496348794f7e09016a5191404bc881989c721a45d04d584b","toolName":"read","content":[{"type":"text","text":"b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa\n"}],"details":{"displayContent":{"text":"b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.30.4"}}},"isError":false,"timestamp":1783730495732}}
{"type":"message","id":"9ed0bd2e","parentId":"80a4362c","timestamp":"2026-07-11T00:41:35.733Z","message":{"role":"toolResult","toolCallId":"call_UnxWlTafIHmjFAAwPoRHsJEv|fc_08496348794f7e09016a5191404bd08198a622cee1ec96e9a8","toolName":"read","content":[{"type":"text","text":"0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047\n"}],"details":{"displayContent":{"text":"0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.30.5"}}},"isError":false,"timestamp":1783730495733}}
{"type":"message","id":"2af0743c","parentId":"9ed0bd2e","timestamp":"2026-07-11T00:41:35.733Z","message":{"role":"toolResult","toolCallId":"call_5dk4owaZl2MU1jNHp7R0Pt93|fc_08496348794f7e09016a5191404bc081989724c962d0c86d3d","toolName":"read","content":[{"type":"text","text":"ade41be9d865b43f92a9c9017507734d8a9edd55\n"}],"details":{"displayContent":{"text":"ade41be9d865b43f92a9c9017507734d8a9edd55\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.30.3"}}},"isError":false,"timestamp":1783730495733}}
{"type":"message","id":"31f45853","parentId":"2af0743c","timestamp":"2026-07-11T00:41:35.733Z","message":{"role":"toolResult","toolCallId":"call_LkpPHexAU87IMetIu8bp2ipS|fc_08496348794f7e09016a5191404bb08198860cde8896ced388","toolName":"read","content":[{"type":"text","text":"8e5ff889e0a4fa9a907801dc861904608cbc8e65\n"}],"details":{"displayContent":{"text":"8e5ff889e0a4fa9a907801dc861904608cbc8e65\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.30.2"}}},"isError":false,"timestamp":1783730495733}}
{"type":"message","id":"88230471","parentId":"31f45853","timestamp":"2026-07-11T00:41:35.734Z","message":{"role":"toolResult","toolCallId":"call_OoyDkyNJotD44LImoCHa0LI2|fc_08496348794f7e09016a5191404bd48198b1410020c5a43c3a","toolName":"read","content":[{"type":"text","text":"d73e19e12f7192a08d6ee502d21d03809a57a921\n"}],"details":{"displayContent":{"text":"d73e19e12f7192a08d6ee502d21d03809a57a921\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.30.6"}}},"isError":false,"timestamp":1783730495734}}
{"type":"message","id":"8d8f5ab4","parentId":"88230471","timestamp":"2026-07-11T00:41:36.552Z","message":{"role":"toolResult","toolCallId":"call_qC5CDICUmkQrxuMsd1hSiYgV|fc_08496348794f7e09016a5191404bdc8198890a246672d7c45c","toolName":"grep","content":[{"type":"text","text":"# spt-core/.git/logs/\n## HEAD\n 1352|134f0fea01d9fe606ee08b9db0013274ade7862c 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)\n*1353|55556482ebca6a2d3c6c453edcbc9b261eb27c15 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783658323 -0700\tcheckout: moving from main to triage/update-wedge-rca-trace\n 1354|55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n*1355|59cf48212449e4f198826e14f2808c014db2f99e c3a86a6cf21683a2262f99f4938b1376d46678b0 Reavo End <decidel@gmail.com> 1783710828 -0700\tcommit: chore(rca): preserve update-wedge/pump-truth RCA artifacts — diag instrumentation, rulings, ADR-0035, TLA specs\n*1356|c3a86a6cf21683a2262f99f4938b1376d46678b0 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710844 -0700\tcheckout: moving from triage/update-wedge-rca-trace to build/msg-identity-w1-f036\n 1357|17b770a18c858182038284f08e8ba0d93bb91263 abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783712376 -0700\tcommit: feat(msg-identity): W1 F-036 — identity-env sanitize + psyche-custody squat guard\n 1358|abaf08f7e77338cb525c79feb29f821c772c1eca abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783722307 -0700\tcheckout: moving from build/msg-identity-w1-f036 to build/msg-identity-w2-f034\n 1359|abaf08f7e77338cb525c79feb29f821c772c1eca 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783723550 -0700\tcommit: feat(msg-identity): W2 F-034 — listen bringup-bind legs (hint form, seed survives refusal, sid fallback)\n\n## refs/heads/\n### update-wedge-drain-drive\n 1|0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n*2|3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n### uwedge2-resume-viewer\n 1|0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n*2|31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\n*3|fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n*4|7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n*5|14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n*6|2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n*7|2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n\n### fix/\n#### update-wedge-bounded-conn-write\n 4|ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n*5|52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\n 6|d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n\n### release-merge/\n#### uwedge-r4\n 1|0000000000000000000000000000000000000000 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783672786 -0700\tbranch: Created from 0a306c9\n*2|0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 53cfab5cc56f04a25ce6a722fd85a0da20a34a1e Reavo End <decidel@gmail.com> 1783672901 -0700\tcommit (merge): merge: r4 update-wedge bounded conn write (f10e5e9) onto v0.30.5 main\n\n### release/\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\n### triage/\n#### update-wedge-rca-trace\n 2|55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n*3|59cf48212449e4f198826e14f2808c014db2f99e c3a86a6cf21683a2262f99f4938b1376d46678b0 Reavo End <decidel@gmail.com> 1783710828 -0700\tcommit: chore(rca): preserve update-wedge/pump-truth RCA artifacts — diag instrumentation, rulings, ADR-0035, TLA specs"}],"details":{"scopePath":"spt-core/.git/logs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":14,"fileCount":7,"files":["spt-core/.git/logs/HEAD","spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","spt-core/.git/logs/refs/heads/release-merge/uwedge-r4","spt-core/.git/logs/refs/heads/release/v0.30.4","spt-core/.git/logs/refs/heads/triage/update-wedge-rca-trace","spt-core/.git/logs/refs/heads/update-wedge-drain-drive","spt-core/.git/logs/refs/heads/uwedge2-resume-viewer"],"fileMatches":[{"path":"spt-core/.git/logs/HEAD","count":3},{"path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","count":1},{"path":"spt-core/.git/logs/refs/heads/release-merge/uwedge-r4","count":1},{"path":"spt-core/.git/logs/refs/heads/release/v0.30.4","count":1},{"path":"spt-core/.git/logs/refs/heads/triage/update-wedge-rca-trace","count":1},{"path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive","count":1},{"path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","count":6}],"truncated":false,"displayContent":"# spt-core/.git/logs/\n## HEAD\n 1352│134f0fea01d9fe606ee08b9db0013274ade7862c 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)\n*1353│55556482ebca6a2d3c6c453edcbc9b261eb27c15 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783658323 -0700\tcheckout: moving from main to triage/update-wedge-rca-trace\n 1354│55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n*1355│59cf48212449e4f198826e14f2808c014db2f99e c3a86a6cf21683a2262f99f4938b1376d46678b0 Reavo End <decidel@gmail.com> 1783710828 -0700\tcommit: chore(rca): preserve update-wedge/pump-truth RCA artifacts — diag instrumentation, rulings, ADR-0035, TLA specs\n*1356│c3a86a6cf21683a2262f99f4938b1376d46678b0 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710844 -0700\tcheckout: moving from triage/update-wedge-rca-trace to build/msg-identity-w1-f036\n 1357│17b770a18c858182038284f08e8ba0d93bb91263 abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783712376 -0700\tcommit: feat(msg-identity): W1 F-036 — identity-env sanitize + psyche-custody squat guard\n 1358│abaf08f7e77338cb525c79feb29f821c772c1eca abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783722307 -0700\tcheckout: moving from build/msg-identity-w1-f036 to build/msg-identity-w2-f034\n 1359│abaf08f7e77338cb525c79feb29f821c772c1eca 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783723550 -0700\tcommit: feat(msg-identity): W2 F-034 — listen bringup-bind legs (hint form, seed survives refusal, sid fallback)\n\n## refs/heads/\n### update-wedge-drain-drive\n 1│0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n*2│3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n### uwedge2-resume-viewer\n 1│0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n*2│31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\n*3│fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n*4│7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n*5│14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n*6│2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n*7│2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n\n### fix/\n#### update-wedge-bounded-conn-write\n 4│ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n*5│52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\n 6│d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n\n### release-merge/\n#### uwedge-r4\n 1│0000000000000000000000000000000000000000 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783672786 -0700\tbranch: Created from 0a306c9\n*2│0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 53cfab5cc56f04a25ce6a722fd85a0da20a34a1e Reavo End <decidel@gmail.com> 1783672901 -0700\tcommit (merge): merge: r4 update-wedge bounded conn write (f10e5e9) onto v0.30.5 main\n\n### release/\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\n### triage/\n#### update-wedge-rca-trace\n 2│55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n*3│59cf48212449e4f198826e14f2808c014db2f99e c3a86a6cf21683a2262f99f4938b1376d46678b0 Reavo End <decidel@gmail.com> 1783710828 -0700\tcommit: chore(rca): preserve update-wedge/pump-truth RCA artifacts — diag instrumentation, rulings, ADR-0035, TLA specs"},"isError":false,"timestamp":1783730496552}}
{"type":"message","id":"97a609f8","parentId":"8d8f5ab4","timestamp":"2026-07-11T00:41:46.177Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing local docs for updates**\n\n<!-- -->"},{"type":"toolCall","id":"call_pocKRd7C2LVypRNdKWYozfnn|fc_08496348794f7e09016a51914ab36c81988bc7963f9fdf35e3","name":"glob","arguments":{"i":"Locate wedge RCA artifacts","path":"spt-core/**/*{WEDGE,wedge,pump,PUMP,resume,RESUME}*.{md,rs,tla,json}","hidden":true,"gitignore":false,"limit":500}},{"type":"toolCall","id":"call_eLvfqI0h52N3gT7QEWZucwyP|fc_08496348794f7e09016a51914ab3808198842da2b4e3868df2","name":"grep","arguments":{"i":"Find SharedSend analysis","pattern":"SharedSend|self-deadlock|non-draining|blackhole|bounded conn|physical broker conn|resume_sessions","path":"spt-core/docs; spt-core/*.md; spt-core/crates/spt-daemon","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_u2o6ayKPaewmOw4BfvhuKQSf|fc_08496348794f7e09016a51914ab3908198af29f08b09cca770","name":"grep","arguments":{"i":"Find echo settle paths","pattern":"VERIFY_ECHO|verify_echo|echo.*verify|settle|re-arm|rearm|redrive|re-drive","path":"spt-core/crates/spt-daemon; spt-core/crates/spt-live; spt-core/crates/spt","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_eG5tiJdDKffODrO9lvfOLuQU|fc_08496348794f7e09016a51914ab39c8198acdef06dc171f4ac","name":"read","arguments":{"i":"Read broker output core","path":"spt-core/crates/spt-daemon/src/broker.rs","selector":"80-180,480-550,700-860,920-1030,1090-1200"}},{"type":"toolCall","id":"call_ecAtJpo7JIzN64mCLgy95oqc|fc_08496348794f7e09016a51914ab3a48198b0f4363d20010a12","name":"read","arguments":{"i":"Read connection writer","path":"spt-core/crates/spt-daemon/src/conn.rs","selector":"1-300"}},{"type":"toolCall","id":"call_GZrVV7M4NbvLHvyAgK4NnhsA|fc_08496348794f7e09016a51914ab3f481988ccc470f3bec0366","name":"read","arguments":{"i":"Read brain resume machinery","path":"spt-core/crates/spt-daemon/src/brain.rs","selector":"280-500,700-900"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3388,"output":429,"cacheRead":76288,"cacheWrite":0,"totalTokens":80105,"reasoningTokens":36,"cost":{"input":0.01694,"output":0.01287,"cacheRead":0.038144,"cacheWrite":0,"total":0.067954}},"stopReason":"toolUse","timestamp":1783730496598,"responseId":"resp_08496348794f7e09016a51914152088198a5fbeac40f86b881","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a519142d96081989b75d1128e7397ac","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFK2TXr5vqI8bEbhIJGdEYsrHCD8xXkC7BlUpk3lbEs4w0CmsW07bLbKUn6rM6Yo8-TlbYxxJszf66D_ptAb5_D_WxPdSoFy7CLWl8UTGh9nsUTTmsNH3OeA8K7UedJhR-mf3-K6OESeswYvPgqmXzKwHHjf1NO9OxU7n9dzUieGXncvpR9Ch0oDJfVOBF0A4iMsysoUydXkRmUV4627bynfoBPl_9kPraTTVqLj9P8QpzW0MQbE3j2-r0hvUcL_VXJk2N3HnIQLoDTK6xgO5p5rSo_DP8-BiU_BRN6lZJL4l_luawm1COxdHsJkPEJw55E1Q4ciL2s1Y1yGB8_hL7f6jnWhsn9tAeOSkkDOsfS1J6e5xpQjP0JhFK1sPVQhSmOGep_mug_OzUg0Hka61FdoN9zdrj0fUwQNmzvwupvnMVgTuv0y_wI10e5Udf1pZubcGIlz8qUv7q2xtAUgeLR-WwxJwVe7iDRywEle8Uz9amoKdeN-SLv6xX7ubOs31aFxTIGLkpJkHEKtTTPeHklVNl4MiHZo4jv3w7ljiYc4BNDP92R9mJJZTTrqPLwo7CBc7Ot_Daah7NDv_fdSJaTCBVNheYn8D5Zyh8p3BmFjWYXcEOlVlWZiIXMq_Z4G8734mEKdSAn6vWuTueXr6Dw8Q-KJf5f0E5QmwjzZa6U6hdpIZkHnS8myCk6l5q2vyS3TPHtW5QA0fi5oOTxjiqoYZksSgL5mF0LFSlg1Kq26jtIlFl2mKgX7SnBEV4wRuyCtm1a2xu522rOT1WafdlSxNqRfJU2rCc7KFfwUD_UwefQnKbJ-5o8n7FKvxXV2BmJF7COVu1-bMfER5BD10TBNvBDrFu9H5gq6fiTdrUnoM1sW6HvQ-efdjXzBOoeRgvWHFAsCvGZZWrPHrFmjDxBocP0WuzOKoHg924cIW_ntUBo6FcRST9TQDOLiLr0dKYPt8usWRhry_s_9_OqSEMMQWIBfIFoq0WZdBunmxdmoLfsrkYVqwCbqY_O6f4PKPwQW7SSV5K_WXHpRj60GHC6bZRbocxjW_39vv1nI8NrOaRf72BjxHdKQAMQw8EJdLC0RQ5e2RvzAGYl_K5x23BRTlpAkeB1qdCpqUtXyt2CpTNrj2x3HgWW9ieV7MTTJxt9","summary":[{"type":"summary_text","text":"**Assessing local docs for updates**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a51914ab36c81988bc7963f9fdf35e3","type":"function_call","status":"completed","arguments":"{\"i\":\"Locate wedge RCA artifacts\",\"path\":\"spt-core/**/*{WEDGE,wedge,pump,PUMP,resume,RESUME}*.{md,rs,tla,json}\",\"hidden\":true,\"gitignore\":false,\"limit\":500}","call_id":"call_pocKRd7C2LVypRNdKWYozfnn","name":"glob"},{"id":"fc_08496348794f7e09016a51914ab3808198842da2b4e3868df2","type":"function_call","status":"completed","arguments":"{\"i\":\"Find SharedSend analysis\",\"pattern\":\"SharedSend|self-deadlock|non-draining|blackhole|bounded conn|physical broker conn|resume_sessions\",\"path\":\"spt-core/docs; spt-core/*.md; spt-core/crates/spt-daemon\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_eLvfqI0h52N3gT7QEWZucwyP","name":"grep"},{"id":"fc_08496348794f7e09016a51914ab3908198af29f08b09cca770","type":"function_call","status":"completed","arguments":"{\"i\":\"Find echo settle paths\",\"pattern\":\"VERIFY_ECHO|verify_echo|echo.*verify|settle|re-arm|rearm|redrive|re-drive\",\"path\":\"spt-core/crates/spt-daemon; spt-core/crates/spt-live; spt-core/crates/spt\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_u2o6ayKPaewmOw4BfvhuKQSf","name":"grep"},{"id":"fc_08496348794f7e09016a51914ab39c8198acdef06dc171f4ac","type":"function_call","status":"completed","arguments":"{\"i\":\"Read broker output core\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"selector\":\"80-180,480-550,700-860,920-1030,1090-1200\"}","call_id":"call_eG5tiJdDKffODrO9lvfOLuQU","name":"read"},{"id":"fc_08496348794f7e09016a51914ab3a48198b0f4363d20010a12","type":"function_call","status":"completed","arguments":"{\"i\":\"Read connection writer\",\"path\":\"spt-core/crates/spt-daemon/src/conn.rs\",\"selector\":\"1-300\"}","call_id":"call_ecAtJpo7JIzN64mCLgy95oqc","name":"read"},{"id":"fc_08496348794f7e09016a51914ab3f481988ccc470f3bec0366","type":"function_call","status":"completed","arguments":"{\"i\":\"Read brain resume machinery\",\"path\":\"spt-core/crates/spt-daemon/src/brain.rs\",\"selector\":\"280-500,700-900\"}","call_id":"call_GZrVV7M4NbvLHvyAgK4NnhsA","name":"read"}]},"duration":9561.226900000125,"ttft":1621.4883000003174,"contextSnapshot":{"promptTokens":79676,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_pocKRd7C2LVypRNdKWYozfnn|fc_08496348794f7e09016a51914ab36c81988bc7963f9fdf35e3","toolName":"glob","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/**/*{WEDGE,wedge,pump,PUMP,resume,RESUME}*.{md,rs,tla,json}"},"intent":"Locate wedge RCA artifacts"},"id":"e8434fe4","parentId":"97a609f8","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_eLvfqI0h52N3gT7QEWZucwyP|fc_08496348794f7e09016a51914ab3808198842da2b4e3868df2","toolName":"grep","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/docs; spt-core/*.md; spt-core/crates/spt-daemon"},"intent":"Find SharedSend analysis"},"id":"d88dc3de","parentId":"e8434fe4","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_u2o6ayKPaewmOw4BfvhuKQSf|fc_08496348794f7e09016a51914ab3908198af29f08b09cca770","toolName":"grep","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/crates/spt-daemon; spt-core/crates/spt-live; spt-core/crates/spt"},"intent":"Find echo settle paths"},"id":"2c48b8c2","parentId":"d88dc3de","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_eG5tiJdDKffODrO9lvfOLuQU|fc_08496348794f7e09016a51914ab39c8198acdef06dc171f4ac","toolName":"read","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs"},"intent":"Read broker output core"},"id":"4101bf65","parentId":"2c48b8c2","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ecAtJpo7JIzN64mCLgy95oqc|fc_08496348794f7e09016a51914ab3a48198b0f4363d20010a12","toolName":"read","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/crates/spt-daemon/src/conn.rs"},"intent":"Read connection writer"},"id":"349a22b8","parentId":"4101bf65","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GZrVV7M4NbvLHvyAgK4NnhsA|fc_08496348794f7e09016a51914ab3f481988ccc470f3bec0366","toolName":"read","startedAt":"2026-07-11T00:41:46.177Z","args":{"path":"spt-core/crates/spt-daemon/src/brain.rs"},"intent":"Read brain resume machinery"},"id":"9d1039b7","parentId":"349a22b8","timestamp":"2026-07-11T00:41:46.177Z"}
{"type":"message","id":"dd77371e","parentId":"9d1039b7","timestamp":"2026-07-11T00:41:46.201Z","message":{"role":"toolResult","toolCallId":"call_GZrVV7M4NbvLHvyAgK4NnhsA|fc_08496348794f7e09016a51914ab3f481988ccc470f3bec0366","toolName":"read","content":[{"type":"text","text":"pub struct Brain {\n…\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    /// Cold start in **PUMP mode**: connect, handshake, then SPLIT the stream\n    /// into the reader-thread carrier ([`BrainConn::Split`]) and arm a per-IPC-\n    /// call deadline of `io_timeout`. This is the peer pump's dedicated client —\n    /// every reply read is bounded ([`Brain::read_event_until`] via\n    /// [`Brain::call_deadline`]) so a black-holed peer can never wedge the\n    /// single-threaded pump. The split is done at CONSTRUCTION (not flipped on a\n    /// live `Whole` after the fact — moving the `Stream` out by value to split it\n    /// is impossible behind `&mut self`), so a pump brain is split-and-bounded\n    /// for its whole life and a non-pump brain never pays the reader thread.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    pub fn cold_start_pump(name: &str, now_ms: u64, io_timeout: Duration) -> io::Result<Self> {\n        let mut stream = LocalSocketTransport::connect(name)?;\n        send_hello(&mut stream, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::split_with_reader(stream),\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: Some(io_timeout),\n            baseline_next_output: false,\n        })\n    }\n\n    /// Handoff: connect, handshake, rehydrate the resume cursor + generation\n    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —\n    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a\n    /// gapless resume.\n    ///\n    /// The eager `subscribe` IS the resume mechanism — every standalone handoff\n    /// caller (the brain-only update engine, `idempotent`/`daemon_e2e`/`handoff`\n    /// integration tests) replays output through it with no later `attach`. So it\n    /// stays. The P1c controller-writer reorder (a `serve_attach` re-serve that\n    /// ALSO subscribes from 0 over this same socket → two racing\n    /// `controller_writer`s) is fixed in the BROKER, not by dropping this\n    /// subscribe: the epoch gate ([`OutputLog::become_controller`]) guarantees a\n    /// single live writer per connection, and the seeded `session_cursors` below\n    /// routes the consumer through the dedup-below + snap-above path so any\n    /// at-least-once boundary re-send is tolerated, never a fatal reject-gap\n    /// ([`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]).\n    ///\n    /// **Test-only (restoration D4-3).** The production resume path is\n    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n    /// never this brain→brain frame; `handoff` survives only for the integration\n    /// tests. See [`BrainState`].\n    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so\n        // `read_event` routes this session through the dedup-below + snap-above\n        // path (matching `resume_sessions`), not the legacy reject-gap path that\n        // turns any at-least-once boundary reorder into a fatal `output gap`.\n        let mut session_cursors = std::collections::HashMap::new();\n        session_cursors.insert(prior.session_id, prior.next_seq);\n        let mut brain = Brain {\n            conn: BrainConn::Whole(conn),\n            generation: prior.generation + 1,\n            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded\n            session_id: Some(prior.session_id),\n            next_seq: prior.next_seq,\n            net_cursors: std::collections::HashMap::new(),\n            presence_cursor: None,\n            session_cursors,\n            io_timeout: None,\n            baseline_next_output: false,\n        };\n        brain.subscribe(prior.session_id, prior.next_seq)?;\n        Ok(brain)\n    }\n\n    /// The wall-clock generation start (epoch ms) — always this generation's own\n    /// start time, never a rehydrated value.\n    pub fn gen_start_ms(&self) -> u64 {\n        self.gen_start_ms\n    }\n\n    /// The generation counter (0 on cold start, +1 per handoff).\n    pub fn generation(&self) -> u64 {\n        self.generation\n    }\n\n    /// The next output sequence still needed (the resume cursor).\n    pub fn next_seq(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// The session id the brain is driving, if any.\n    pub fn session_id(&self) -> Option<u64> {\n        self.session_id\n    }\n\n    /// Snapshot durable state for a future handoff. **Test-only (restoration\n    /// D4-3)** — the production path reconstructs from the broker, never a\n    /// snapshot frame; see [`BrainState`].\n    pub fn snapshot(&self) -> BrainState {\n        BrainState {\n            session_id: self.session_id.expect(\"snapshot before a session exists\"),\n            generation: self.generation,\n            next_seq: self.next_seq,\n            gen_start_ms: self.gen_start_ms,\n        }\n    }\n\n    /// Spawn a session under a broker PTY; the broker auto-subscribes this\n    /// connection. Reads until the `spawned` reply (consuming any output that\n    /// races ahead).\n    pub fn spawn_session(&mut self, req: SpawnReq) -> io::Result<u64> {\n        self.spawn_session_pid(req)\n            .map(|(session_id, _)| session_id)\n    }\n\n    /// [`spawn_session`](Self::spawn_session) that also surfaces the child's\n    /// OS pid (when the backend exposes one) — measurement/observability\n    /// callers (the D9-3 dormancy-budget harness) sample the seat by pid.\n    ///\n    /// **Resume-mode caveat (KNOWN-HAZARDS 6.9).** The `Spawned`-wait loop below\n    /// calls [`Brain::read_event`] on every interleaved frame, so on a *resume-mode*\n    /// brain (multiple sessions in `session_cursors`) an OUTPUT frame for a\n    /// **different** session read here is cursor-processed (its cursor snaps; the\n    /// broker already counted it delivered) and then **discarded** by `_ => continue`\n    /// — that chunk is lost to its consumer and the broker will not re-send it.\n    /// Unreachable today (the supervised brain spawns nothing; a legacy single-session\n    /// seat has no other session); the daemon-hosted live-agent event pump must make\n    /// this non-blocking (one demux owns `read_event`, routing each frame to its\n    /// session) or route the frames it reads for other sessions, not drop them.\n    pub fn spawn_session_pid(&mut self, req: SpawnReq) -> io::Result<(u64, Option<u32>)> {\n        self.send(\n            KIND_SPAWN,\n            serde_json::to_value(req).expect(\"SpawnReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Spawned { session_id, pid } => {\n                    self.session_id = Some(session_id);\n                    // A resume-mode brain tracks this fresh session per-session via\n                    // `read_event`'s first-sight `or_insert` seed (D4-2b) — no seed\n                    // needed here; a legacy brain (empty map) uses `next_seq`.\n                    return Ok((session_id, pid));\n                }\n                BrokerEvent::Error { message } => {\n                    return Err(io::Error::other(message));\n                }\n                _ => continue,\n            }\n        }\n    }\n\n    /// Inject input bytes into the current session (send-keys), the **legacy\n    /// direct** path: no durable id, no dedup, no ack. For exactly-once delivery\n    /// of an effect that originates from a durable source (a spool row, a\n    /// sequenced injection), use [`Brain::send_effect`].\n    pub fn send_input(&mut self, bytes: &[u8]) -> io::Result<()> {\n        let sid = self.require_session()?;\n        self.send(\n            KIND_INPUT,\n            serde_json::to_value(InputReq {\n                session_id: sid,\n                data_b64: encode_bytes(bytes),\n                op_id: None,\n                minter: None,\n                ack: true,\n            })\n            .expect(\"InputReq serializes\"),\n        )\n    }\n…\n    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n        loop {\n…\n            match env.kind.as_str() {\n                KIND_OUTPUT => {\n…\n                    return Ok(BrokerEvent::Output {\n                        session_id: ev.session_id,\n                        seq: ev.seq,\n                        bytes,\n                    });\n                }\n                KIND_SPAWNED => {\n                    let ev: Spawned = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Spawned {\n                        session_id: ev.session_id,\n                        pid: ev.pid,\n                    });\n                }\n                KIND_EXIT => {\n                    let ev: ExitEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Exit {\n                        session_id: ev.session_id,\n                        code: ev.code,\n                    });\n                }\n                KIND_SUBSCRIBED => {\n                    let ev: SubscribedReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Subscribed {\n                        session_id: ev.session_id,\n                        outcome: ev.outcome,\n                    });\n                }\n                KIND_SIZE => {\n                    let ev: SizeEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Size {\n                        session_id: ev.session_id,\n                        rows: ev.rows,\n                        cols: ev.cols,\n                    });\n                }\n                KIND_DISPLACED => {\n                    let ev: DisplacedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Displaced {\n                        session_id: ev.session_id,\n                        by: ev.by,\n                    });\n                }\n                // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n                KIND_VIEWER_EVICTED => {\n                    let ev: ViewerEvictedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::ViewerEvicted {\n                        session_id: ev.session_id,\n                    });\n                }\n                KIND_APPLIED => {\n                    let ev: AppliedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Applied {\n                        session_id: ev.session_id,\n                        op_id: ev.op_id,\n                        applied_now: ev.applied_now,\n                    });\n                }\n                KIND_NET_STATUS_REPLY => {\n                    let ev: NetStatusReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStatus(ev));\n                }\n                KIND_NET_DIALED => {\n                    let ev: NetDialed = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetDialed(ev));\n                }\n                // The bare ack for a non-blocking submit-dial (PUMP-TRUTH W2) —\n                // null payload; the dial's outcome rides the presence stream.\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMITTED => return Ok(BrokerEvent::NetDialSubmitted),\n                KIND_NET_STREAM_OPENED => {\n                    let ev: NetStreamOpened = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreamOpened(ev));\n                }\n                KIND_NET_SENT => {\n                    let ev: NetSent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetSent(ev));\n                }\n                KIND_NET_STREAMS_REPLY => {\n                    let ev: NetStreamsReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreams(ev));\n                }\n                KIND_SESSIONS_REPLY => {\n                    let ev: SessionsReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Sessions(ev));\n                }\n                KIND_PAIR_JOINED => {\n                    let ev: PairJoinReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::PairJoined(ev));\n                }\n                KIND_MET_MEMBER => {\n                    let ev: MetMember = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::MetMember(ev));\n                }\n                KIND_NET_STREAM_DATA => {\n                    let ev: NetStreamData = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    // Same exactly-once discipline as PTY output, per stream:\n                    // accept the contiguous next seq, drop a replay duplicate,\n                    // reject a forward gap (a lost chunk must not pass silently).\n                    let cursor = self.net_cursors.entry(ev.stream_id).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!(\n                                \"stream {} gap: got seq {} want {}\",\n                                ev.stream_id, ev.seq, *cursor\n                            ),\n                        ));\n                    }\n                    *cursor = ev.seq + 1;\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…\n    }\n…\n}"}],"details":{"displayContent":{"text":"pub struct Brain {\n…\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    /// Cold start in **PUMP mode**: connect, handshake, then SPLIT the stream\n    /// into the reader-thread carrier ([`BrainConn::Split`]) and arm a per-IPC-\n    /// call deadline of `io_timeout`. This is the peer pump's dedicated client —\n    /// every reply read is bounded ([`Brain::read_event_until`] via\n    /// [`Brain::call_deadline`]) so a black-holed peer can never wedge the\n    /// single-threaded pump. The split is done at CONSTRUCTION (not flipped on a\n    /// live `Whole` after the fact — moving the `Stream` out by value to split it\n    /// is impossible behind `&mut self`), so a pump brain is split-and-bounded\n    /// for its whole life and a non-pump brain never pays the reader thread.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    pub fn cold_start_pump(name: &str, now_ms: u64, io_timeout: Duration) -> io::Result<Self> {\n        let mut stream = LocalSocketTransport::connect(name)?;\n        send_hello(&mut stream, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::split_with_reader(stream),\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: Some(io_timeout),\n            baseline_next_output: false,\n        })\n    }\n\n    /// Handoff: connect, handshake, rehydrate the resume cursor + generation\n    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —\n    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a\n    /// gapless resume.\n    ///\n    /// The eager `subscribe` IS the resume mechanism — every standalone handoff\n    /// caller (the brain-only update engine, `idempotent`/`daemon_e2e`/`handoff`\n    /// integration tests) replays output through it with no later `attach`. So it\n    /// stays. The P1c controller-writer reorder (a `serve_attach` re-serve that\n    /// ALSO subscribes from 0 over this same socket → two racing\n    /// `controller_writer`s) is fixed in the BROKER, not by dropping this\n    /// subscribe: the epoch gate ([`OutputLog::become_controller`]) guarantees a\n    /// single live writer per connection, and the seeded `session_cursors` below\n    /// routes the consumer through the dedup-below + snap-above path so any\n    /// at-least-once boundary re-send is tolerated, never a fatal reject-gap\n    /// ([`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]).\n    ///\n    /// **Test-only (restoration D4-3).** The production resume path is\n    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n    /// never this brain→brain frame; `handoff` survives only for the integration\n    /// tests. See [`BrainState`].\n    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so\n        // `read_event` routes this session through the dedup-below + snap-above\n        // path (matching `resume_sessions`), not the legacy reject-gap path that\n        // turns any at-least-once boundary reorder into a fatal `output gap`.\n        let mut session_cursors = std::collections::HashMap::new();\n        session_cursors.insert(prior.session_id, prior.next_seq);\n        let mut brain = Brain {\n            conn: BrainConn::Whole(conn),\n            generation: prior.generation + 1,\n            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded\n            session_id: Some(prior.session_id),\n            next_seq: prior.next_seq,\n            net_cursors: std::collections::HashMap::new(),\n            presence_cursor: None,\n            session_cursors,\n            io_timeout: None,\n            baseline_next_output: false,\n        };\n        brain.subscribe(prior.session_id, prior.next_seq)?;\n        Ok(brain)\n    }\n\n    /// The wall-clock generation start (epoch ms) — always this generation's own\n    /// start time, never a rehydrated value.\n    pub fn gen_start_ms(&self) -> u64 {\n        self.gen_start_ms\n    }\n\n    /// The generation counter (0 on cold start, +1 per handoff).\n    pub fn generation(&self) -> u64 {\n        self.generation\n    }\n\n    /// The next output sequence still needed (the resume cursor).\n    pub fn next_seq(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// The session id the brain is driving, if any.\n    pub fn session_id(&self) -> Option<u64> {\n        self.session_id\n    }\n\n    /// Snapshot durable state for a future handoff. **Test-only (restoration\n    /// D4-3)** — the production path reconstructs from the broker, never a\n    /// snapshot frame; see [`BrainState`].\n    pub fn snapshot(&self) -> BrainState {\n        BrainState {\n            session_id: self.session_id.expect(\"snapshot before a session exists\"),\n            generation: self.generation,\n            next_seq: self.next_seq,\n            gen_start_ms: self.gen_start_ms,\n        }\n    }\n\n    /// Spawn a session under a broker PTY; the broker auto-subscribes this\n    /// connection. Reads until the `spawned` reply (consuming any output that\n    /// races ahead).\n    pub fn spawn_session(&mut self, req: SpawnReq) -> io::Result<u64> {\n        self.spawn_session_pid(req)\n            .map(|(session_id, _)| session_id)\n    }\n\n    /// [`spawn_session`](Self::spawn_session) that also surfaces the child's\n    /// OS pid (when the backend exposes one) — measurement/observability\n    /// callers (the D9-3 dormancy-budget harness) sample the seat by pid.\n    ///\n    /// **Resume-mode caveat (KNOWN-HAZARDS 6.9).** The `Spawned`-wait loop below\n    /// calls [`Brain::read_event`] on every interleaved frame, so on a *resume-mode*\n    /// brain (multiple sessions in `session_cursors`) an OUTPUT frame for a\n    /// **different** session read here is cursor-processed (its cursor snaps; the\n    /// broker already counted it delivered) and then **discarded** by `_ => continue`\n    /// — that chunk is lost to its consumer and the broker will not re-send it.\n    /// Unreachable today (the supervised brain spawns nothing; a legacy single-session\n    /// seat has no other session); the daemon-hosted live-agent event pump must make\n    /// this non-blocking (one demux owns `read_event`, routing each frame to its\n    /// session) or route the frames it reads for other sessions, not drop them.\n    pub fn spawn_session_pid(&mut self, req: SpawnReq) -> io::Result<(u64, Option<u32>)> {\n        self.send(\n            KIND_SPAWN,\n            serde_json::to_value(req).expect(\"SpawnReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Spawned { session_id, pid } => {\n                    self.session_id = Some(session_id);\n                    // A resume-mode brain tracks this fresh session per-session via\n                    // `read_event`'s first-sight `or_insert` seed (D4-2b) — no seed\n                    // needed here; a legacy brain (empty map) uses `next_seq`.\n                    return Ok((session_id, pid));\n                }\n                BrokerEvent::Error { message } => {\n                    return Err(io::Error::other(message));\n                }\n                _ => continue,\n            }\n        }\n    }\n\n    /// Inject input bytes into the current session (send-keys), the **legacy\n    /// direct** path: no durable id, no dedup, no ack. For exactly-once delivery\n    /// of an effect that originates from a durable source (a spool row, a\n    /// sequenced injection), use [`Brain::send_effect`].\n    pub fn send_input(&mut self, bytes: &[u8]) -> io::Result<()> {\n        let sid = self.require_session()?;\n        self.send(\n            KIND_INPUT,\n            serde_json::to_value(InputReq {\n                session_id: sid,\n                data_b64: encode_bytes(bytes),\n                op_id: None,\n                minter: None,\n                ack: true,\n            })\n            .expect(\"InputReq serializes\"),\n        )\n    }\n…\n    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n        loop {\n…\n            match env.kind.as_str() {\n                KIND_OUTPUT => {\n…\n                    return Ok(BrokerEvent::Output {\n                        session_id: ev.session_id,\n                        seq: ev.seq,\n                        bytes,\n                    });\n                }\n                KIND_SPAWNED => {\n                    let ev: Spawned = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Spawned {\n                        session_id: ev.session_id,\n                        pid: ev.pid,\n                    });\n                }\n                KIND_EXIT => {\n                    let ev: ExitEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Exit {\n                        session_id: ev.session_id,\n                        code: ev.code,\n                    });\n                }\n                KIND_SUBSCRIBED => {\n                    let ev: SubscribedReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Subscribed {\n                        session_id: ev.session_id,\n                        outcome: ev.outcome,\n                    });\n                }\n                KIND_SIZE => {\n                    let ev: SizeEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Size {\n                        session_id: ev.session_id,\n                        rows: ev.rows,\n                        cols: ev.cols,\n                    });\n                }\n                KIND_DISPLACED => {\n                    let ev: DisplacedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Displaced {\n                        session_id: ev.session_id,\n                        by: ev.by,\n                    });\n                }\n                // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n                KIND_VIEWER_EVICTED => {\n                    let ev: ViewerEvictedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::ViewerEvicted {\n                        session_id: ev.session_id,\n                    });\n                }\n                KIND_APPLIED => {\n                    let ev: AppliedEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Applied {\n                        session_id: ev.session_id,\n                        op_id: ev.op_id,\n                        applied_now: ev.applied_now,\n                    });\n                }\n                KIND_NET_STATUS_REPLY => {\n                    let ev: NetStatusReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStatus(ev));\n                }\n                KIND_NET_DIALED => {\n                    let ev: NetDialed = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetDialed(ev));\n                }\n                // The bare ack for a non-blocking submit-dial (PUMP-TRUTH W2) —\n                // null payload; the dial's outcome rides the presence stream.\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMITTED => return Ok(BrokerEvent::NetDialSubmitted),\n                KIND_NET_STREAM_OPENED => {\n                    let ev: NetStreamOpened = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreamOpened(ev));\n                }\n                KIND_NET_SENT => {\n                    let ev: NetSent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetSent(ev));\n                }\n                KIND_NET_STREAMS_REPLY => {\n                    let ev: NetStreamsReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreams(ev));\n                }\n                KIND_SESSIONS_REPLY => {\n                    let ev: SessionsReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Sessions(ev));\n                }\n                KIND_PAIR_JOINED => {\n                    let ev: PairJoinReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::PairJoined(ev));\n                }\n                KIND_MET_MEMBER => {\n                    let ev: MetMember = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::MetMember(ev));\n                }\n                KIND_NET_STREAM_DATA => {\n                    let ev: NetStreamData = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    // Same exactly-once discipline as PTY output, per stream:\n                    // accept the contiguous next seq, drop a replay duplicate,\n                    // reject a forward gap (a lost chunk must not pass silently).\n                    let cursor = self.net_cursors.entry(ev.stream_id).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!(\n                                \"stream {} gap: got seq {} want {}\",\n                                ev.stream_id, ev.seq, *cursor\n                            ),\n                        ));\n                    }\n                    *cursor = ev.seq + 1;\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…\n    }\n…\n}","startLine":263,"lineNumbers":[263,null,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,null,635,636,null,638,639,null,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,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,null,912,null,1724]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"}}},"isError":false,"timestamp":1783730506201}}
{"type":"message","id":"714d995d","parentId":"dd77371e","timestamp":"2026-07-11T00:41:46.210Z","message":{"role":"toolResult","toolCallId":"call_eLvfqI0h52N3gT7QEWZucwyP|fc_08496348794f7e09016a51914ab3808198842da2b4e3868df2","toolName":"grep","content":[{"type":"text","text":"# spt-core/\n## M12-W2.5-PLAN.md\n 18|  notice slots in there. rc sends **no resize** today (geometry static).\n*19|- **broker** `OutputLog.subscriber: Option<SharedSend>` (broker.rs:87), `SharedSend =\n 20|  Arc<Mutex<SendHalf>>` (a brain↔broker IPC local-socket write half, one per attach connection).\n 21|  `append` (broker.rs:137) holds the log lock and does a **blocking** `write_frame`+flush to the\n 22|  subscriber → **a slow subscriber stalls the single drain thread** = the viewer-isolation hazard.\n...\n 35|**Q1 — viewer collection + eviction signal.**\n*36|`OutputLog`: `subscriber` → `controller: Option<SharedSend>` + `viewers: HashMap<u64 attach_id,\n 37|ViewerSink>` (keyed by attach id for clean removal). **Eviction = bounded-queue overflow, NOT a\n 38|shared blocking write** (no heartbeat): each viewer gets a small **bounded SPSC channel + a\n 39|dedicated viewer-writer thread** that does the blocking socket write. `append` fans out under the\n...\n 214|- **T2 DONE + GREEN (broker)** — full `spt-daemon` suite 285 + all int green (3× stable, no flake).\n*215|  OutputLog → `controller: Option<SharedSend>` + `controller_by: Option<String>` + `viewers:\n 216|  HashMap<u64, ViewerSink>` + `endpoint`/`size`. `append` fan-out: controller blocking authoritative\n 217|  (advances `delivered_through`), viewers `try_send`→bounded channel (depth 256)→`viewer_writer`\n 218|  thread, evict-on-Full/Disconnected (drain never touches a viewer socket); soft cap MAX_VIEWERS=32.\n...\n 270|  (dispatch.rs `worker` → `connect(broker_name)`), so each attach is a DISTINCT broker IPC\n*271|  `SharedSend` → the broker can hold controller + viewers as distinct sinks. `HostedSession.endpoint`\n 272|  (broker.rs:209) carries the label, so the broker CAN own the `driven_by`/`viewer_count` writes.\n 273|  IPC `subscribe` (brain.rs:998) is fire-and-forget today → the controller/viewer/busy/took OUTCOME\n 274|  + the Displaced notice need IPC additions (SubscribeReq role + a reply/outcome + a KIND_DISPLACED\n## M12-W2.5-RULING.md\n 16|## Current impl gap (verified)\n*17|`OutputLog.subscriber: Option<SharedSend>` (broker.rs:87) is SINGULAR — one subscriber, silent\n 18|last-wins displace. `--view` (rc.rs:165) is only a client-side no-input choice; at the broker it\n 19|still takes the single slot + silently displaces (the forbidden anti-pattern). The controller/\n 20|viewer model is UNBUILT. PTY-ownership (child survives any attach/detach — broker.rs:28-31) DOES\n...\n 25|### 1. Broker — multi-subscriber `OutputLog`\n*26|- `subscriber: Option<SharedSend>` → **`controller: Option<SharedSend>` + `viewers: <keyed collection>`** (key by attach-id so a viewer removes cleanly). Append fans the frame out to the controller AND every viewer, **under the existing log lock**, each in strict `seq` order (preserve the no-interleave invariant).\n 27|- **Brain-resume cursor (`delivered_through`, ADR-0018) tracks the CONTROLLER only.** Viewers are ephemeral: each replays from its own `subscribe` cursor (the existing `attach(sub, from_seq)` per-attach replay), and a viewer's write success/failure NEVER moves `delivered_through`. This preserves at-least-once brain resume.\n 28|\n 29|### 2. Viewer isolation — the new hazard (load-bearing)\n## RESTORATION-D1-PLAN.md\n 27|   - `run_brain()`: the `spt daemon brain` child entry. Connect to the broker\n*28|     (`Brain::cold_start`, bounded connect-retry — the broker may still be\n 29|     binding), write the ready breadcrumb, then idle: hold the connection,\n 30|     heartbeat the breadcrumb, exit non-zero if the broker connection drops\n 31|     (so the supervisor respawns). No de-elevation guard (inherits the\n## RESTORATION-D4-PLAN.md\n 65|  net-consumers + shellwake, both re-derived from disk / `net-status` on start.\n*66|  So `run_brain` calling `resume_sessions()` is a **no-op today** (zero hosted\n 67|  sessions) and **forward-correct** when daemon-hosted sessions arrive with the\n 68|  live-agent adapter. D4 builds + **proves the capability with a real\n 69|  multi-session harness now** (activate-don't-pre-fail: the machinery is real and\n...\n 166|    accepted, not rejected. Evicted tail lost only; live stream survives.\n*167|- **`Brain::resume_sessions(&mut self)`** — the cold-start continuity primitive:\n 168|  call `sessions()`, and for each returned `SessionInfo` `subscribe(session_id,\n 169|  resume_seq)` and seed `session_cursors[session_id] = resume_seq`. Replaces the\n 170|  master plan's stale \"re-attach `sessions.first()` from 0\" with \"re-attach **all**\n...\n 187|**hard-killed — no `snapshot()` taken** (doyle amendment 4: this same mode is\n*188|D4-3's falsifiable guard), a fresh cold-start brain `resume_sessions` re-attaches\n 189|**every** session from its cursor — all resume, **no duplicate** output, gapless,\n 190|child pids unchanged; **plus the clamp case (doyle watch-item): a `resume_seq`\n 191|below the broker ring floor after a long outage → resume succeeds, the brain\n...\n 205|  `Brain::snapshot` as **test-only continuity** in their rustdoc: the production\n*206|  resume path is `cold_start` + `resume_sessions` (the broker is cursor-of-record,\n 207|  D4-1/D4-2); these types remain `pub` solely because the **integration tests**\n 208|  (separate test target — `#[cfg(test)]` would hide them) still drive the\n 209|  handoff-frame shape directly. Add the matching note to KNOWN-HAZARDS (the 2.4\n...\n 215|  (SIGKILL / `child.kill()` — no `snapshot()` possible)**, then a fresh\n*216|  `cold_start` + `resume_sessions` resumes every session gaplessly. A brain that\n 217|  was never given the chance to snapshot, yet resumes cleanly, demonstrates the\n 218|  `BrainState` frame is not on the production path. D4-3 then carries **no new\n 219|  machinery** — only the rustdoc/KNOWN-HAZARDS tags + the documented grep-clean\n...\n 234|D4-1 (broker delivered-cursor + `resume_seq` on the reply — the new persistent\n*235|surface) → **D4-2** (brain consumes it: per-session cursors + `resume_sessions`,\n 236|wired into `run_brain`) → **D4-3** (confirm + lock the `BrainState` retirement).\n 237|D4-1 before D4-2 because the brain resumes from the broker-supplied cursor; D4-3\n 238|last because it asserts the end-state both produce.\n...\n 298|that the cursor survives a detach. Then D4-2 (brain `session_cursors` +\n*299|`resume_sessions`, wired into `run_brain`).\n## RESTORATION-D5-PLAN.md\n 220|  **(Open call resolved by doyle → pure helper, 3 reasons: V3's target is\n*221|  untested dead code so a tested-unwired derive is the D4 `resume_sessions`\n 222|  posture exactly; the D5-3 evidence tag already presumes a helper and docs-only\n 223|  would orphan it; falsifiability now as executable asserts. The never-reset-on-\n 224|  crash asymmetry vs the periodic rule is exactly the kind of thing that drifts\n...\n 265|> `OneShotDeadline::fire_if_due(now)` in `deadline.rs` — tested, unwired, the D4\n*266|> `resume_sessions` posture (real machinery, real test, no live wiring). Pure\n 267|> derive + minimal `target_ms` persist only; `load` ignores `StartReason`; no\n 268|> scheduler/timer/thread. The DEFERRED.md row remains the scheduler record,\n 269|> cross-ref'd from the helper rustdoc.\n## RESTORATION-D6-PLAN.md\n 98|- **Forward-correct, not field-exercised (the D4/D5 posture, verbatim).** The\n*99|  daemon brain hosts no PTY sessions today, so `resume_sessions`\n 100|  (`brain.rs:596-608`) is a near-noop and a real *field* update is not driven on\n 101|  a single-home dev box. D6 is therefore **forward-correct capability + a real\n 102|  spawn/kill/respawn harness proof** — the readiness-gate + rollback machinery is\n...\n 204|  - **Post-resume, not post-connect** — write ready only **after**\n*205|    `resume_sessions` (`brain.rs:596-608`) completes + loops are resumed, i.e.\n 206|    the design's \"re-attached all sessions + resumed loops,\" not merely \"socket\n 207|    connected\" (today's `:162` write is post-connect). Today resume is a near-noop\n 208|    (no hosted sessions), so this is forward-correct: the gate is *defined*\n## RESTORATION-D7-PLAN.md\n 176|  --generation N --start-reason …` (env inherited per `spawn_brain_child`'s own\n*177|  posture); `run_brain` `connect_retry`s the test broker socket; `resume_sessions`\n 178|  re-attaches the test-established session. **This makes D7-1 the FIRST\n 179|  process-level exercise of the D4 cursor-of-record resume with N≥1 hosted\n 180|  sessions** — that alone justifies the real entry over a fixture.\n...\n 501|   injected `spawn_child` execs `P daemon brain --generation N --start-reason …`,\n*502|   `run_brain` connect-retries, `resume_sessions` re-attaches). This makes D7-1 the\n 503|   **first process-level exercise of the D4 cursor-of-record resume with N≥1 hosted\n 504|   sessions**. Test drive establishes PTY+QUIC once, drops its client **hard** (D4\n 505|   posture); the real child resumes; assert from the in-process broker's own state.\n## RESTORATION-PLAN.md\n 229|  output). (The original `applyhost.rs:239` site is gone; the equivalent\n*230|  cold-start re-attach is `run_brain` → `Brain::resume_sessions`.)\n 231|- **Output is at-least-once** (broker resumes from last-*sent*); **input/\n 232|  effects stay exactly-once** via the already-broker-owned `EffectJournal`.\n 233|  Matches the SPIKE-05 terminal-stream contract (a resize repaint already\n## V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md\n 21|\n*22|`become_controller` (broker.rs:349-375) drops the prior `ControllerSink` (its `tx`) but **does not stop the prior writer thread** — writer-A keeps flushing its `initial` batch. Both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`, broker.rs:364) and `controller_writer` (broker.rs:703-737) writes its `initial` batch frame-by-frame under that mutex with **no inter-thread ordering**. When writer-A's seq 1 wins the socket before writer-B's seq 0, the consumer (strict legacy path, brain.r...\n 23|\n 24|### RACEDIAG trace (failing interleaving)\n 25|```\n...\n 52|\n*53|3. **DEFENSE-IN-DEPTH — seed `session_cursors` on handoff** (brain.rs:341 is currently empty) so the consumer uses the dedup-below + snap-above path (brain.rs:582-595), matching production `resume_sessions` (brain.rs:830) and cold-start. The strict reject-gap legacy path (brain.rs:599-608) is a latent trap for any at-least-once reorder; production resume already avoids it.\n 54|\n 55|## Traceability (mint FIRST, then satisfy — rule 3)\n 56|\n 57|Add to `traceable-reqs.toml`:\n*58|- **`REQ-HAZARD-CONTROLLER-WRITER-REORDER`** — \"Two `controller_writer` threads must never race one brain connection's socket: `become_controller` superseding a controller leaves the prior writer able to flush its `initial` batch concurrently with the new writer on the same `SharedSend`, producing a non-monotonic on-wire seq stream (`got seq 1 want 0`) that the strict consumer rejects (gap) or that drops an un-sent lower seq under snap-above (byte loss). Invariant: one live `controller_writer` per conn...\n 59|- KNOWN-HAZARDS.md entry (next 7.x) with the failure / invariant / spt-core-mapping / source structure.\n 60|\n 61|## Gate (non-vacuous — doyle)\n\n## crates/spt-daemon/\n\n### src/\n#### brain.rs\n 76|/// respawns reconstructs continuity by querying the broker\n*77|/// ([`Brain::resume_sessions`] over the broker's cursor-of-record), never this\n 78|/// message. `BrainState` / [`Brain::handoff`] / [`Brain::snapshot`] stay `pub`\n 79|/// solely because the integration tests (separate test target — `#[cfg(test)]`\n 80|/// would hide them) still drive the handoff-frame shape directly; there is **no\n...\n 282|    /// continuity state a cold-started brain rebuilds from the broker via\n*283|    /// [`Brain::resume_sessions`], replacing the retired single-session\n 284|    /// `BrainState` handoff. A session present here is in **resume mode** — its\n 285|    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n 286|    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n...\n 380|    /// **Test-only (restoration D4-3).** The production resume path is\n*381|    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n 382|    /// never this brain→brain frame; `handoff` survives only for the integration\n 383|    /// tests. See [`BrainState`].\n 384|    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n...\n 389|        // `read_event` routes this session through the dedup-below + snap-above\n*390|        // path (matching `resume_sessions`), not the legacy reject-gap path that\n 391|        // turns any at-least-once boundary reorder into a fatal `output gap`.\n 392|        let mut session_cursors = std::collections::HashMap::new();\n 393|        session_cursors.insert(prior.session_id, prior.next_seq);\n...\n 642|                    // A **resume-mode** brain (the per-session map is populated by\n*643|                    // `resume_sessions`) routes EVERY session through the map —\n 644|                    // including a freshly-spawned or first-seen one, which seeds\n 645|                    // itself at its first `seq` via `entry().or_insert` (D4-2b,\n 646|                    // doyle amendment 5: this closes the mixed-mode gap where N≥2\n...\n 1029|    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK). This subscribes the sessions onto THIS conn.\n*1030|    /// A NON-DRAINING consumer will SELF-DEADLOCK: an actively-streaming session backs\n 1031|    /// the conn up, the broker-side subscriber writer blocks holding the conn's single\n*1032|    /// `SharedSend` mutex ACROSS a blocking `write_frame` (broker.rs\n 1033|    /// `viewer_writer`/`controller_writer`), the dispatch reply path (`send_frame`) needs\n*1034|    /// that same lock, so any request/reply the non-draining consumer issues is starved\n 1035|    /// (dead-peer-independent — the v0.30.4 field re-wedge). The supervised daemon brain\n*1036|    /// is exactly such a non-draining consumer (its `run_brain` heartbeat reads this conn\n 1037|    /// only in `net_status()`/`sessions()` drain-and-discard loops; it drives no PTY\n 1038|    /// session — brainproc.rs:184 — and nothing on its side consumes the output, §3\n 1039|    /// guard), so it MUST call [`Self::resume_session_cursors`] (cursor-only, NO\n...\n 1045|    // [doc->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n*1046|    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {\n 1047|        let reply = self.sessions()?;\n 1048|        let mut resumed = Vec::with_capacity(reply.sessions.len());\n 1049|        for info in reply.sessions {\n...\n 1060|\n*1061|    /// Cursor-only resume for a NON-DRAINING caller (the supervised daemon brain): query\n 1062|    /// every hosted session and seed [`Self::session_cursors`] at each `resume_seq`,\n 1063|    /// returning the re-attached ids — but DO **NOT** subscribe. This is the safe resume\n*1064|    /// the daemon `run_brain` heartbeat uses in place of [`Self::resume_sessions`]:\n 1065|    /// carrying only request/reply on the brain's conn (no UNDRAINED subscription) makes\n*1066|    /// the `SharedSend` self-deadlock structurally impossible (UPDATE-WEDGE round 3,\n 1067|    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK — see the deadlock warning on\n*1068|    /// [`Self::resume_sessions`]). The daemon brain drives no broker PTY session\n 1069|    /// (brainproc.rs:184) and nothing on its side consumes session output (§3 guard), so\n 1070|    /// the observation cursor is all it needs; no attach also means no controller steal\n 1071|    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL, a fortiori). Rides the brain-swap ⇒ seamless.\n...\n 1088|    /// The per-session resume cursor (the next seq still needed for a session\n*1089|    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.\n 1090|    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {\n 1091|        self.session_cursors.get(&session_id).copied()\n 1092|    }\n...\n 1605|    /// `spt rc`) it never drains, freezing the session\n*1606|    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL). [`Brain::resume_sessions`] re-attaches\n 1607|    /// as Viewer instead; Control is reserved for sessions the daemon brain drives.\n 1608|    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n 1609|    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n#### brainproc.rs\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#### broker.rs\n 78|/// handle under the same gate discipline. Round-4\n*79|/// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): no longer a raw\n 80|/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n 81|/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n 82|/// matching stays `Arc::ptr_eq`.\n*83|pub(crate) type SharedSend = Arc<BrokerConn>;\n 84|\n 85|/// Default bounded output-log depth (chunks). Large enough that a brain restart\n 86|/// window never evicts unreplayed output in practice; the bound is what keeps a\n...\n 122|    /// (a dropped brain connection removes its viewer rows); never written here.\n*123|    send: SharedSend,\n 124|    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n 125|    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n 126|    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n...\n 498|    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n*499|    send: SharedSend,\n 500|    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n 501|    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n 502|    by: Option<String>,\n...\n 890|    /// controller's identity.\n*891|    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {\n 892|        // Drop the prior controller sink first (its writer's live loop ends when\n 893|        // tx drops), and bump the generation so (a) an in-flight deadline-evict\n 894|        // for the old controller can't unseat this one and (b) a prior writer\n...\n 1072|    /// live queue. The drain thread fans live frames in via `try_send`.\n*1073|    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {\n 1074|        let vid = self.next_viewer_id;\n 1075|        self.next_viewer_id += 1;\n 1076|        // The size frame + the initial batch (owned by the thread, not the bounded\n...\n 1115|        &mut self,\n*1116|        sub: SharedSend,\n 1117|        from_seq: u64,\n 1118|        intent: AttachIntent,\n 1119|        by: Option<String>,\n...\n 1224|    /// `driven_by` / `viewer_count` when a role actually changed.\n*1225|    fn detach_if(&mut self, sub: &SharedSend) {\n 1226|        let is_ctrl = self\n 1227|            .controller\n 1228|            .as_ref()\n...\n 1241|    /// an `exit` frame.\n*1242|    fn all_sinks(&self) -> Vec<SharedSend> {\n*1243|        let mut v: Vec<SharedSend> = Vec::new();\n 1244|        if let Some(c) = &self.controller {\n 1245|            v.push(Arc::clone(&c.send));\n 1246|        }\n...\n 1310|    /// Whether `sub` is the current controller (the resize-exclusivity check).\n*1311|    fn is_controller(&self, sub: &SharedSend) -> bool {\n 1312|        self.controller\n 1313|            .as_ref()\n 1314|            .is_some_and(|c| Arc::ptr_eq(&c.send, sub))\n...\n 1330|fn viewer_writer(\n*1331|    send: SharedSend,\n 1332|    session_id: u64,\n 1333|    initial: Vec<Envelope>,\n 1334|    rx: Receiver<Envelope>,\n...\n 1340|        // parking this thread in the OS write forever.\n*1341|        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1342|        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n 1343|            return;\n 1344|        }\n...\n 1364|\n*1365|/// Round-4 observability bridge: a controller writer whose bounded conn write\n 1366|/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n 1367|/// write, or a gate-wait behind one exhausted the bound) IS the\n 1368|/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n...\n 1372|/// death, any non-TimedOut error) stays uncounted, exactly as before.\n*1373|// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1374|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1375|fn note_controller_write_retired(sid: u64, e: &io::Error) {\n 1376|    if e.kind() != io::ErrorKind::TimedOut {\n...\n 1440|    sid: u64,\n*1441|    send: SharedSend,\n 1442|    initial: Vec<(u64, Vec<u8>)>,\n 1443|    rx: Receiver<CtrlMsg>,\n 1444|    delivered_through: Arc<AtomicU64>,\n...\n 1472|        // fix #1 removes the contradictory double-take that produced it.)\n*1473|        // Round-4: the write itself is bounded + cancelable — a non-draining\n 1474|        // consumer poisons + retires the physical conn at the deadline instead\n 1475|        // of wedging this thread in the OS write forever.\n*1476|        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1477|        mark_blocked(true);\n 1478|        let outcome =\n 1479|            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n...\n 2465|    /// UPDATE-WEDGE round-4 int gate's observability seam\n*2466|    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n 2467|    /// physical conn must drive its handler to EOF cleanup within the write\n 2468|    /// bound WITHOUT the client dropping — the test watches this count fall.\n 2469|    conn_handlers: AtomicUsize,\n...\n 2711|        };\n*2712|        // A real (immediately-abandoned) socket pair for the sink's SharedSend\n 2713|        // identity — nothing ever writes to it (the fixture invariant), so the\n 2714|        // dropped peer is irrelevant; the stall signal is the injected\n 2715|        // `write_blocked_since`, not a real socket block.\n#### conn.rs\n 59|//! healthy connections; only the poison/retire path is new.\n*60|// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 61|\n 62|use std::io;\n 63|use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\n...\n 178|            // First poison of this conn: loud, once — the observable fact the\n*179|            // field-acceptance harness (blackhole-controller.ps1) watches for.\n 180|            eprintln!(\n*181|                \"CONN_WRITE_POISONED: physical broker conn retired — a write \\\n 182|                 exceeded its {}ms bound (or failed); in-flight I/O aborted, \\\n*183|                 conn never reused [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 184|                self.bound.as_millis()\n 185|            );\n 186|        }\n...\n 304|/// The shared, serialized, BOUNDED send side of one physical broker\n*305|/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n 306|/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\n 307|pub(crate) struct BrokerConn {\n 308|    inner: Arc<Inner>,\n#### effect.rs\n 810|    // user `effect()` closure (and across two fsyncs). A PtyWrite effect that\n*811|    // blocks (a non-draining child's full PTY input buffer) therefore holds the\n 812|    // GLOBAL journal lock forever — so EVERY other journaled effect (another\n 813|    // keystroke, another attach's journaled input) wedges, and the single-\n 814|    // threaded dispatch can no longer service `spt rc` attaches. Both units below\n...\n 840|                entered_tx.send(()).expect(\"signal entered\");\n*841|                // Block inside the effect — the wedge a non-draining PTY induces.\n 842|                release_rx.recv().expect(\"wait for release\");\n 843|                Ok(())\n 844|            })\n#### nethost.rs\n 43|\n*44|use crate::broker::SharedSend;\n 45|\n 46|use crate::effect::Minter;\n 47|use crate::seedproofx::{prove_membership, MembershipSource, RosterExchange};\n...\n 170|    cap_chunks: usize,\n*171|    subscriber: Option<SharedSend>,\n 172|    /// Read side finished cleanly (peer called finish / conn ended).\n 173|    finished: bool,\n 174|    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n...\n 220|            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\n*221|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 222|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 223|            // the deadline instead of parking this producer.\n 224|            let _ = sub.write(&frame);\n...\n 248|            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n*249|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 250|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 251|            // the deadline instead of parking this producer.\n 252|            let _ = sub.write(&frame);\n...\n 257|    /// `seq >= from_seq` (then eof, if already finished) before live streaming.\n*258|    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n 259|        self.subscriber = Some(Arc::clone(&sub));\n 260|        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {\n 261|            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);\n*262|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 263|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 264|            // the deadline instead of parking this producer.\n 265|            let _ = sub.write(&frame);\n...\n 268|            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n*269|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 270|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 271|            // the deadline instead of parking this producer.\n 272|            let _ = sub.write(&frame);\n...\n 277|    /// not unsubscribe a newer brain).\n*278|    fn detach_if(&mut self, sub: &SharedSend) {\n 279|        if let Some(cur) = &self.subscriber {\n 280|            if Arc::ptr_eq(cur, sub) {\n 281|                self.subscriber = None;\n...\n 297|    cap_events: usize,\n*298|    subscriber: Option<SharedSend>,\n 299|}\n 300|\n 301|impl PresenceLog {\n...\n 369|            let frame = net_presence_event_envelope(&ev);\n*370|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 371|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 372|            // the deadline instead of parking this producer.\n 373|            let _ = sub.write(&frame);\n...\n 378|    /// `seq >= from_seq` before live streaming (the gapless-resume attach).\n*379|    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n 380|        self.subscriber = Some(Arc::clone(&sub));\n 381|        for ev in self.ring.iter().filter(|e| e.seq >= from_seq) {\n 382|            let frame = net_presence_event_envelope(ev);\n*383|            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n 384|            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n 385|            // the deadline instead of parking this producer.\n 386|            let _ = sub.write(&frame);\n...\n 391|    /// not unsubscribe a newer brain).\n*392|    fn detach_if(&mut self, sub: &SharedSend) {\n 393|        if let Some(cur) = &self.subscriber {\n 394|            if Arc::ptr_eq(cur, sub) {\n 395|                self.subscriber = None;\n...\n 1492|        stream_id: u64,\n*1493|        sub: SharedSend,\n 1494|        from_seq: u64,\n 1495|    ) -> io::Result<()> {\n 1496|        let log = {\n...\n 1511|    /// disconnect cleanup; the stream + its ring persist).\n*1512|    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1513|        let log = {\n 1514|            let streams = self.shared.streams.lock().unwrap();\n 1515|            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n...\n 1524|    // [impl->REQ-EP-4]\n*1525|    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n 1526|        self.shared.presence.lock().unwrap().attach(sub, from_seq);\n 1527|    }\n 1528|\n...\n 1530|    /// disconnect cleanup; the log + its ring persist).\n*1531|    pub(crate) fn detach_presence_sub(&self, sub: &SharedSend) {\n 1532|        self.shared.presence.lock().unwrap().detach_if(sub);\n 1533|    }\n 1534|\n#### seedmap.rs\n 291|\n*292|/// Bounded connect for the STOP PATH (REQ-HAZARD-DAEMON-STOP-BARRIER). request_stop's\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\n### tests/\n#### brain_decouple.rs\n 384|// connection: `stall_evict_controller` drops the `ControllerSink` (one\n*385|// `SharedSend` clone + the `JoinHandle`) but neither cancels the in-flight\n 386|// Windows named-pipe write, closes the socket, nor `CancelIoEx`'s it. The\n*387|// already-running `controller_writer` keeps its OWN `SharedSend` clone and stays\n 388|// wedged inside `write_frame` (WriteFileEx + SleepEx(INFINITE, alertable) — no\n 389|// supported write timeout) until the client drops or the process exits (~128 s\n 390|// in the field capture). The `handle_conn` reader for that same conn also never\n 391|// reaches EOF while the client end stays open. This is LOAD-GATED: it needs a\n*392|// real seq>0 controller frame plus a non-draining consumer (seq-0 boot + a\n 393|// quiescent update are clean).\n 394|//\n 395|// This test reproduces that EXACT Windows failure mode with a real PTY child and\n...\n 415|/// and parks again. The seq-0 boot output (VT init + prompt + SEED) gives a real\n*416|/// `seq>0` controller frame; the burst floods a non-draining controller's socket\n 417|/// so its writer parks INSIDE `write_frame` — the exact black-hole the RCA pinned.\n 418|/// Sibling of [`flood_spawn_req`], but FINITE + input-triggered (the flood child\n 419|/// never stops, so it can't prove a frozen cursor / bounded high-water advance).\n...\n 470|\n*471|// [int->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 472|#[cfg(windows)]\n 473|#[test]\n 474|fn non_draining_controller_stall_evict_releases_writer_and_connection() {\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...\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 16|//! NO dead peer required.\n 17|//!\n...\n 19|//! heartbeat stays LIVE while sessions flood. Against the CURRENT tree (where\n*20|//! `resume_sessions` still subscribes) they go RED (the heartbeat starves). Option A\n*21|//! (resume_sessions does NOT subscribe) turns them GREEN. The observable is the number\n 22|//! of heartbeat round-trips that COMPLETE inside a fixed window — robust to the\n 23|//! std `Mutex`'s unfairness (a slow-but-progressing heartbeat still fails the RED bar,\n 24|//! a truly deadlocked one completes ~zero).\n...\n 75|/// A child that FLOODS stdout forever — the heavy-output case that fills a\n*76|/// non-draining subscriber's conn and parks its writer inside `write_frame`.\n 77|fn flood_spawn_req(endpoint: &str) -> SpawnReq {\n 78|    #[cfg(unix)]\n 79|    let (program, args) = (\"yes\".to_string(), vec![\"FLOODFLOODFLOODFLOOD\".to_string()]);\n...\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 138|enum ResumeMode {\n...\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 147|/// shipping daemon heartbeat under flood.\n 148|fn run_heartbeat_probe(\n 149|    name: &str,\n...\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 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 234|}\n 235|\n...\n 257|/// `BrokerConn` watchdog retires a blocked write at `brain_write_deadline()`), so a\n*258|/// non-draining subscriber no longer starves the heartbeat to ~0 and the timing-based\n 259|/// proof was invalidated — it failed REAL on kitsubito Linux CI (\"worst completed 10\").\n 260|/// doyle ruling `docs/UPDATE-WEDGE-R4-STALE-GUARD-RULING.md` (2026-07-10). The two\n 261|/// cursor-only heartbeat tests above remain the POSITIVE liveness proof; THIS is the\n...\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 270|///   still a broken resume, just a different failure. Cursor-only must attach nothing.\n 271|///   SEAM-SENSITIVITY (keeps the proof NON-VACUOUS) — a SECOND brain on the SAME broker\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...\n 278|/// (it writes the `KIND_SUBSCRIBE` frame and does NOT await `KIND_SUBSCRIBED`), and\n*279|/// `resume_sessions` runs its internal `sessions()` round-trip BEFORE sending those\n 280|/// subscribes — so on return the subscribes may not yet be applied. We force a\n 281|/// happens-before with ONE follow-up `sessions()` round-trip on the SAME hazard conn:\n 282|/// the broker dispatches that conn's frames strictly in order and inserts each viewer\n...\n 322|             non-zero count means the brain SUBSCRIBED its own undrained heartbeat conn — \\\n*323|             the round-3 SharedSend self-deadlock class re-opens (post-r4 it instead \\\n 324|             poisons/retires the heartbeat conn via the write watchdog — still a broken \\\n 325|             resume).\"\n 326|        );\n...\n 329|    // SEAM-SENSITIVITY arm — a SECOND brain on the SAME broker takes the hazard path\n*330|    // (`resume_sessions` still exists) and MUST raise every count to >= 1, proving the\n 331|    // seam actually observes the subscription the positive arm forbids (else the Some(0)\n 332|    // above is a vacuous pass against an always-0 read).\n 333|    let mut hazard_brain = Brain::cold_start(&name, 0).expect(\"cold_start hazard brain\");\n*334|    hazard_brain.resume_sessions().expect(\"hazard subscribe resume\");\n*335|    // Deterministic barrier: subscribe_with is fire-and-forget and resume_sessions'\n 336|    // internal round-trip precedes the subscribes, so flush with ONE more same-conn\n 337|    // sessions() round-trip. Per-conn in-order dispatch means all 3 KIND_SUBSCRIBE\n 338|    // frames were applied (add_viewer inserts synchronously) before this reply; the\n#### false_promote.rs\n 20|//! `write_frame` on a black-holed spawner conn. Round-4\n*21|//! (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK) gives every physical conn\n 22|//! write an independent watchdog that CANCELS + retires a wedged write at the SAME\n 23|//! `brain_write_deadline()` bound — so a real parked writer now self-heals at the\n 24|//! deadline and the rig's persistent-wedge precondition cannot exist (that is the\n#### inject_control_wedge.rs\n 10|//! VIEWERS instead get a dedicated writer thread + a bounded, evicting\n*11|//! `sync_channel` and can never stall the drain. So a slow / non-draining\n 12|//! controller (here a controller connection that stops reading while the child\n 13|//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output\n 14|//! stalled and any path needing that log lock (a fresh attach, or the global\n...\n 498|// effect closure. A journaled PtyWrite (every operator keystroke carrying an\n*499|// op_id) runs `write_input` INSIDE that lock. If a non-draining child's PTY input\n 500|// buffer is full, `write_input` BLOCKS — holding the journal lock forever. So the\n 501|// single-threaded dispatch can no longer service ANY journaled effect, and a\n 502|// concurrent `spt rc` attach dies with \"brain IPC read deadline elapsed\" (facet\n...\n 777|        // on the serve thread: a controller's `serve_attach` forward can block on a\n*778|        // non-draining operator socket while the flood child keeps producing — the\n 779|        // main thread kills the child to unblock it. Reporting first then draining\n 780|        // keeps the watchdog free of that wedge.\n 781|        let _ = result_tx.send((subscribed, got_output));\n...\n 2456|//    exercised an INDEFINITE block. THIS gate makes `write_input` GENUINELY PARK on\n*2457|//    a non-draining harness (forkpty's small kernel input queue on Unix; ConPTY\n 2458|//    absorbs on Windows — the documented platform-divergence stance the rest of\n 2459|//    this file uses), then proves the fix: with one input-writer thread per session\n 2460|//    as the SOLE blocking writer, the parked write parks ONLY its own thread —\n\nShowing files 1-20 of 31+. Use skip=20 for the next page, or narrow paths/pattern.\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"spt-core/docs, spt-core/*.md, spt-core/crates/spt-daemon","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":118,"fileCount":20,"files":["spt-core/M12-W2.5-PLAN.md","spt-core/M12-W2.5-RULING.md","spt-core/RESTORATION-D1-PLAN.md","spt-core/RESTORATION-D4-PLAN.md","spt-core/RESTORATION-D5-PLAN.md","spt-core/RESTORATION-D6-PLAN.md","spt-core/RESTORATION-D7-PLAN.md","spt-core/RESTORATION-PLAN.md","spt-core/V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md","spt-core/crates/spt-daemon/src/brain.rs","spt-core/crates/spt-daemon/src/brainproc.rs","spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/conn.rs","spt-core/crates/spt-daemon/src/effect.rs","spt-core/crates/spt-daemon/src/nethost.rs","spt-core/crates/spt-daemon/src/seedmap.rs","spt-core/crates/spt-daemon/tests/brain_decouple.rs","spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/crates/spt-daemon/tests/false_promote.rs","spt-core/crates/spt-daemon/tests/inject_control_wedge.rs"],"fileMatches":[{"path":"spt-core/M12-W2.5-PLAN.md","count":4},{"path":"spt-core/M12-W2.5-RULING.md","count":2},{"path":"spt-core/RESTORATION-D1-PLAN.md","count":1},{"path":"spt-core/RESTORATION-D4-PLAN.md","count":7},{"path":"spt-core/RESTORATION-D5-PLAN.md","count":2},{"path":"spt-core/RESTORATION-D6-PLAN.md","count":2},{"path":"spt-core/RESTORATION-D7-PLAN.md","count":2},{"path":"spt-core/RESTORATION-PLAN.md","count":1},{"path":"spt-core/V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md","count":3},{"path":"spt-core/crates/spt-daemon/src/brain.rs","count":16},{"path":"spt-core/crates/spt-daemon/src/brainproc.rs","count":4},{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/conn.rs","count":5},{"path":"spt-core/crates/spt-daemon/src/effect.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/nethost.rs","count":17},{"path":"spt-core/crates/spt-daemon/src/seedmap.rs","count":1},{"path":"spt-core/crates/spt-daemon/tests/brain_decouple.rs","count":5},{"path":"spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","count":19},{"path":"spt-core/crates/spt-daemon/tests/false_promote.rs","count":1},{"path":"spt-core/crates/spt-daemon/tests/inject_control_wedge.rs","count":4}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# spt-core/\n## M12-W2.5-PLAN.md\n  18│  notice slots in there. rc sends **no resize** today (geometry static).\n *19│- **broker** `OutputLog.subscriber: Option<SharedSend>` (broker.rs:87), `SharedSend =\n  20│  Arc<Mutex<SendHalf>>` (a brain↔broker IPC local-socket write half, one per attach connection).\n  21│  `append` (broker.rs:137) holds the log lock and does a **blocking** `write_frame`+flush to the\n  22│  subscriber → **a slow subscriber stalls the single drain thread** = the viewer-isolation hazard.\n    │...\n  35│**Q1 — viewer collection + eviction signal.**\n *36│`OutputLog`: `subscriber` → `controller: Option<SharedSend>` + `viewers: HashMap<u64 attach_id,\n  37│ViewerSink>` (keyed by attach id for clean removal). **Eviction = bounded-queue overflow, NOT a\n  38│shared blocking write** (no heartbeat): each viewer gets a small **bounded SPSC channel + a\n  39│dedicated viewer-writer thread** that does the blocking socket write. `append` fans out under the\n    │...\n 214│- **T2 DONE + GREEN (broker)** — full `spt-daemon` suite 285 + all int green (3× stable, no flake).\n*215│  OutputLog → `controller: Option<SharedSend>` + `controller_by: Option<String>` + `viewers:\n 216│  HashMap<u64, ViewerSink>` + `endpoint`/`size`. `append` fan-out: controller blocking authoritative\n 217│  (advances `delivered_through`), viewers `try_send`→bounded channel (depth 256)→`viewer_writer`\n 218│  thread, evict-on-Full/Disconnected (drain never touches a viewer socket); soft cap MAX_VIEWERS=32.\n    │...\n 270│  (dispatch.rs `worker` → `connect(broker_name)`), so each attach is a DISTINCT broker IPC\n*271│  `SharedSend` → the broker can hold controller + viewers as distinct sinks. `HostedSession.endpoint`\n 272│  (broker.rs:209) carries the label, so the broker CAN own the `driven_by`/`viewer_count` writes.\n 273│  IPC `subscribe` (brain.rs:998) is fire-and-forget today → the controller/viewer/busy/took OUTCOME\n 274│  + the Displaced notice need IPC additions (SubscribeReq role + a reply/outcome + a KIND_DISPLACED\n## M12-W2.5-RULING.md\n 16│## Current impl gap (verified)\n*17│`OutputLog.subscriber: Option<SharedSend>` (broker.rs:87) is SINGULAR — one subscriber, silent\n 18│last-wins displace. `--view` (rc.rs:165) is only a client-side no-input choice; at the broker it\n 19│still takes the single slot + silently displaces (the forbidden anti-pattern). The controller/\n 20│viewer model is UNBUILT. PTY-ownership (child survives any attach/detach — broker.rs:28-31) DOES\n   │...\n 25│### 1. Broker — multi-subscriber `OutputLog`\n*26│- `subscriber: Option<SharedSend>` → **`controller: Option<SharedSend>` + `viewers: <keyed collection>`** (key by attach-id so a viewer removes cleanly). Append fans the frame out to the controller AND every viewer, **under the existing log lock**, each in strict `seq` order (preserve the no-interleave invariant).\n 27│- **Brain-resume cursor (`delivered_through`, ADR-0018) tracks the CONTROLLER only.** Viewers are ephemeral: each replays from its own `subscribe` cursor (the existing `attach(sub, from_seq)` per-attach replay), and a viewer's write success/failure NEVER moves `delivered_through`. This preserves at-least-once brain resume.\n 28│\n 29│### 2. Viewer isolation — the new hazard (load-bearing)\n## RESTORATION-D1-PLAN.md\n 27│   - `run_brain()`: the `spt daemon brain` child entry. Connect to the broker\n*28│     (`Brain::cold_start`, bounded connect-retry — the broker may still be\n 29│     binding), write the ready breadcrumb, then idle: hold the connection,\n 30│     heartbeat the breadcrumb, exit non-zero if the broker connection drops\n 31│     (so the supervisor respawns). No de-elevation guard (inherits the\n## RESTORATION-D4-PLAN.md\n  65│  net-consumers + shellwake, both re-derived from disk / `net-status` on start.\n *66│  So `run_brain` calling `resume_sessions()` is a **no-op today** (zero hosted\n  67│  sessions) and **forward-correct** when daemon-hosted sessions arrive with the\n  68│  live-agent adapter. D4 builds + **proves the capability with a real\n  69│  multi-session harness now** (activate-don't-pre-fail: the machinery is real and\n    │...\n 166│    accepted, not rejected. Evicted tail lost only; live stream survives.\n*167│- **`Brain::resume_sessions(&mut self)`** — the cold-start continuity primitive:\n 168│  call `sessions()`, and for each returned `SessionInfo` `subscribe(session_id,\n 169│  resume_seq)` and seed `session_cursors[session_id] = resume_seq`. Replaces the\n 170│  master plan's stale \"re-attach `sessions.first()` from 0\" with \"re-attach **all**\n    │...\n 187│**hard-killed — no `snapshot()` taken** (doyle amendment 4: this same mode is\n*188│D4-3's falsifiable guard), a fresh cold-start brain `resume_sessions` re-attaches\n 189│**every** session from its cursor — all resume, **no duplicate** output, gapless,\n 190│child pids unchanged; **plus the clamp case (doyle watch-item): a `resume_seq`\n 191│below the broker ring floor after a long outage → resume succeeds, the brain\n    │...\n 205│  `Brain::snapshot` as **test-only continuity** in their rustdoc: the production\n*206│  resume path is `cold_start` + `resume_sessions` (the broker is cursor-of-record,\n 207│  D4-1/D4-2); these types remain `pub` solely because the **integration tests**\n 208│  (separate test target — `#[cfg(test)]` would hide them) still drive the\n 209│  handoff-frame shape directly. Add the matching note to KNOWN-HAZARDS (the 2.4\n    │...\n 215│  (SIGKILL / `child.kill()` — no `snapshot()` possible)**, then a fresh\n*216│  `cold_start` + `resume_sessions` resumes every session gaplessly. A brain that\n 217│  was never given the chance to snapshot, yet resumes cleanly, demonstrates the\n 218│  `BrainState` frame is not on the production path. D4-3 then carries **no new\n 219│  machinery** — only the rustdoc/KNOWN-HAZARDS tags + the documented grep-clean\n    │...\n 234│D4-1 (broker delivered-cursor + `resume_seq` on the reply — the new persistent\n*235│surface) → **D4-2** (brain consumes it: per-session cursors + `resume_sessions`,\n 236│wired into `run_brain`) → **D4-3** (confirm + lock the `BrainState` retirement).\n 237│D4-1 before D4-2 because the brain resumes from the broker-supplied cursor; D4-3\n 238│last because it asserts the end-state both produce.\n    │...\n 298│that the cursor survives a detach. Then D4-2 (brain `session_cursors` +\n*299│`resume_sessions`, wired into `run_brain`).\n## RESTORATION-D5-PLAN.md\n 220│  **(Open call resolved by doyle → pure helper, 3 reasons: V3's target is\n*221│  untested dead code so a tested-unwired derive is the D4 `resume_sessions`\n 222│  posture exactly; the D5-3 evidence tag already presumes a helper and docs-only\n 223│  would orphan it; falsifiability now as executable asserts. The never-reset-on-\n 224│  crash asymmetry vs the periodic rule is exactly the kind of thing that drifts\n    │...\n 265│> `OneShotDeadline::fire_if_due(now)` in `deadline.rs` — tested, unwired, the D4\n*266│> `resume_sessions` posture (real machinery, real test, no live wiring). Pure\n 267│> derive + minimal `target_ms` persist only; `load` ignores `StartReason`; no\n 268│> scheduler/timer/thread. The DEFERRED.md row remains the scheduler record,\n 269│> cross-ref'd from the helper rustdoc.\n## RESTORATION-D6-PLAN.md\n  98│- **Forward-correct, not field-exercised (the D4/D5 posture, verbatim).** The\n *99│  daemon brain hosts no PTY sessions today, so `resume_sessions`\n 100│  (`brain.rs:596-608`) is a near-noop and a real *field* update is not driven on\n 101│  a single-home dev box. D6 is therefore **forward-correct capability + a real\n 102│  spawn/kill/respawn harness proof** — the readiness-gate + rollback machinery is\n    │...\n 204│  - **Post-resume, not post-connect** — write ready only **after**\n*205│    `resume_sessions` (`brain.rs:596-608`) completes + loops are resumed, i.e.\n 206│    the design's \"re-attached all sessions + resumed loops,\" not merely \"socket\n 207│    connected\" (today's `:162` write is post-connect). Today resume is a near-noop\n 208│    (no hosted sessions), so this is forward-correct: the gate is *defined*\n## RESTORATION-D7-PLAN.md\n 176│  --generation N --start-reason …` (env inherited per `spawn_brain_child`'s own\n*177│  posture); `run_brain` `connect_retry`s the test broker socket; `resume_sessions`\n 178│  re-attaches the test-established session. **This makes D7-1 the FIRST\n 179│  process-level exercise of the D4 cursor-of-record resume with N≥1 hosted\n 180│  sessions** — that alone justifies the real entry over a fixture.\n    │...\n 501│   injected `spawn_child` execs `P daemon brain --generation N --start-reason …`,\n*502│   `run_brain` connect-retries, `resume_sessions` re-attaches). This makes D7-1 the\n 503│   **first process-level exercise of the D4 cursor-of-record resume with N≥1 hosted\n 504│   sessions**. Test drive establishes PTY+QUIC once, drops its client **hard** (D4\n 505│   posture); the real child resumes; assert from the in-process broker's own state.\n## RESTORATION-PLAN.md\n 229│  output). (The original `applyhost.rs:239` site is gone; the equivalent\n*230│  cold-start re-attach is `run_brain` → `Brain::resume_sessions`.)\n 231│- **Output is at-least-once** (broker resumes from last-*sent*); **input/\n 232│  effects stay exactly-once** via the already-broker-owned `EffectJournal`.\n 233│  Matches the SPIKE-05 terminal-stream contract (a resize repaint already\n## V0.13.0-P1C-CONTROLLER-WRITER-REORDER-PLAN.md\n 21│\n*22│`become_controller` (broker.rs:349-375) drops the prior `ControllerSink` (its `tx`) but **does not stop the prior writer thread** — writer-A keeps flushing its `initial` batch. Both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`, broker.rs:364) and `controller_writer` (broker.rs:703-737) writes its `initial` batch frame-by-frame under that mutex with **no inter-thread ordering**. When writer-A's seq 1 wins the socket before writer-B's seq 0, the consumer (strict legacy path, brain.r...\n 23│\n 24│### RACEDIAG trace (failing interleaving)\n 25│```\n   │...\n 52│\n*53│3. **DEFENSE-IN-DEPTH — seed `session_cursors` on handoff** (brain.rs:341 is currently empty) so the consumer uses the dedup-below + snap-above path (brain.rs:582-595), matching production `resume_sessions` (brain.rs:830) and cold-start. The strict reject-gap legacy path (brain.rs:599-608) is a latent trap for any at-least-once reorder; production resume already avoids it.\n 54│\n 55│## Traceability (mint FIRST, then satisfy — rule 3)\n 56│\n 57│Add to `traceable-reqs.toml`:\n*58│- **`REQ-HAZARD-CONTROLLER-WRITER-REORDER`** — \"Two `controller_writer` threads must never race one brain connection's socket: `become_controller` superseding a controller leaves the prior writer able to flush its `initial` batch concurrently with the new writer on the same `SharedSend`, producing a non-monotonic on-wire seq stream (`got seq 1 want 0`) that the strict consumer rejects (gap) or that drops an un-sent lower seq under snap-above (byte loss). Invariant: one live `controller_writer` per conn...\n 59│- KNOWN-HAZARDS.md entry (next 7.x) with the failure / invariant / spt-core-mapping / source structure.\n 60│\n 61│## Gate (non-vacuous — doyle)\n\n## crates/spt-daemon/\n\n### src/\n#### brain.rs\n   76│/// respawns reconstructs continuity by querying the broker\n  *77│/// ([`Brain::resume_sessions`] over the broker's cursor-of-record), never this\n   78│/// message. `BrainState` / [`Brain::handoff`] / [`Brain::snapshot`] stay `pub`\n   79│/// solely because the integration tests (separate test target — `#[cfg(test)]`\n   80│/// would hide them) still drive the handoff-frame shape directly; there is **no\n     │...\n  282│    /// continuity state a cold-started brain rebuilds from the broker via\n *283│    /// [`Brain::resume_sessions`], replacing the retired single-session\n  284│    /// `BrainState` handoff. A session present here is in **resume mode** — its\n  285│    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n  286│    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n     │...\n  380│    /// **Test-only (restoration D4-3).** The production resume path is\n *381│    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n  382│    /// never this brain→brain frame; `handoff` survives only for the integration\n  383│    /// tests. See [`BrainState`].\n  384│    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n     │...\n  389│        // `read_event` routes this session through the dedup-below + snap-above\n *390│        // path (matching `resume_sessions`), not the legacy reject-gap path that\n  391│        // turns any at-least-once boundary reorder into a fatal `output gap`.\n  392│        let mut session_cursors = std::collections::HashMap::new();\n  393│        session_cursors.insert(prior.session_id, prior.next_seq);\n     │...\n  642│                    // A **resume-mode** brain (the per-session map is populated by\n *643│                    // `resume_sessions`) routes EVERY session through the map —\n  644│                    // including a freshly-spawned or first-seen one, which seeds\n  645│                    // itself at its first `seq` via `entry().or_insert` (D4-2b,\n  646│                    // doyle amendment 5: this closes the mixed-mode gap where N≥2\n     │...\n 1029│    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK). This subscribes the sessions onto THIS conn.\n*1030│    /// A NON-DRAINING consumer will SELF-DEADLOCK: an actively-streaming session backs\n 1031│    /// the conn up, the broker-side subscriber writer blocks holding the conn's single\n*1032│    /// `SharedSend` mutex ACROSS a blocking `write_frame` (broker.rs\n 1033│    /// `viewer_writer`/`controller_writer`), the dispatch reply path (`send_frame`) needs\n*1034│    /// that same lock, so any request/reply the non-draining consumer issues is starved\n 1035│    /// (dead-peer-independent — the v0.30.4 field re-wedge). The supervised daemon brain\n*1036│    /// is exactly such a non-draining consumer (its `run_brain` heartbeat reads this conn\n 1037│    /// only in `net_status()`/`sessions()` drain-and-discard loops; it drives no PTY\n 1038│    /// session — brainproc.rs:184 — and nothing on its side consumes the output, §3\n 1039│    /// guard), so it MUST call [`Self::resume_session_cursors`] (cursor-only, NO\n     │...\n 1045│    // [doc->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n*1046│    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {\n 1047│        let reply = self.sessions()?;\n 1048│        let mut resumed = Vec::with_capacity(reply.sessions.len());\n 1049│        for info in reply.sessions {\n     │...\n 1060│\n*1061│    /// Cursor-only resume for a NON-DRAINING caller (the supervised daemon brain): query\n 1062│    /// every hosted session and seed [`Self::session_cursors`] at each `resume_seq`,\n 1063│    /// returning the re-attached ids — but DO **NOT** subscribe. This is the safe resume\n*1064│    /// the daemon `run_brain` heartbeat uses in place of [`Self::resume_sessions`]:\n 1065│    /// carrying only request/reply on the brain's conn (no UNDRAINED subscription) makes\n*1066│    /// the `SharedSend` self-deadlock structurally impossible (UPDATE-WEDGE round 3,\n 1067│    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK — see the deadlock warning on\n*1068│    /// [`Self::resume_sessions`]). The daemon brain drives no broker PTY session\n 1069│    /// (brainproc.rs:184) and nothing on its side consumes session output (§3 guard), so\n 1070│    /// the observation cursor is all it needs; no attach also means no controller steal\n 1071│    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL, a fortiori). Rides the brain-swap ⇒ seamless.\n     │...\n 1088│    /// The per-session resume cursor (the next seq still needed for a session\n*1089│    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.\n 1090│    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {\n 1091│        self.session_cursors.get(&session_id).copied()\n 1092│    }\n     │...\n 1605│    /// `spt rc`) it never drains, freezing the session\n*1606│    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL). [`Brain::resume_sessions`] re-attaches\n 1607│    /// as Viewer instead; Control is reserved for sessions the daemon brain drives.\n 1608│    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n 1609│    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n#### brainproc.rs\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#### broker.rs\n   78│/// handle under the same gate discipline. Round-4\n  *79│/// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): no longer a raw\n   80│/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n   81│/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n   82│/// matching stays `Arc::ptr_eq`.\n  *83│pub(crate) type SharedSend = Arc<BrokerConn>;\n   84│\n   85│/// Default bounded output-log depth (chunks). Large enough that a brain restart\n   86│/// window never evicts unreplayed output in practice; the bound is what keeps a\n     │...\n  122│    /// (a dropped brain connection removes its viewer rows); never written here.\n *123│    send: SharedSend,\n  124│    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n  125│    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n  126│    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n     │...\n  498│    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n *499│    send: SharedSend,\n  500│    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n  501│    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n  502│    by: Option<String>,\n     │...\n  890│    /// controller's identity.\n *891│    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {\n  892│        // Drop the prior controller sink first (its writer's live loop ends when\n  893│        // tx drops), and bump the generation so (a) an in-flight deadline-evict\n  894│        // for the old controller can't unseat this one and (b) a prior writer\n     │...\n 1072│    /// live queue. The drain thread fans live frames in via `try_send`.\n*1073│    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {\n 1074│        let vid = self.next_viewer_id;\n 1075│        self.next_viewer_id += 1;\n 1076│        // The size frame + the initial batch (owned by the thread, not the bounded\n     │...\n 1115│        &mut self,\n*1116│        sub: SharedSend,\n 1117│        from_seq: u64,\n 1118│        intent: AttachIntent,\n 1119│        by: Option<String>,\n     │...\n 1224│    /// `driven_by` / `viewer_count` when a role actually changed.\n*1225│    fn detach_if(&mut self, sub: &SharedSend) {\n 1226│        let is_ctrl = self\n 1227│            .controller\n 1228│            .as_ref()\n     │...\n 1241│    /// an `exit` frame.\n*1242│    fn all_sinks(&self) -> Vec<SharedSend> {\n*1243│        let mut v: Vec<SharedSend> = Vec::new();\n 1244│        if let Some(c) = &self.controller {\n 1245│            v.push(Arc::clone(&c.send));\n 1246│        }\n     │...\n 1310│    /// Whether `sub` is the current controller (the resize-exclusivity check).\n*1311│    fn is_controller(&self, sub: &SharedSend) -> bool {\n 1312│        self.controller\n 1313│            .as_ref()\n 1314│            .is_some_and(|c| Arc::ptr_eq(&c.send, sub))\n     │...\n 1330│fn viewer_writer(\n*1331│    send: SharedSend,\n 1332│    session_id: u64,\n 1333│    initial: Vec<Envelope>,\n 1334│    rx: Receiver<Envelope>,\n     │...\n 1340│        // parking this thread in the OS write forever.\n*1341│        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1342│        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n 1343│            return;\n 1344│        }\n     │...\n 1364│\n*1365│/// Round-4 observability bridge: a controller writer whose bounded conn write\n 1366│/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n 1367│/// write, or a gate-wait behind one exhausted the bound) IS the\n 1368│/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n     │...\n 1372│/// death, any non-TimedOut error) stays uncounted, exactly as before.\n*1373│// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1374│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1375│fn note_controller_write_retired(sid: u64, e: &io::Error) {\n 1376│    if e.kind() != io::ErrorKind::TimedOut {\n     │...\n 1440│    sid: u64,\n*1441│    send: SharedSend,\n 1442│    initial: Vec<(u64, Vec<u8>)>,\n 1443│    rx: Receiver<CtrlMsg>,\n 1444│    delivered_through: Arc<AtomicU64>,\n     │...\n 1472│        // fix #1 removes the contradictory double-take that produced it.)\n*1473│        // Round-4: the write itself is bounded + cancelable — a non-draining\n 1474│        // consumer poisons + retires the physical conn at the deadline instead\n 1475│        // of wedging this thread in the OS write forever.\n*1476│        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 1477│        mark_blocked(true);\n 1478│        let outcome =\n 1479│            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n     │...\n 2465│    /// UPDATE-WEDGE round-4 int gate's observability seam\n*2466│    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n 2467│    /// physical conn must drive its handler to EOF cleanup within the write\n 2468│    /// bound WITHOUT the client dropping — the test watches this count fall.\n 2469│    conn_handlers: AtomicUsize,\n     │...\n 2711│        };\n*2712│        // A real (immediately-abandoned) socket pair for the sink's SharedSend\n 2713│        // identity — nothing ever writes to it (the fixture invariant), so the\n 2714│        // dropped peer is irrelevant; the stall signal is the injected\n 2715│        // `write_blocked_since`, not a real socket block.\n#### conn.rs\n  59│//! healthy connections; only the poison/retire path is new.\n *60│// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n  61│\n  62│use std::io;\n  63│use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\n    │...\n 178│            // First poison of this conn: loud, once — the observable fact the\n*179│            // field-acceptance harness (blackhole-controller.ps1) watches for.\n 180│            eprintln!(\n*181│                \"CONN_WRITE_POISONED: physical broker conn retired — a write \\\n 182│                 exceeded its {}ms bound (or failed); in-flight I/O aborted, \\\n*183│                 conn never reused [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 184│                self.bound.as_millis()\n 185│            );\n 186│        }\n    │...\n 304│/// The shared, serialized, BOUNDED send side of one physical broker\n*305│/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n 306│/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\n 307│pub(crate) struct BrokerConn {\n 308│    inner: Arc<Inner>,\n#### effect.rs\n 810│    // user `effect()` closure (and across two fsyncs). A PtyWrite effect that\n*811│    // blocks (a non-draining child's full PTY input buffer) therefore holds the\n 812│    // GLOBAL journal lock forever — so EVERY other journaled effect (another\n 813│    // keystroke, another attach's journaled input) wedges, and the single-\n 814│    // threaded dispatch can no longer service `spt rc` attaches. Both units below\n    │...\n 840│                entered_tx.send(()).expect(\"signal entered\");\n*841│                // Block inside the effect — the wedge a non-draining PTY induces.\n 842│                release_rx.recv().expect(\"wait for release\");\n 843│                Ok(())\n 844│            })\n#### nethost.rs\n   43│\n  *44│use crate::broker::SharedSend;\n   45│\n   46│use crate::effect::Minter;\n   47│use crate::seedproofx::{prove_membership, MembershipSource, RosterExchange};\n     │...\n  170│    cap_chunks: usize,\n *171│    subscriber: Option<SharedSend>,\n  172│    /// Read side finished cleanly (peer called finish / conn ended).\n  173│    finished: bool,\n  174│    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n     │...\n  220│            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\n *221│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  222│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  223│            // the deadline instead of parking this producer.\n  224│            let _ = sub.write(&frame);\n     │...\n  248│            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n *249│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  250│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  251│            // the deadline instead of parking this producer.\n  252│            let _ = sub.write(&frame);\n     │...\n  257│    /// `seq >= from_seq` (then eof, if already finished) before live streaming.\n *258│    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n  259│        self.subscriber = Some(Arc::clone(&sub));\n  260│        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {\n  261│            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);\n *262│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  263│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  264│            // the deadline instead of parking this producer.\n  265│            let _ = sub.write(&frame);\n     │...\n  268│            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n *269│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  270│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  271│            // the deadline instead of parking this producer.\n  272│            let _ = sub.write(&frame);\n     │...\n  277│    /// not unsubscribe a newer brain).\n *278│    fn detach_if(&mut self, sub: &SharedSend) {\n  279│        if let Some(cur) = &self.subscriber {\n  280│            if Arc::ptr_eq(cur, sub) {\n  281│                self.subscriber = None;\n     │...\n  297│    cap_events: usize,\n *298│    subscriber: Option<SharedSend>,\n  299│}\n  300│\n  301│impl PresenceLog {\n     │...\n  369│            let frame = net_presence_event_envelope(&ev);\n *370│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  371│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  372│            // the deadline instead of parking this producer.\n  373│            let _ = sub.write(&frame);\n     │...\n  378│    /// `seq >= from_seq` before live streaming (the gapless-resume attach).\n *379│    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n  380│        self.subscriber = Some(Arc::clone(&sub));\n  381│        for ev in self.ring.iter().filter(|e| e.seq >= from_seq) {\n  382│            let frame = net_presence_event_envelope(ev);\n *383│            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n  384│            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n  385│            // the deadline instead of parking this producer.\n  386│            let _ = sub.write(&frame);\n     │...\n  391│    /// not unsubscribe a newer brain).\n *392│    fn detach_if(&mut self, sub: &SharedSend) {\n  393│        if let Some(cur) = &self.subscriber {\n  394│            if Arc::ptr_eq(cur, sub) {\n  395│                self.subscriber = None;\n     │...\n 1492│        stream_id: u64,\n*1493│        sub: SharedSend,\n 1494│        from_seq: u64,\n 1495│    ) -> io::Result<()> {\n 1496│        let log = {\n     │...\n 1511│    /// disconnect cleanup; the stream + its ring persist).\n*1512│    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1513│        let log = {\n 1514│            let streams = self.shared.streams.lock().unwrap();\n 1515│            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n     │...\n 1524│    // [impl->REQ-EP-4]\n*1525│    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n 1526│        self.shared.presence.lock().unwrap().attach(sub, from_seq);\n 1527│    }\n 1528│\n     │...\n 1530│    /// disconnect cleanup; the log + its ring persist).\n*1531│    pub(crate) fn detach_presence_sub(&self, sub: &SharedSend) {\n 1532│        self.shared.presence.lock().unwrap().detach_if(sub);\n 1533│    }\n 1534│\n#### seedmap.rs\n 291│\n*292│/// Bounded connect for the STOP PATH (REQ-HAZARD-DAEMON-STOP-BARRIER). request_stop's\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\n### tests/\n#### brain_decouple.rs\n 384│// connection: `stall_evict_controller` drops the `ControllerSink` (one\n*385│// `SharedSend` clone + the `JoinHandle`) but neither cancels the in-flight\n 386│// Windows named-pipe write, closes the socket, nor `CancelIoEx`'s it. The\n*387│// already-running `controller_writer` keeps its OWN `SharedSend` clone and stays\n 388│// wedged inside `write_frame` (WriteFileEx + SleepEx(INFINITE, alertable) — no\n 389│// supported write timeout) until the client drops or the process exits (~128 s\n 390│// in the field capture). The `handle_conn` reader for that same conn also never\n 391│// reaches EOF while the client end stays open. This is LOAD-GATED: it needs a\n*392│// real seq>0 controller frame plus a non-draining consumer (seq-0 boot + a\n 393│// quiescent update are clean).\n 394│//\n 395│// This test reproduces that EXACT Windows failure mode with a real PTY child and\n    │...\n 415│/// and parks again. The seq-0 boot output (VT init + prompt + SEED) gives a real\n*416│/// `seq>0` controller frame; the burst floods a non-draining controller's socket\n 417│/// so its writer parks INSIDE `write_frame` — the exact black-hole the RCA pinned.\n 418│/// Sibling of [`flood_spawn_req`], but FINITE + input-triggered (the flood child\n 419│/// never stops, so it can't prove a frozen cursor / bounded high-water advance).\n    │...\n 470│\n*471│// [int->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 472│#[cfg(windows)]\n 473│#[test]\n 474│fn non_draining_controller_stall_evict_releases_writer_and_connection() {\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    │...\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  16│//! NO dead peer required.\n  17│//!\n    │...\n  19│//! heartbeat stays LIVE while sessions flood. Against the CURRENT tree (where\n *20│//! `resume_sessions` still subscribes) they go RED (the heartbeat starves). Option A\n *21│//! (resume_sessions does NOT subscribe) turns them GREEN. The observable is the number\n  22│//! of heartbeat round-trips that COMPLETE inside a fixed window — robust to the\n  23│//! std `Mutex`'s unfairness (a slow-but-progressing heartbeat still fails the RED bar,\n  24│//! a truly deadlocked one completes ~zero).\n    │...\n  75│/// A child that FLOODS stdout forever — the heavy-output case that fills a\n *76│/// non-draining subscriber's conn and parks its writer inside `write_frame`.\n  77│fn flood_spawn_req(endpoint: &str) -> SpawnReq {\n  78│    #[cfg(unix)]\n  79│    let (program, args) = (\"yes\".to_string(), vec![\"FLOODFLOODFLOODFLOOD\".to_string()]);\n    │...\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 138│enum ResumeMode {\n    │...\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 147│/// shipping daemon heartbeat under flood.\n 148│fn run_heartbeat_probe(\n 149│    name: &str,\n    │...\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 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 234│}\n 235│\n    │...\n 257│/// `BrokerConn` watchdog retires a blocked write at `brain_write_deadline()`), so a\n*258│/// non-draining subscriber no longer starves the heartbeat to ~0 and the timing-based\n 259│/// proof was invalidated — it failed REAL on kitsubito Linux CI (\"worst completed 10\").\n 260│/// doyle ruling `docs/UPDATE-WEDGE-R4-STALE-GUARD-RULING.md` (2026-07-10). The two\n 261│/// cursor-only heartbeat tests above remain the POSITIVE liveness proof; THIS is the\n    │...\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 270│///   still a broken resume, just a different failure. Cursor-only must attach nothing.\n 271│///   SEAM-SENSITIVITY (keeps the proof NON-VACUOUS) — a SECOND brain on the SAME broker\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    │...\n 278│/// (it writes the `KIND_SUBSCRIBE` frame and does NOT await `KIND_SUBSCRIBED`), and\n*279│/// `resume_sessions` runs its internal `sessions()` round-trip BEFORE sending those\n 280│/// subscribes — so on return the subscribes may not yet be applied. We force a\n 281│/// happens-before with ONE follow-up `sessions()` round-trip on the SAME hazard conn:\n 282│/// the broker dispatches that conn's frames strictly in order and inserts each viewer\n    │...\n 322│             non-zero count means the brain SUBSCRIBED its own undrained heartbeat conn — \\\n*323│             the round-3 SharedSend self-deadlock class re-opens (post-r4 it instead \\\n 324│             poisons/retires the heartbeat conn via the write watchdog — still a broken \\\n 325│             resume).\"\n 326│        );\n    │...\n 329│    // SEAM-SENSITIVITY arm — a SECOND brain on the SAME broker takes the hazard path\n*330│    // (`resume_sessions` still exists) and MUST raise every count to >= 1, proving the\n 331│    // seam actually observes the subscription the positive arm forbids (else the Some(0)\n 332│    // above is a vacuous pass against an always-0 read).\n 333│    let mut hazard_brain = Brain::cold_start(&name, 0).expect(\"cold_start hazard brain\");\n*334│    hazard_brain.resume_sessions().expect(\"hazard subscribe resume\");\n*335│    // Deterministic barrier: subscribe_with is fire-and-forget and resume_sessions'\n 336│    // internal round-trip precedes the subscribes, so flush with ONE more same-conn\n 337│    // sessions() round-trip. Per-conn in-order dispatch means all 3 KIND_SUBSCRIBE\n 338│    // frames were applied (add_viewer inserts synchronously) before this reply; the\n#### false_promote.rs\n 20│//! `write_frame` on a black-holed spawner conn. Round-4\n*21│//! (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK) gives every physical conn\n 22│//! write an independent watchdog that CANCELS + retires a wedged write at the SAME\n 23│//! `brain_write_deadline()` bound — so a real parked writer now self-heals at the\n 24│//! deadline and the rig's persistent-wedge precondition cannot exist (that is the\n#### inject_control_wedge.rs\n   10│//! VIEWERS instead get a dedicated writer thread + a bounded, evicting\n  *11│//! `sync_channel` and can never stall the drain. So a slow / non-draining\n   12│//! controller (here a controller connection that stops reading while the child\n   13│//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output\n   14│//! stalled and any path needing that log lock (a fresh attach, or the global\n     │...\n  498│// effect closure. A journaled PtyWrite (every operator keystroke carrying an\n *499│// op_id) runs `write_input` INSIDE that lock. If a non-draining child's PTY input\n  500│// buffer is full, `write_input` BLOCKS — holding the journal lock forever. So the\n  501│// single-threaded dispatch can no longer service ANY journaled effect, and a\n  502│// concurrent `spt rc` attach dies with \"brain IPC read deadline elapsed\" (facet\n     │...\n  777│        // on the serve thread: a controller's `serve_attach` forward can block on a\n *778│        // non-draining operator socket while the flood child keeps producing — the\n  779│        // main thread kills the child to unblock it. Reporting first then draining\n  780│        // keeps the watchdog free of that wedge.\n  781│        let _ = result_tx.send((subscribed, got_output));\n     │...\n 2456│//    exercised an INDEFINITE block. THIS gate makes `write_input` GENUINELY PARK on\n*2457│//    a non-draining harness (forkpty's small kernel input queue on Unix; ConPTY\n 2458│//    absorbs on Windows — the documented platform-divergence stance the rest of\n 2459│//    this file uses), then proves the fix: with one input-writer thread per session\n 2460│//    as the SOLE blocking writer, the parked write parks ONLY its own thread —","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1783730506210}}
{"type":"message","id":"7a908a13","parentId":"714d995d","timestamp":"2026-07-11T00:41:46.261Z","message":{"role":"toolResult","toolCallId":"call_eG5tiJdDKffODrO9lvfOLuQU|fc_08496348794f7e09016a51914ab39c8198acdef06dc171f4ac","toolName":"read","content":[{"type":"text","text":"/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n/// matching stays `Arc::ptr_eq`.\npub(crate) type SharedSend = Arc<BrokerConn>;\n\n/// Default bounded output-log depth (chunks). Large enough that a brain restart\n/// window never evicts unreplayed output in practice; the bound is what keeps a\n/// disconnected session from buffering without limit.\nconst DEFAULT_LOG_CHUNKS: usize = 4096;\n\n/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n/// owns the replay batch), so a viewer attaching to a busy session is not\n/// spuriously evicted.\nconst VIEWER_CHANNEL_DEPTH: usize = 256;\n\n/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\nconst MAX_VIEWERS: usize = 32;\n\n/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\n/// the live stream) or a `Disconnected` one (its writer thread ended after a\n/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n/// is the pure decision the fan-out applies under the log lock.\n// [impl->REQ-HAZARD-VIEWER-ISOLATION]\nfn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n    r.is_err()\n}\n\n/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n/// only its own queue — never the drain. When the queue overflows the sink is\n/// dropped (the `tx` closes → the writer ends) and removed from the map.\nstruct ViewerSink {\n    /// The bounded live-frame queue to this viewer's writer thread.\n    tx: SyncSender<Envelope>,\n    /// The viewer's IPC send half — kept ONLY for identity matching on detach\n    /// (a dropped brain connection removes its viewer rows); never written here.\n    send: SharedSend,\n    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n    /// on its `rx.recv()` Err, reads this and — when true — emits a\n    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n    /// the serving brain skips to live instead of tearing the attach down.\n    evicted: Arc<AtomicBool>,\n    /// The writer thread handle (held for ownership; it exits when `tx` drops or\n    /// a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// The controller live-frame channel depth (frames). Deeper than a viewer's\n/// queue because the controller is the AUTHORITATIVE view — unlike a viewer it is\n/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\n/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n/// never trips the deadline.\nconst CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n\n/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n/// [`ControllerSink::last_ok`]) before the drain declares it stalled and detaches it\n/// (W1, refined by b4 / REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE). This\n/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n/// untouched.\nconst CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n\n/// How long a controller's writer thread may sit BLOCKED inside a single socket\n/// write — the brain subscriber suspended / black-holed — before a take/reattach\n/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n/// protects the fan-out against ONE per-client conn falling behind and is\n/// output-driven (the drain observes a Full channel); THIS bound is measured\n/// full-with-**zero writer progress** and covers the case the output-driven path\n/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n/// output, so `append`/`mark_controller_gone` never runs, and the stalled\n/// controller would otherwise latch `driven_by` forever (reattach refused, `--take`\n/// blocked). The brain aggregates every session + psyche + WAN feed, so its drain\n/// is bursty; 15 s tolerates a normal burst-stall while still bounding the wedge.\n/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\n/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nconst BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n\n/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n/// the slot) is provable in ~1 s instead of 15 (mirrors the\n/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n/// ONLY — never relaxes an assertion.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n…\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n/// the `budget` (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Below the budget\n/// the miss is tolerated (binary preserved, worker continues).\nfn miss_strikes_exhausted(strikes: u32, budget: u32) -> bool {\n    strikes >= budget\n}\n\n/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING\n/// socket write so a backed-up controller can never stall the session drain\n/// thread (the v0.12.x wedge: the controller write was inline on the drain,\n/// holding `Mutex<OutputLog>`). Unlike a viewer it is AUTHORITATIVE: its writer\n/// advances `delivered_through` on each successful write, and it is never evicted\n/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\nstruct ControllerSink {\n    /// The bounded live-frame queue to the controller's writer thread.\n    tx: SyncSender<CtrlMsg>,\n    /// The controller's IPC send half — kept for identity matching on detach and\n    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n    send: SharedSend,\n    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n    by: Option<String>,\n    /// Time of the last SUCCESSFUL handoff (`try_send` Ok) to this controller's\n    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\n    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n    last_ok: Instant,\n    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n    /// and back to `None` the instant the write returns. A reader\n    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n    /// state; the mutex is taken only around the brief set/clear and the read, never\n    /// held across the socket write itself.\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n    /// when a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// A bounded, sequenced output log for one session — the gapless-resume buffer\n/// AND the W2.5 controller/viewer fan-out hub (REQ-RCVIEW-1).\n///\n/// The session's single drain thread is the only producer ([`OutputLog::append`]).\n/// Output fans out to ONE interactive `controller` (the authoritative,\n/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n/// an isolated bounded queue + writer thread, evicted on overflow). Every\n/// controller send and viewer `try_send` happens while the enclosing\n/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\nstruct OutputLog {\n    session_id: u64,\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    /// The single interactive controller's isolated sink (W1: a dedicated writer\n    /// thread + bounded channel + authoritative cursor). `None` ⇒ undriven. The\n    /// controller's identity (`Some(node)` remote / `None` local) lives in\n    /// [`ControllerSink::by`] and keys ownership — a same-`by` re-subscribe\n    /// silently re-takes (no `Displaced`, the gate-#7 self-kick guard); only a\n    /// DIFFERENT remote's `Take` displaces.\n    controller: Option<ControllerSink>,\n…\n}\n…\n    /// --attach` case #6 is about (attach to an already-producing TUI). A\n    /// resume-from-floor re-fetch (`from_seq > 0`, a controller recovering a b4\n    /// drop-don't-block gap) is deliberately NOT repainted: it keeps the raw-ring\n    /// exactly-once replay + the strict reject-gap + the irrecoverable-behind\n    /// detection untouched (REQ-HAZARD-CONTROLLER-GAP-RESUME / B2). `None` also when\n    /// nothing has been produced yet (`next_seq == 0`) → an empty initial batch, so\n    /// the first live frame is the contiguous seq 0 (the spawn seat, unchanged).\n    fn repaint_initial(&self, from_seq: u64) -> Option<(u64, Vec<u8>)> {\n        if from_seq != 0 || self.next_seq == 0 {\n            return None;\n        }\n        Some((self.next_seq - 1, self.grid.render_repaint()))\n    }\n\n    /// Append a freshly-produced output chunk: assign the next `seq`, retain it in\n    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n    /// log lock; `None` otherwise.\n    ///\n    /// b4 (REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE): W1 moved the\n    /// controller SOCKET WRITE off the drain (the `controller_writer` thread) but\n    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n    /// exactly like a viewer: the drain runs at full PTY speed regardless of\n    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n    /// (bounded wedge preserved). The writer still does the blocking socket write +\n    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n    /// single drain thread.\n    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n    // [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n    #[must_use]\n    /// Coherence clamp for a poison-RECOVERED ring (B-1, doyle ruling; run only by\n    /// [`recover_log`] on the recovery path). A panic mid-[`append`] can leave the\n    /// ring torn — an over-cap ring (interrupted prune), a last seq not below\n    /// `next_seq`, or a non-monotonic front/back — and serving those bytes blind\n    /// risks garbage to a subscriber. Cheap-check the ring invariants (counts within\n    /// cap, indices ordered and below `next_seq`); on ANY violation RESET the ring\n    /// EMPTY (+ loud log) — `next_seq` is left intact so cursors never rewind, and the\n    /// lost scrollback self-heals on the next PTY output + repaint. Returns `true`\n    /// iff it reset. A coherent ring (the common case — most panics fire OUTSIDE the\n    /// ring mutation, poisoning the lock while the buffer stays intact) is untouched.\n    // [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n    fn clamp_or_reset(&mut self) -> bool {\n        let within_cap = self.ring.len() <= self.cap_chunks;\n        let last_below_next = match self.ring.back() {\n            Some(&(last, _)) => last < self.next_seq,\n            None => true,\n        };\n        let ordered = match (self.ring.front(), self.ring.back()) {\n            (Some(&(front, _)), Some(&(back, _))) => front <= back,\n            _ => true,\n        };\n        if within_cap && last_below_next && ordered {\n            return false;\n        }\n        let had = self.ring.len();\n        self.ring.clear();\n        eprintln!(\n            \"broker: session {} output ring was torn by a poison-recover \\\n             (had {had} chunks, next_seq {}); reset to empty — scrollback self-heals \\\n             on the next output [REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\",\n            self.session_id, self.next_seq\n        );\n        true\n    }\n\n    /// The current high-water sequence (the next seq to be appended) — the snapshot\n    /// point for the inject leg's settle-gate + echo-verify (REQ-INJECT-MULTILINE-\n    /// INTEGRITY). A rising high-water since a baseline == the PTY produced output.\n    fn high_water(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// Concatenate ring bytes at or after `from_seq` — the output PRODUCED since a\n    /// snapshot, used to confirm a settle probe echoed (readiness, Layer 1) and that\n    /// a typed payload's head echoed back (integrity, Layer 2). Best-effort: bytes\n    /// pruned out of the bounded ring are simply absent (the echo we look for is\n    /// recent, well within cap).\n    fn bytes_since(&self, from_seq: u64) -> Vec<u8> {\n        let mut out = Vec::new();\n        for (seq, chunk) in self.ring.iter() {\n            if *seq >= from_seq {\n                out.extend_from_slice(chunk);\n            }\n        }\n        out\n    }\n\n    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        while self.ring.len() > self.cap_chunks {\n            self.ring.pop_front();\n        }\n        let frame = output_envelope(self.session_id, seq, bytes);\n        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n        if !self.viewers.is_empty() {\n            let mut evicted: Vec<u64> = Vec::new();\n            for (vid, sink) in self.viewers.iter() {\n                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n                    evicted.push(*vid);\n                }\n            }\n            if !evicted.is_empty() {\n                for vid in evicted {\n                    // Mark eviction BEFORE dropping the sink: the writer's\n                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n                    if let Some(sink) = self.viewers.remove(&vid) {\n                        sink.evicted.store(true, Ordering::Release);\n                    }\n                }\n                self.stamp_viewer_count();\n            }\n        }\n        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n        // slow controller falls behind the ring) unless it has been Full for the\n        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n        // → EVICT. The drain off-lock turns `Some(epoch)` into `mark_controller_gone`.\n        let now = Instant::now();\n        let epoch = self.controller_epoch.load(Ordering::Acquire);\n        let result = if let Some(c) = self.controller.as_mut() {\n            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n                Ok(()) => {\n                    c.last_ok = now;\n                    None\n                }\n                Err(TrySendError::Full(_)) => {\n                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n                        Some(epoch) // stalled past the deadline → bounded detach\n                    } else {\n                        None // drop this chunk; controller falls behind the ring\n                    }\n                }\n                Err(TrySendError::Disconnected(_)) => Some(epoch),\n            }\n        } else {\n            None\n        };\n        // Advance the render grid AFTER the fan-out — it must never sit between the\n        // drain and the controller/viewer `try_send` (REQ-HAZARD-INJECT-CONTROL-COEXIST\n        // 7.12). The grid is single-writer (this one drain thread); `render_repaint`\n        // snapshots it under the same short log lock only when an attach requests it.\n        self.grid.advance(bytes);\n        result\n    }\n\n    /// The current controller's identity (`Some(node)` remote / `None` local),\n    /// or `None` when undriven. The `by`-keyed ownership logic in\n    /// [`resolve_subscribe`] reads this.\n    fn controller_by(&self) -> Option<&str> {\n…\n    }\n…\n    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {\n…\n        // stall-evict can distinguish a wedged writer from an idle one.\n        let write_blocked_since = Arc::new(Mutex::new(None));\n        let writer_blocked = Arc::clone(&write_blocked_since);\n        let writer = thread::spawn(move || {\n            controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate, writer_blocked)\n        });\n        self.controller = Some(ControllerSink {\n            tx,\n            send: sub,\n            by,\n            last_ok: Instant::now(),\n            write_blocked_since,\n            _writer: writer,\n        });\n        self.stamp_driven_by();\n    }\n\n    /// Detach the controller and UNLATCH the perch — the ONE unlatch path shared\n    /// by W1's deadline-evict ([`mark_controller_gone`]), [`detach_if`], and (per\n    /// the W1↔W5 synergy) W5's reconcile self-heal. Dropping the sink ends its\n    /// writer thread; clearing the slot re-stamps `driven_by` to `None` (so an\n    /// `ONLINE+CONTROLLED` latch cannot outlive a gone controller).\n    fn clear_controller(&mut self) {\n        if self.controller.take().is_some() {\n            self.stamp_driven_by();\n        }\n    }\n\n    /// B-2 (REQ-CONTROLLER-LIVENESS-REAP): lazily drop a controller whose WRITER\n    /// THREAD has EXITED — the record-vs-truth honesty step, run on the authoritative\n    /// `KIND_SESSIONS` poll BEFORE `controller_by`/`has_controller` are read. The\n    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n    /// controller) it is NOT finished, so a live controller is never touched (the\n    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n    /// `driven_by`/`controlled` stamp — no new detach path, no new query.\n    ///\n    /// LOCK-SAFE: this runs UNDER the log lock in the snapshot closure, so it must do\n    /// NO file I/O (the lock-across-effect discipline, KH 7.12/5.16). It drops the\n    /// sink in-memory ONLY (like [`become_controller`]'s prior-drop) and does NOT\n    /// [`stamp_driven_by`] — the OFF-lock `converge_perch_stamps` writes the honest\n    /// stamp. Returns true iff it reaped.\n    ///\n    /// SCOPE (doyle B-2 reduction): catches a writer that FAILED A WRITE (output was\n    /// flowing when the conn severed) or whose detach was dropped by a prior wedge\n    /// (the B-1 poison class). A TRULY IDLE severed controller (writer parked on recv,\n    /// no output, conn not yet EOF'd) stays `is_finished() == false` and converges\n    /// only once output resumes or the conn EOFs (→ `handle_conn` detach) — that\n    /// harder active-probe case is the reserved [`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`],\n    /// deliberately NOT built here. KH 7.15 held by construction: only ever CLEARS,\n    /// never latches `driven_by`.\n    // [impl->REQ-CONTROLLER-LIVENESS-REAP]\n    fn reap_dead_controller(&mut self) -> bool {\n        let dead = self\n            .controller\n            .as_ref()\n            .is_some_and(|c| c._writer.is_finished());\n        if dead {\n            // In-memory drop only (ends the — already-exited — writer); NO stamp here\n            // (info.json I/O under the log lock is the KH 7.12 hazard). converge does it.\n            self.controller = None;\n            return true;\n        }\n        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller whose\n        // writer is BLOCKED (not exited) on a suspended brain conn past\n        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n        self.stall_evict_controller(Instant::now(), StampMode::Deferred)\n    }\n\n    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and\n    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same\n    /// controller generation the drain saw (`epoch`), so a NEWER controller that\n    /// attached during the stall is never unseated.\n    fn mark_controller_gone(&mut self, epoch: u64) {\n        if self.controller_epoch.load(Ordering::Acquire) == epoch {\n            self.clear_controller();\n        }\n    }\n\n    /// Whether the current controller's writer is WEDGED — blocked inside a single\n    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n    fn controller_write_stalled(&self, now: Instant, deadline: Duration) -> bool {\n        self.controller.as_ref().is_some_and(|c| {\n            c.write_blocked_since\n                .lock()\n                .ok()\n                .and_then(|g| *g)\n                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n        })\n    }\n\n    /// Whether this session's controller is the brain's OWN LOCAL conn (`by: None`)\n    /// AND wedged past `deadline` — the brain-generation promotion DRAINED gate reads\n    /// it (REQ-UPDATE-PROMOTE-DRAINED). A killed OLD brain generation whose local\n    /// controller conn is black-holed reads wedged here until the W2 stall-evict\n    /// releases it (or the conn EOFs); a cleanly-closed conn is simply absent. A\n    /// REMOTE operator's wedged conn (`by: Some`) is the W2 stall-evict concern, NOT\n    /// an old-gen brain conn, so it is EXCLUDED — a stuck remote viewer must never\n    /// gate a brain promotion. Pure over the injected `now`/`deadline` (no thread).\n    // [impl->REQ-UPDATE-PROMOTE-DRAINED]\n    fn local_controller_wedged(&self, now: Instant, deadline: Duration) -> bool {\n        self.controller.as_ref().is_some_and(|c| c.by.is_none())\n            && self.controller_write_stalled(now, deadline)\n    }\n…\n    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {\n…\n        match self.repaint_initial(from_seq) {\n…\n        }\n        let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n        let writer_send = Arc::clone(&sub);\n        let evicted = Arc::new(AtomicBool::new(false));\n        let writer_evicted = Arc::clone(&evicted);\n        let session_id = self.session_id;\n        let writer =\n            thread::spawn(move || viewer_writer(writer_send, session_id, initial, rx, writer_evicted));\n        self.viewers.insert(\n            vid,\n            ViewerSink {\n                tx,\n                send: sub,\n                evicted,\n                _writer: writer,\n            },\n        );\n    }\n\n    /// Resolve a controller/viewer subscribe under the W2.5 model\n    /// (REQ-RCVIEW-1 / REQ-KICK-1). Returns the [`SubscribeOutcome`]; performs\n    /// the controller take / viewer add / loud displace as a side effect. See the\n    /// identity matrix in the body — the `by`-keyed logic is what makes a\n    /// brain-restart re-serve silent (gate #7) and a cross-operator `Take` loud.\n    fn resolve_subscribe(\n        &mut self,\n        sub: SharedSend,\n        from_seq: u64,\n        intent: AttachIntent,\n        by: Option<String>,\n    ) -> SubscribeOutcome {\n        if intent == AttachIntent::Viewer {\n            if self.viewers.len() >= MAX_VIEWERS {\n                // Soft cap reached — refuse the viewer (bounds thread count).\n                return SubscribeOutcome::BusyControlled {\n                    by: \"viewer cap reached\".to_string(),\n                };\n            }\n            self.add_viewer(sub, from_seq);\n            self.stamp_viewer_count();\n            return SubscribeOutcome::Viewer;\n        }\n        // Control or Take.\n        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n        // stall-evicted HERE — the take/reattach self-heals against broker truth\n        // without waiting for new output (the output-driven mark_controller_gone\n        // never fires on an idle session). Inline stamp: this path already writes the\n        // perch under the log lock via become_controller. After the evict the slot\n        // reads free and the normal free/take logic below runs.\n        self.stall_evict_controller(Instant::now(), StampMode::Inline);\n        if self.controller.is_none() {\n            // Free endpoint → become controller (nothing to displace).\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        let same_identity = self.controller_by() == by.as_deref();\n        if same_identity {\n            // Same operator (or both local) re-serving — silent re-take. This is\n            // the gate-#7 path: a successor re-taking the slot after a brain\n            // restart NEVER fires Displaced.\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        if by.is_none() {\n            // The LOCAL brain re-attaching while a REMOTE controls — never steal;\n            // fall back to a silent viewer (gate #7 for the local-resume path).\n            self.add_viewer(sub, from_seq);\n            self.stamp_viewer_count();\n            return SubscribeOutcome::Viewer;\n        }\n        if self.controller_by().is_none() {\n            // A remote takes an UNDRIVEN session (only the local spawner held the\n            // slot) — silent control, no loud displace (there is no remote driver\n            // to notify).\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        // A DIFFERENT remote controls it.\n        match intent {\n            AttachIntent::Take => {\n                // The ONLY loud-displace path: notify the incumbent, then take.\n                // W1: route the Displaced notice through the OLD controller's\n                // writer thread (best-effort, off the dispatch thread) — the\n                // incumbent we're kicking may be exactly the wedged one, so an\n                // inline write here would block the new attacher's dispatch.\n                if let Some(old) = &self.controller {\n                    let frame = displaced_envelope(self.session_id, by.as_deref().unwrap_or(\"\"));\n                    let _ = old.tx.try_send(CtrlMsg::Control(frame));\n                }\n                self.become_controller(sub, by, from_seq);\n                SubscribeOutcome::TookControl\n            }\n            _ => SubscribeOutcome::BusyControlled {\n                by: self.controller_by().unwrap_or_default().to_string(),\n            },\n        }\n    }\n\n    /// Update the stored PTY size and push a `size` frame to every viewer (the\n    /// letterbox feed). Called on a controller resize. Viewer pushes are\n    /// non-blocking with eviction, like [`OutputLog::append`].\n    fn set_size_and_notify(&mut self, rows: u16, cols: u16) {\n        self.size = (rows, cols);\n        // Keep the render grid geometry in step so a post-resize repaint reflects\n        // the new viewport (#8 resize-artifact fix, ADR-0031).\n        self.grid.resize(rows, cols);\n        if self.viewers.is_empty() {\n            return;\n        }\n        let frame = size_envelope(self.session_id, rows, cols);\n…\n    }"}],"details":{"displayContent":{"text":"/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n/// matching stays `Arc::ptr_eq`.\npub(crate) type SharedSend = Arc<BrokerConn>;\n\n/// Default bounded output-log depth (chunks). Large enough that a brain restart\n/// window never evicts unreplayed output in practice; the bound is what keeps a\n/// disconnected session from buffering without limit.\nconst DEFAULT_LOG_CHUNKS: usize = 4096;\n\n/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n/// owns the replay batch), so a viewer attaching to a busy session is not\n/// spuriously evicted.\nconst VIEWER_CHANNEL_DEPTH: usize = 256;\n\n/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\nconst MAX_VIEWERS: usize = 32;\n\n/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\n/// the live stream) or a `Disconnected` one (its writer thread ended after a\n/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n/// is the pure decision the fan-out applies under the log lock.\n// [impl->REQ-HAZARD-VIEWER-ISOLATION]\nfn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n    r.is_err()\n}\n\n/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n/// only its own queue — never the drain. When the queue overflows the sink is\n/// dropped (the `tx` closes → the writer ends) and removed from the map.\nstruct ViewerSink {\n    /// The bounded live-frame queue to this viewer's writer thread.\n    tx: SyncSender<Envelope>,\n    /// The viewer's IPC send half — kept ONLY for identity matching on detach\n    /// (a dropped brain connection removes its viewer rows); never written here.\n    send: SharedSend,\n    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n    /// on its `rx.recv()` Err, reads this and — when true — emits a\n    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n    /// the serving brain skips to live instead of tearing the attach down.\n    evicted: Arc<AtomicBool>,\n    /// The writer thread handle (held for ownership; it exits when `tx` drops or\n    /// a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// The controller live-frame channel depth (frames). Deeper than a viewer's\n/// queue because the controller is the AUTHORITATIVE view — unlike a viewer it is\n/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\n/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n/// never trips the deadline.\nconst CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n\n/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n/// [`ControllerSink::last_ok`]) before the drain declares it stalled and detaches it\n/// (W1, refined by b4 / REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE). This\n/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n/// untouched.\nconst CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n\n/// How long a controller's writer thread may sit BLOCKED inside a single socket\n/// write — the brain subscriber suspended / black-holed — before a take/reattach\n/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n/// protects the fan-out against ONE per-client conn falling behind and is\n/// output-driven (the drain observes a Full channel); THIS bound is measured\n/// full-with-**zero writer progress** and covers the case the output-driven path\n/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n/// output, so `append`/`mark_controller_gone` never runs, and the stalled\n/// controller would otherwise latch `driven_by` forever (reattach refused, `--take`\n/// blocked). The brain aggregates every session + psyche + WAN feed, so its drain\n/// is bursty; 15 s tolerates a normal burst-stall while still bounding the wedge.\n/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\n/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nconst BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n\n/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n/// the slot) is provable in ~1 s instead of 15 (mirrors the\n/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n/// ONLY — never relaxes an assertion.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n…\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n/// the `budget` (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Below the budget\n/// the miss is tolerated (binary preserved, worker continues).\nfn miss_strikes_exhausted(strikes: u32, budget: u32) -> bool {\n    strikes >= budget\n}\n\n/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING\n/// socket write so a backed-up controller can never stall the session drain\n/// thread (the v0.12.x wedge: the controller write was inline on the drain,\n/// holding `Mutex<OutputLog>`). Unlike a viewer it is AUTHORITATIVE: its writer\n/// advances `delivered_through` on each successful write, and it is never evicted\n/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\nstruct ControllerSink {\n    /// The bounded live-frame queue to the controller's writer thread.\n    tx: SyncSender<CtrlMsg>,\n    /// The controller's IPC send half — kept for identity matching on detach and\n    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n    send: SharedSend,\n    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n    by: Option<String>,\n    /// Time of the last SUCCESSFUL handoff (`try_send` Ok) to this controller's\n    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\n    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n    last_ok: Instant,\n    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n    /// and back to `None` the instant the write returns. A reader\n    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n    /// state; the mutex is taken only around the brief set/clear and the read, never\n    /// held across the socket write itself.\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n    /// when a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// A bounded, sequenced output log for one session — the gapless-resume buffer\n/// AND the W2.5 controller/viewer fan-out hub (REQ-RCVIEW-1).\n///\n/// The session's single drain thread is the only producer ([`OutputLog::append`]).\n/// Output fans out to ONE interactive `controller` (the authoritative,\n/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n/// an isolated bounded queue + writer thread, evicted on overflow). Every\n/// controller send and viewer `try_send` happens while the enclosing\n/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\nstruct OutputLog {\n    session_id: u64,\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    /// The single interactive controller's isolated sink (W1: a dedicated writer\n    /// thread + bounded channel + authoritative cursor). `None` ⇒ undriven. The\n    /// controller's identity (`Some(node)` remote / `None` local) lives in\n    /// [`ControllerSink::by`] and keys ownership — a same-`by` re-subscribe\n    /// silently re-takes (no `Displaced`, the gate-#7 self-kick guard); only a\n    /// DIFFERENT remote's `Take` displaces.\n    controller: Option<ControllerSink>,\n…\n}\n…\n    /// --attach` case #6 is about (attach to an already-producing TUI). A\n    /// resume-from-floor re-fetch (`from_seq > 0`, a controller recovering a b4\n    /// drop-don't-block gap) is deliberately NOT repainted: it keeps the raw-ring\n    /// exactly-once replay + the strict reject-gap + the irrecoverable-behind\n    /// detection untouched (REQ-HAZARD-CONTROLLER-GAP-RESUME / B2). `None` also when\n    /// nothing has been produced yet (`next_seq == 0`) → an empty initial batch, so\n    /// the first live frame is the contiguous seq 0 (the spawn seat, unchanged).\n    fn repaint_initial(&self, from_seq: u64) -> Option<(u64, Vec<u8>)> {\n        if from_seq != 0 || self.next_seq == 0 {\n            return None;\n        }\n        Some((self.next_seq - 1, self.grid.render_repaint()))\n    }\n\n    /// Append a freshly-produced output chunk: assign the next `seq`, retain it in\n    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n    /// log lock; `None` otherwise.\n    ///\n    /// b4 (REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE): W1 moved the\n    /// controller SOCKET WRITE off the drain (the `controller_writer` thread) but\n    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n    /// exactly like a viewer: the drain runs at full PTY speed regardless of\n    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n    /// (bounded wedge preserved). The writer still does the blocking socket write +\n    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n    /// single drain thread.\n    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n    // [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n    #[must_use]\n    /// Coherence clamp for a poison-RECOVERED ring (B-1, doyle ruling; run only by\n    /// [`recover_log`] on the recovery path). A panic mid-[`append`] can leave the\n    /// ring torn — an over-cap ring (interrupted prune), a last seq not below\n    /// `next_seq`, or a non-monotonic front/back — and serving those bytes blind\n    /// risks garbage to a subscriber. Cheap-check the ring invariants (counts within\n    /// cap, indices ordered and below `next_seq`); on ANY violation RESET the ring\n    /// EMPTY (+ loud log) — `next_seq` is left intact so cursors never rewind, and the\n    /// lost scrollback self-heals on the next PTY output + repaint. Returns `true`\n    /// iff it reset. A coherent ring (the common case — most panics fire OUTSIDE the\n    /// ring mutation, poisoning the lock while the buffer stays intact) is untouched.\n    // [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n    fn clamp_or_reset(&mut self) -> bool {\n        let within_cap = self.ring.len() <= self.cap_chunks;\n        let last_below_next = match self.ring.back() {\n            Some(&(last, _)) => last < self.next_seq,\n            None => true,\n        };\n        let ordered = match (self.ring.front(), self.ring.back()) {\n            (Some(&(front, _)), Some(&(back, _))) => front <= back,\n            _ => true,\n        };\n        if within_cap && last_below_next && ordered {\n            return false;\n        }\n        let had = self.ring.len();\n        self.ring.clear();\n        eprintln!(\n            \"broker: session {} output ring was torn by a poison-recover \\\n             (had {had} chunks, next_seq {}); reset to empty — scrollback self-heals \\\n             on the next output [REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\",\n            self.session_id, self.next_seq\n        );\n        true\n    }\n\n    /// The current high-water sequence (the next seq to be appended) — the snapshot\n    /// point for the inject leg's settle-gate + echo-verify (REQ-INJECT-MULTILINE-\n    /// INTEGRITY). A rising high-water since a baseline == the PTY produced output.\n    fn high_water(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// Concatenate ring bytes at or after `from_seq` — the output PRODUCED since a\n    /// snapshot, used to confirm a settle probe echoed (readiness, Layer 1) and that\n    /// a typed payload's head echoed back (integrity, Layer 2). Best-effort: bytes\n    /// pruned out of the bounded ring are simply absent (the echo we look for is\n    /// recent, well within cap).\n    fn bytes_since(&self, from_seq: u64) -> Vec<u8> {\n        let mut out = Vec::new();\n        for (seq, chunk) in self.ring.iter() {\n            if *seq >= from_seq {\n                out.extend_from_slice(chunk);\n            }\n        }\n        out\n    }\n\n    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        while self.ring.len() > self.cap_chunks {\n            self.ring.pop_front();\n        }\n        let frame = output_envelope(self.session_id, seq, bytes);\n        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n        if !self.viewers.is_empty() {\n            let mut evicted: Vec<u64> = Vec::new();\n            for (vid, sink) in self.viewers.iter() {\n                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n                    evicted.push(*vid);\n                }\n            }\n            if !evicted.is_empty() {\n                for vid in evicted {\n                    // Mark eviction BEFORE dropping the sink: the writer's\n                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n                    if let Some(sink) = self.viewers.remove(&vid) {\n                        sink.evicted.store(true, Ordering::Release);\n                    }\n                }\n                self.stamp_viewer_count();\n            }\n        }\n        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n        // slow controller falls behind the ring) unless it has been Full for the\n        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n        // → EVICT. The drain off-lock turns `Some(epoch)` into `mark_controller_gone`.\n        let now = Instant::now();\n        let epoch = self.controller_epoch.load(Ordering::Acquire);\n        let result = if let Some(c) = self.controller.as_mut() {\n            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n                Ok(()) => {\n                    c.last_ok = now;\n                    None\n                }\n                Err(TrySendError::Full(_)) => {\n                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n                        Some(epoch) // stalled past the deadline → bounded detach\n                    } else {\n                        None // drop this chunk; controller falls behind the ring\n                    }\n                }\n                Err(TrySendError::Disconnected(_)) => Some(epoch),\n            }\n        } else {\n            None\n        };\n        // Advance the render grid AFTER the fan-out — it must never sit between the\n        // drain and the controller/viewer `try_send` (REQ-HAZARD-INJECT-CONTROL-COEXIST\n        // 7.12). The grid is single-writer (this one drain thread); `render_repaint`\n        // snapshots it under the same short log lock only when an attach requests it.\n        self.grid.advance(bytes);\n        result\n    }\n\n    /// The current controller's identity (`Some(node)` remote / `None` local),\n    /// or `None` when undriven. The `by`-keyed ownership logic in\n    /// [`resolve_subscribe`] reads this.\n    fn controller_by(&self) -> Option<&str> {\n…\n    }\n…\n    fn become_controller(&mut self, sub: SharedSend, by: Option<String>, from_seq: u64) {\n…\n        // stall-evict can distinguish a wedged writer from an idle one.\n        let write_blocked_since = Arc::new(Mutex::new(None));\n        let writer_blocked = Arc::clone(&write_blocked_since);\n        let writer = thread::spawn(move || {\n            controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate, writer_blocked)\n        });\n        self.controller = Some(ControllerSink {\n            tx,\n            send: sub,\n            by,\n            last_ok: Instant::now(),\n            write_blocked_since,\n            _writer: writer,\n        });\n        self.stamp_driven_by();\n    }\n\n    /// Detach the controller and UNLATCH the perch — the ONE unlatch path shared\n    /// by W1's deadline-evict ([`mark_controller_gone`]), [`detach_if`], and (per\n    /// the W1↔W5 synergy) W5's reconcile self-heal. Dropping the sink ends its\n    /// writer thread; clearing the slot re-stamps `driven_by` to `None` (so an\n    /// `ONLINE+CONTROLLED` latch cannot outlive a gone controller).\n    fn clear_controller(&mut self) {\n        if self.controller.take().is_some() {\n            self.stamp_driven_by();\n        }\n    }\n\n    /// B-2 (REQ-CONTROLLER-LIVENESS-REAP): lazily drop a controller whose WRITER\n    /// THREAD has EXITED — the record-vs-truth honesty step, run on the authoritative\n    /// `KIND_SESSIONS` poll BEFORE `controller_by`/`has_controller` are read. The\n    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n    /// controller) it is NOT finished, so a live controller is never touched (the\n    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n    /// `driven_by`/`controlled` stamp — no new detach path, no new query.\n    ///\n    /// LOCK-SAFE: this runs UNDER the log lock in the snapshot closure, so it must do\n    /// NO file I/O (the lock-across-effect discipline, KH 7.12/5.16). It drops the\n    /// sink in-memory ONLY (like [`become_controller`]'s prior-drop) and does NOT\n    /// [`stamp_driven_by`] — the OFF-lock `converge_perch_stamps` writes the honest\n    /// stamp. Returns true iff it reaped.\n    ///\n    /// SCOPE (doyle B-2 reduction): catches a writer that FAILED A WRITE (output was\n    /// flowing when the conn severed) or whose detach was dropped by a prior wedge\n    /// (the B-1 poison class). A TRULY IDLE severed controller (writer parked on recv,\n    /// no output, conn not yet EOF'd) stays `is_finished() == false` and converges\n    /// only once output resumes or the conn EOFs (→ `handle_conn` detach) — that\n    /// harder active-probe case is the reserved [`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`],\n    /// deliberately NOT built here. KH 7.15 held by construction: only ever CLEARS,\n    /// never latches `driven_by`.\n    // [impl->REQ-CONTROLLER-LIVENESS-REAP]\n    fn reap_dead_controller(&mut self) -> bool {\n        let dead = self\n            .controller\n            .as_ref()\n            .is_some_and(|c| c._writer.is_finished());\n        if dead {\n            // In-memory drop only (ends the — already-exited — writer); NO stamp here\n            // (info.json I/O under the log lock is the KH 7.12 hazard). converge does it.\n            self.controller = None;\n            return true;\n        }\n        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller whose\n        // writer is BLOCKED (not exited) on a suspended brain conn past\n        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n        self.stall_evict_controller(Instant::now(), StampMode::Deferred)\n    }\n\n    /// Deadline-evict path (W1): the drain observed a fully-stalled controller and\n    /// timed out its `send_timeout`. Detach it — but ONLY if it is still the same\n    /// controller generation the drain saw (`epoch`), so a NEWER controller that\n    /// attached during the stall is never unseated.\n    fn mark_controller_gone(&mut self, epoch: u64) {\n        if self.controller_epoch.load(Ordering::Acquire) == epoch {\n            self.clear_controller();\n        }\n    }\n\n    /// Whether the current controller's writer is WEDGED — blocked inside a single\n    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n    fn controller_write_stalled(&self, now: Instant, deadline: Duration) -> bool {\n        self.controller.as_ref().is_some_and(|c| {\n            c.write_blocked_since\n                .lock()\n                .ok()\n                .and_then(|g| *g)\n                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n        })\n    }\n\n    /// Whether this session's controller is the brain's OWN LOCAL conn (`by: None`)\n    /// AND wedged past `deadline` — the brain-generation promotion DRAINED gate reads\n    /// it (REQ-UPDATE-PROMOTE-DRAINED). A killed OLD brain generation whose local\n    /// controller conn is black-holed reads wedged here until the W2 stall-evict\n    /// releases it (or the conn EOFs); a cleanly-closed conn is simply absent. A\n    /// REMOTE operator's wedged conn (`by: Some`) is the W2 stall-evict concern, NOT\n    /// an old-gen brain conn, so it is EXCLUDED — a stuck remote viewer must never\n    /// gate a brain promotion. Pure over the injected `now`/`deadline` (no thread).\n    // [impl->REQ-UPDATE-PROMOTE-DRAINED]\n    fn local_controller_wedged(&self, now: Instant, deadline: Duration) -> bool {\n        self.controller.as_ref().is_some_and(|c| c.by.is_none())\n            && self.controller_write_stalled(now, deadline)\n    }\n…\n    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {\n…\n        match self.repaint_initial(from_seq) {\n…\n        }\n        let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n        let writer_send = Arc::clone(&sub);\n        let evicted = Arc::new(AtomicBool::new(false));\n        let writer_evicted = Arc::clone(&evicted);\n        let session_id = self.session_id;\n        let writer =\n            thread::spawn(move || viewer_writer(writer_send, session_id, initial, rx, writer_evicted));\n        self.viewers.insert(\n            vid,\n            ViewerSink {\n                tx,\n                send: sub,\n                evicted,\n                _writer: writer,\n            },\n        );\n    }\n\n    /// Resolve a controller/viewer subscribe under the W2.5 model\n    /// (REQ-RCVIEW-1 / REQ-KICK-1). Returns the [`SubscribeOutcome`]; performs\n    /// the controller take / viewer add / loud displace as a side effect. See the\n    /// identity matrix in the body — the `by`-keyed logic is what makes a\n    /// brain-restart re-serve silent (gate #7) and a cross-operator `Take` loud.\n    fn resolve_subscribe(\n        &mut self,\n        sub: SharedSend,\n        from_seq: u64,\n        intent: AttachIntent,\n        by: Option<String>,\n    ) -> SubscribeOutcome {\n        if intent == AttachIntent::Viewer {\n            if self.viewers.len() >= MAX_VIEWERS {\n                // Soft cap reached — refuse the viewer (bounds thread count).\n                return SubscribeOutcome::BusyControlled {\n                    by: \"viewer cap reached\".to_string(),\n                };\n            }\n            self.add_viewer(sub, from_seq);\n            self.stamp_viewer_count();\n            return SubscribeOutcome::Viewer;\n        }\n        // Control or Take.\n        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n        // stall-evicted HERE — the take/reattach self-heals against broker truth\n        // without waiting for new output (the output-driven mark_controller_gone\n        // never fires on an idle session). Inline stamp: this path already writes the\n        // perch under the log lock via become_controller. After the evict the slot\n        // reads free and the normal free/take logic below runs.\n        self.stall_evict_controller(Instant::now(), StampMode::Inline);\n        if self.controller.is_none() {\n            // Free endpoint → become controller (nothing to displace).\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        let same_identity = self.controller_by() == by.as_deref();\n        if same_identity {\n            // Same operator (or both local) re-serving — silent re-take. This is\n            // the gate-#7 path: a successor re-taking the slot after a brain\n            // restart NEVER fires Displaced.\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        if by.is_none() {\n            // The LOCAL brain re-attaching while a REMOTE controls — never steal;\n            // fall back to a silent viewer (gate #7 for the local-resume path).\n            self.add_viewer(sub, from_seq);\n            self.stamp_viewer_count();\n            return SubscribeOutcome::Viewer;\n        }\n        if self.controller_by().is_none() {\n            // A remote takes an UNDRIVEN session (only the local spawner held the\n            // slot) — silent control, no loud displace (there is no remote driver\n            // to notify).\n            self.become_controller(sub, by, from_seq);\n            return SubscribeOutcome::Controller;\n        }\n        // A DIFFERENT remote controls it.\n        match intent {\n            AttachIntent::Take => {\n                // The ONLY loud-displace path: notify the incumbent, then take.\n                // W1: route the Displaced notice through the OLD controller's\n                // writer thread (best-effort, off the dispatch thread) — the\n                // incumbent we're kicking may be exactly the wedged one, so an\n                // inline write here would block the new attacher's dispatch.\n                if let Some(old) = &self.controller {\n                    let frame = displaced_envelope(self.session_id, by.as_deref().unwrap_or(\"\"));\n                    let _ = old.tx.try_send(CtrlMsg::Control(frame));\n                }\n                self.become_controller(sub, by, from_seq);\n                SubscribeOutcome::TookControl\n            }\n            _ => SubscribeOutcome::BusyControlled {\n                by: self.controller_by().unwrap_or_default().to_string(),\n            },\n        }\n    }\n\n    /// Update the stored PTY size and push a `size` frame to every viewer (the\n    /// letterbox feed). Called on a controller resize. Viewer pushes are\n    /// non-blocking with eviction, like [`OutputLog::append`].\n    fn set_size_and_notify(&mut self, rows: u16, cols: u16) {\n        self.size = (rows, cols);\n        // Keep the render grid geometry in step so a post-resize repaint reflects\n        // the new viewport (#8 resize-artifact fix, ADR-0031).\n        self.grid.resize(rows, cols);\n        if self.viewers.is_empty() {\n            return;\n        }\n        let frame = size_envelope(self.session_id, rows, cols);\n…\n    }","startLine":80,"lineNumbers":[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,null,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,null,601,null,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,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,null,862,null,891,null,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,null,1073,null,1082,null,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,null,1218]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"}}},"isError":false,"timestamp":1783730506261}}
{"type":"message","id":"39b2f9d0","parentId":"7a908a13","timestamp":"2026-07-11T00:41:46.266Z","message":{"role":"toolResult","toolCallId":"call_u2o6ayKPaewmOw4BfvhuKQSf|fc_08496348794f7e09016a51914ab3908198af29f08b09cca770","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/spt-daemon/\n\n## src/\n### attach.rs\n 39|//! A restarted target brain re-attaches the session from its output cursor,\n*40|//! resubscribes the stream from its net cursor, and re-drives journaled sends;\n 41|//! a restarted operator brain resubscribes and dedups rendered chunks by\n 42|//! `Output.seq`.\n 43|\n### brain.rs\n 509|    /// the same logical effect must always carry the same id, so the broker's\n*510|    /// applied-set recognizes the re-drive. The broker answers with a\n 511|    /// [`BrokerEvent::Applied`] for `(session, op_id)`.\n 512|    ///\n 513|    /// ACKED (`ack=true`): the caller waits on the [`BrokerEvent::Applied`] one at a\n### broker.rs\n 353|// The typed idle-inject leg ate HEAD bytes nondeterministically: bytes typed into a\n*354|// PTY whose input reader is not yet attached/settled are swallowed mid-stream (the\n*355|// pre-settle race — 1854B once landed intact, ~1150B usually swallowed = a timing\n 356|// class, not a size cap). A live-SENT inject leaves NO spool copy, so a swallow is\n*357|// silent AND unrecoverable. The fix is three layers: (1) MUST settle-gate before ANY\n*358|// byte (positive readiness probe, not a blind timer); (2) MUST echo-verify the head\n*359|// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n 360|// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n 361|// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n 362|\n*363|/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n 364|/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n 365|/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n 366|/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n*367|/// inject after startup, the pre-settle window; re-drives settle again). An echoing PTY\n 368|/// returns in a tick or two, well under this.\n*369|const INJECT_SETTLE_DEADLINE: Duration = Duration::from_millis(400);\n*370|/// Poll/probe cadence within the settle-gate and the echo-verify wait.\n*371|const INJECT_SETTLE_POLL: Duration = Duration::from_millis(40);\n 372|/// The benign readiness probe: a DSR cursor-position query (`ESC[6n`). Invisible to\n 373|/// a TUI input line and never a submit; a live terminal/child answers or re-renders,\n 374|/// producing output — the POSITIVE signal that the input reader is attached.\n*375|const INJECT_SETTLE_PROBE: &[u8] = b\"\\x1b[6n\";\n 376|\n*377|/// The effective settle-gate deadline; the int gate tunes it via\n*378|/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n*379|fn inject_settle_deadline() -> Duration {\n*380|    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n 381|        Ok(ms) => ms\n 382|            .parse::<u64>()\n 383|            .map(Duration::from_millis)\n*384|            .unwrap_or(INJECT_SETTLE_DEADLINE),\n*385|        Err(_) => INJECT_SETTLE_DEADLINE,\n 386|    }\n 387|}\n 388|\n*389|/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n 390|/// the typed payload's head to echo back on the session ring before declaring a\n 391|/// verify MISS (round-trip + XON-resume latency bound).\n*392|const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n 393|\n*394|/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n### daemon.rs\n 1261|    // [unit->REQ-DAEMON-9] the net self-heal backoff ladder doubles each retry\n*1262|    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n 1263|    // 30s poll, never an ever-growing or zero-length spin.\n 1264|    #[test]\n 1265|    fn net_retry_backoff_doubles_then_caps() {\n### effect.rs\n 17|//! ([`EffectJournal::apply_once`] Err arm): not marking `applied` is what lets a\n*18|//! re-drive re-attempt it (no drop), and the failure is surfaced to the caller\n 19|//! (the broker routes the `apply_once` Err to `send_error` → a `KIND_ERROR`\n 20|//! frame) — never silent. So a `PENDING` with no matching `DONE` is the EXPECTED\n 21|//! shape of an effect that did not complete: for a `net-send`, an **unconfirmed\n*22|//! write to an offline/unreachable peer**, retained as the re-drive recovery\n*23|//! marker (it re-drives on the next brain restart / reachability). Two reader\n*24|//! caveats: the log is **append-only**, so a failed-then-re-driven effect keeps\n 25|//! its stale `PENDING` line AND appends a fresh `PENDING`+`DONE` — a raw\n 26|//! line-count OVER-reads the live pending set; [`recover`] is authoritative and\n 27|//! dedups **by key** (any `DONE` for a key marks it applied). And `PENDING`/`DONE`\n...\n 57|//! therefore hits an already-populated applied-set and is deduped (no dup); the\n*58|//! re-drive itself is what re-attempts an un-applied effect (no drop).\n 59|//!\n 60|//! The applied-set is additionally journaled to disk (`PENDING`/`DONE` lines,\n 61|//! fsync'd) so it is rebuilt on a broker *restart* — a forward-looking durability\n...\n 293|/// produced the op, and the source's durable op id (a PTY-write sequence, a\n*294|/// spool-row id, …). The dedup key — the same logical effect re-driven after a\n 295|/// crash carries the *same* key, so the applied-set recognizes the replay. The\n 296|/// `minter` dimension (ADR-0034) stops two counters that both start at 1 from\n 297|/// colliding in the one journal namespace.\n...\n 377|    /// W1b wedge fix), and the `applied` set is the authoritative dedup anchor, so a\n*378|    /// re-driven effect either dedups or re-attempts — no double-apply, no lost op.\n 379|    // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\n 380|    fn lock_recover(&self) -> std::sync::MutexGuard<'_, Inner> {\n 381|        self.inner\n...\n 389|    /// `key` joins the applied-set ([`Outcome::Applied`]). On closure error the key\n*390|    /// is left un-applied (so a re-drive re-attempts it) and the error propagated.\n 391|    ///\n 392|    /// **W1b (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE):** the inner lock is RELEASED\n 393|    /// across `effect()`. Pre-W1b the lock was held across both fsyncs AND the\n...\n 409|    /// the reserve→effect→finalize sequence runs to completion on the broker thread\n*410|    /// uninterrupted; a re-driven (replayed) effect hits the applied-set and dedups.\n 411|    /// Same-key concurrency does not occur in the model (a crashed brain is gone\n*412|    /// before its successor re-drives the same durable id), so the released window\n 413|    /// is only ever crossed by *different* keys — which is the entire point.\n 414|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n 415|    // [impl->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]\n...\n 459|            // The effect did not complete: leave the (durable) PENDING marker for\n*460|            // recovery surfacing and do NOT mark applied, so a re-drive re-attempts\n 461|            // it (no drop) — the pre-W1b error semantics.\n 462|            Err(e) => Err(e),\n 463|        }\n...\n 668|    // [unit->REQ-HAZARD-RESTART-IDEMPOTENT] a closure error leaves the key\n*669|    // un-applied (no false DONE) so a later re-drive re-attempts it — no drop.\n 670|    #[test]\n 671|    fn failed_effect_is_not_recorded_applied() {\n 672|        let dir = tempfile::tempdir().unwrap();\n...\n 682|\n*683|        // Re-drive succeeds and applies exactly once.\n 684|        let ok = j.apply_once(key, EffectKind::Spool, || Ok(())).unwrap();\n 685|        assert_eq!(ok, Outcome::Applied);\n 686|        assert!(j.is_applied(key));\n...\n 778|\n*779|    /// One brain life: re-drive the durable source `0..total` through the\n 780|    /// persistent journal, appending each applied op to `effects` (the real,\n 781|    /// durable side-effect target). `crash` injects a window on one op.\n 782|    fn run_life(\n...\n 798|                .expect(\"apply_once\");\n*799|            // A re-drive of an already-applied op must not re-push.\n 800|            debug_assert!(outcome == Outcome::Applied || outcome == Outcome::Deduped);\n 801|            if crash == Some((op, Window::AfterApplied)) {\n 802|                return; // effect landed; the source crashed before marking it done\n...\n 963|            Some((5, Window::AfterApplied)), // effect 5 landed, ack lost\n*964|            Some((5, Window::BeforeIntent)), // re-drive crashes again before 5...\n 965|            Some((8, Window::AfterApplied)),\n 966|            Some((11, Window::BeforeIntent)),\n 967|            None, // a clean life finishes the tail\n### lib.rs\n 70|//! discipline) a brain subscribes to with a resume cursor; sends/opens carry\n*71|//! durable `op_id`s journaled at the broker, so a restarted brain's re-drive\n 72|//! dedups instead of double-writing the wire.\n 73|//!\n 74|//! **D4a (this commit)** moves the node's network presence into the broker\n### linkhost.rs\n 577|                // wire (the remote operator has no TTY here); the operator\n*578|                // approves on the shell's node, then re-drives.\n 579|                Err(ShellLinkError::NeedsApproval(ask, _)) => {\n 580|                    let d = format!(\n*581|                        \"op needs approval on the shell's node ({}); approve there, then re-drive\",\n 582|                        ask.capability\n 583|                    );\n 584|                    (\n### livehost.rs\n 1706|            // W0.4 mutate_info sentinel lock makes status reliably stay offline, so\n*1707|            // un-host reliably fires; the earlier long-pulse+settle workaround is\n 1708|            // removed (the lock, not timing, is what makes this deterministic).\n 1709|            let cfg = fast_cfg();\n 1710|            let run = |reason| {\n### msg.rs\n 864|    /// wake); `false` = IDLE / no hosted session → spool NON-deferred (the live\n*865|    /// relay/poll surfaces it = the spt-core-driven wake). Additive + serde-defaulted\n 866|    /// (`false`): an N-1 broker omits it and the caller spools non-deferred exactly as\n 867|    /// in v0.14.3. Meaningless (ignored) when `delivered == true`.\n 868|    #[serde(default)]\n### propagate.rs\n 380|        let ev = brain.read_peer_reply_until(deadline)?;\n*381|        // Re-arm the budget on each chunk on OUR stream: a whole reply-read budget\n 382|        // of NO progress is a wedge, a streaming pull is healthy (REQ-HAZARD-PUMP-IPC-DEADLINE).\n 383|        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n 384|            deadline = brain.reply_read_deadline();\n### resting.rs\n 1144|\n*1145|            // wake: no gate re-arm; the freshness-pull marker drops instead.\n 1146|            std::fs::remove_file(&gate).unwrap();\n 1147|            let report = daemon_rest_event(\"ling\", RestEvent::Wake, None)\n 1148|                .expect(\"apply ok\")\n### serveprobe.rs\n 6|//!\n*7|//! `subnet status --nodes` settles liveness in two tiers: gossip-recency\n*8|//! first, a live probe for the rows gossip could not settle. The probe used to\n 9|//! be a raw transport dial by node-pubkey — it proved the peer's daemon\n 10|//! endpoint was bound (the box is up), but ONE ALPN serves every subnet, so it\n 11|//! could not tell \"still serving THIS subnet\" from \"up for its own home\". A\n### shellhost.rs\n 119|    // the binary rises — and any (re)launch is \"shell activity\", so the\n*120|    // crash give-up latch re-arms. [impl->REQ-SHELL-2]\n 121|    crate::shellwake::kill_waker_at(&perch);\n 122|    crate::shellwake::clear_gave_up(&perch);\n 123|    let token = mint_link_token();\n### shellwake.rs\n 675|    // (with backoff) exactly give_up_after times, then the durable give-up\n*676|    // latch drops and the resolution never fires; clear_gave_up re-arms.\n 677|    #[test]\n 678|    fn watcher_crash_exits_respawn_then_give_up() {\n 679|        let tmp = tempfile::tempdir().unwrap();\n...\n 705|            !perch.join(WAKER_GAVE_UP_FILE).exists(),\n*706|            \"shell activity re-arms\"\n 707|        );\n 708|    }\n 709|\n### sync.rs\n 381|        let ev = brain.read_peer_reply_until(deadline)?;\n*382|        // Re-arm the budget on each chunk that arrives on OUR stream: a whole\n 383|        // reply-read budget of NO progress is a wedge; a streaming pull that keeps\n 384|        // delivering stays healthy however long it runs (REQ-HAZARD-PUMP-IPC-DEADLINE).\n 385|        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n### translation.rs\n 69|    /// Pause this many milliseconds between keystrokes (the choreography's stash\n*70|    /// settle, e.g. `{\"delay_ms\":50}`). Carried in the sequence, applied by the\n 71|    /// broker's atomic applier.\n 72|    Delay { delay_ms: u64 },\n 73|    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\n### wan.rs\n 14|//! 2. **op-id idempotency** (REQ-HAZARD-RESTART-IDEMPOTENT): the wire feed\n*15|//!    replays — a sender's journaled redrive, or this brain restarting and\n 16|//!    resubscribing from an earlier cursor — so delivery dedups on the\n 17|//!    sender-minted durable `op_id`.\n 18|//!\n\n### bin/\n#### xlate_choreo_fixture.rs\n 27|//!     REQ-INJECT-MULTILINE-INTEGRITY): a single big typed payload the broker's\n*28|//!     settle-gate + chunked write must deliver byte-complete (head AND tail intact)\n 29|//!     across a LOOP of injects.\n 30|//!\n 31|//! Privacy assertion support (doyle gate #1): every stdin line we receive is\n...\n 142|/// would false-pass). ~12 lines of ~70 cols ≈ 850 B — larger than the field's ~322 B\n*143|/// truncation and many settle-chunks wide, yet it fits inside a 24-row screen grid so a\n 144|/// cold-attach repaint (ADR-0031 = current screen) still carries the HEAD (a taller\n 145|/// payload scrolls HEADSTART off the visible grid).\n 146|fn big_multiline_payload() -> String {\n\n## tests/\n### brain_resume_conn_deadlock.rs\n 264|///\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...\n 286|/// — no deadlock, and (at N=3) no write is held long enough for the r4 watchdog to\n*287|/// retire/evict a viewer before we read the counts. State is settled the moment the\n 288|/// barrier `sessions()` returns.\n 289|// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 290|#[test]\n...\n 339|    // reply-read loop also drains the replay so no viewer writer blocks/evicts. State\n*340|    // is settled on return — no timing window.\n 341|    hazard_brain\n 342|        .sessions()\n 343|        .expect(\"barrier round-trip flushes the fire-and-forget subscribes\");\n### broker.rs\n 916|        // The SAME (session, op_id) sent THREE times, all ack=false (the fire-and-\n*917|        // forward replay an rc re-drive after a hiccup would produce). The broker\n 918|        // dedups by the key; the PtyWrite runs only on the first apply_once.\n 919|        let line = b\"ONCE-ONLY-LINE\\r\";\n 920|        for _ in 0..3 {\n...\n 927|        // Drain output until the line round-trips (or a bounded frame budget runs\n*928|        // out). We do NOT add a trailing fixed-duration settle loop: a blocking\n 929|        // `read_frame` on a Windows named pipe cannot be time-bounded, so once the\n*930|        // echo child goes idle a settle read would hang. A double-type (the bug we\n 931|        // guard) is caught authoritatively by the broker's applied-set\n 932|        // (`applied_count == 1`) — the echo count is corroboration, not the proof —\n 933|        // so an exact echo-count window is unnecessary here.\n\nShowing files 1-20 of 39+. Use skip=20 for the next page, or narrow paths/pattern."}],"details":{"scopePath":"spt-core/crates/spt-daemon, spt-core/crates/spt-live, spt-core/crates/spt","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":62,"fileCount":20,"files":["spt-core/crates/spt-daemon/src/attach.rs","spt-core/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","spt-core/crates/spt-daemon/src/brain.rs","spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/daemon.rs","spt-core/crates/spt-daemon/src/effect.rs","spt-core/crates/spt-daemon/src/lib.rs","spt-core/crates/spt-daemon/src/linkhost.rs","spt-core/crates/spt-daemon/src/livehost.rs","spt-core/crates/spt-daemon/src/msg.rs","spt-core/crates/spt-daemon/src/propagate.rs","spt-core/crates/spt-daemon/src/resting.rs","spt-core/crates/spt-daemon/src/serveprobe.rs","spt-core/crates/spt-daemon/src/shellhost.rs","spt-core/crates/spt-daemon/src/shellwake.rs","spt-core/crates/spt-daemon/src/sync.rs","spt-core/crates/spt-daemon/src/translation.rs","spt-core/crates/spt-daemon/src/wan.rs","spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/crates/spt-daemon/tests/broker.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/attach.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/brain.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/daemon.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/effect.rs","count":16},{"path":"spt-core/crates/spt-daemon/src/lib.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/linkhost.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/livehost.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/msg.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/propagate.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/resting.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/serveprobe.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/shellhost.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/shellwake.rs","count":2},{"path":"spt-core/crates/spt-daemon/src/sync.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/translation.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/wan.rs","count":1},{"path":"spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","count":3},{"path":"spt-core/crates/spt-daemon/tests/broker.rs","count":3}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# spt-core/crates/spt-daemon/\n\n## src/\n### attach.rs\n 39│//! A restarted target brain re-attaches the session from its output cursor,\n*40│//! resubscribes the stream from its net cursor, and re-drives journaled sends;\n 41│//! a restarted operator brain resubscribes and dedups rendered chunks by\n 42│//! `Output.seq`.\n 43│\n### brain.rs\n 509│    /// the same logical effect must always carry the same id, so the broker's\n*510│    /// applied-set recognizes the re-drive. The broker answers with a\n 511│    /// [`BrokerEvent::Applied`] for `(session, op_id)`.\n 512│    ///\n 513│    /// ACKED (`ack=true`): the caller waits on the [`BrokerEvent::Applied`] one at a\n### broker.rs\n 353│// The typed idle-inject leg ate HEAD bytes nondeterministically: bytes typed into a\n*354│// PTY whose input reader is not yet attached/settled are swallowed mid-stream (the\n*355│// pre-settle race — 1854B once landed intact, ~1150B usually swallowed = a timing\n 356│// class, not a size cap). A live-SENT inject leaves NO spool copy, so a swallow is\n*357│// silent AND unrecoverable. The fix is three layers: (1) MUST settle-gate before ANY\n*358│// byte (positive readiness probe, not a blind timer); (2) MUST echo-verify the head\n*359│// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n 360│// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n 361│// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n 362│\n*363│/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n 364│/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n 365│/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n 366│/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n*367│/// inject after startup, the pre-settle window; re-drives settle again). An echoing PTY\n 368│/// returns in a tick or two, well under this.\n*369│const INJECT_SETTLE_DEADLINE: Duration = Duration::from_millis(400);\n*370│/// Poll/probe cadence within the settle-gate and the echo-verify wait.\n*371│const INJECT_SETTLE_POLL: Duration = Duration::from_millis(40);\n 372│/// The benign readiness probe: a DSR cursor-position query (`ESC[6n`). Invisible to\n 373│/// a TUI input line and never a submit; a live terminal/child answers or re-renders,\n 374│/// producing output — the POSITIVE signal that the input reader is attached.\n*375│const INJECT_SETTLE_PROBE: &[u8] = b\"\\x1b[6n\";\n 376│\n*377│/// The effective settle-gate deadline; the int gate tunes it via\n*378│/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n*379│fn inject_settle_deadline() -> Duration {\n*380│    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n 381│        Ok(ms) => ms\n 382│            .parse::<u64>()\n 383│            .map(Duration::from_millis)\n*384│            .unwrap_or(INJECT_SETTLE_DEADLINE),\n*385│        Err(_) => INJECT_SETTLE_DEADLINE,\n 386│    }\n 387│}\n 388│\n*389│/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n 390│/// the typed payload's head to echo back on the session ring before declaring a\n 391│/// verify MISS (round-trip + XON-resume latency bound).\n*392│const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n 393│\n*394│/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n### daemon.rs\n 1261│    // [unit->REQ-DAEMON-9] the net self-heal backoff ladder doubles each retry\n*1262│    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n 1263│    // 30s poll, never an ever-growing or zero-length spin.\n 1264│    #[test]\n 1265│    fn net_retry_backoff_doubles_then_caps() {\n### effect.rs\n  17│//! ([`EffectJournal::apply_once`] Err arm): not marking `applied` is what lets a\n *18│//! re-drive re-attempt it (no drop), and the failure is surfaced to the caller\n  19│//! (the broker routes the `apply_once` Err to `send_error` → a `KIND_ERROR`\n  20│//! frame) — never silent. So a `PENDING` with no matching `DONE` is the EXPECTED\n  21│//! shape of an effect that did not complete: for a `net-send`, an **unconfirmed\n *22│//! write to an offline/unreachable peer**, retained as the re-drive recovery\n *23│//! marker (it re-drives on the next brain restart / reachability). Two reader\n *24│//! caveats: the log is **append-only**, so a failed-then-re-driven effect keeps\n  25│//! its stale `PENDING` line AND appends a fresh `PENDING`+`DONE` — a raw\n  26│//! line-count OVER-reads the live pending set; [`recover`] is authoritative and\n  27│//! dedups **by key** (any `DONE` for a key marks it applied). And `PENDING`/`DONE`\n    │...\n  57│//! therefore hits an already-populated applied-set and is deduped (no dup); the\n *58│//! re-drive itself is what re-attempts an un-applied effect (no drop).\n  59│//!\n  60│//! The applied-set is additionally journaled to disk (`PENDING`/`DONE` lines,\n  61│//! fsync'd) so it is rebuilt on a broker *restart* — a forward-looking durability\n    │...\n 293│/// produced the op, and the source's durable op id (a PTY-write sequence, a\n*294│/// spool-row id, …). The dedup key — the same logical effect re-driven after a\n 295│/// crash carries the *same* key, so the applied-set recognizes the replay. The\n 296│/// `minter` dimension (ADR-0034) stops two counters that both start at 1 from\n 297│/// colliding in the one journal namespace.\n    │...\n 377│    /// W1b wedge fix), and the `applied` set is the authoritative dedup anchor, so a\n*378│    /// re-driven effect either dedups or re-attempts — no double-apply, no lost op.\n 379│    // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\n 380│    fn lock_recover(&self) -> std::sync::MutexGuard<'_, Inner> {\n 381│        self.inner\n    │...\n 389│    /// `key` joins the applied-set ([`Outcome::Applied`]). On closure error the key\n*390│    /// is left un-applied (so a re-drive re-attempts it) and the error propagated.\n 391│    ///\n 392│    /// **W1b (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE):** the inner lock is RELEASED\n 393│    /// across `effect()`. Pre-W1b the lock was held across both fsyncs AND the\n    │...\n 409│    /// the reserve→effect→finalize sequence runs to completion on the broker thread\n*410│    /// uninterrupted; a re-driven (replayed) effect hits the applied-set and dedups.\n 411│    /// Same-key concurrency does not occur in the model (a crashed brain is gone\n*412│    /// before its successor re-drives the same durable id), so the released window\n 413│    /// is only ever crossed by *different* keys — which is the entire point.\n 414│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n 415│    // [impl->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]\n    │...\n 459│            // The effect did not complete: leave the (durable) PENDING marker for\n*460│            // recovery surfacing and do NOT mark applied, so a re-drive re-attempts\n 461│            // it (no drop) — the pre-W1b error semantics.\n 462│            Err(e) => Err(e),\n 463│        }\n    │...\n 668│    // [unit->REQ-HAZARD-RESTART-IDEMPOTENT] a closure error leaves the key\n*669│    // un-applied (no false DONE) so a later re-drive re-attempts it — no drop.\n 670│    #[test]\n 671│    fn failed_effect_is_not_recorded_applied() {\n 672│        let dir = tempfile::tempdir().unwrap();\n    │...\n 682│\n*683│        // Re-drive succeeds and applies exactly once.\n 684│        let ok = j.apply_once(key, EffectKind::Spool, || Ok(())).unwrap();\n 685│        assert_eq!(ok, Outcome::Applied);\n 686│        assert!(j.is_applied(key));\n    │...\n 778│\n*779│    /// One brain life: re-drive the durable source `0..total` through the\n 780│    /// persistent journal, appending each applied op to `effects` (the real,\n 781│    /// durable side-effect target). `crash` injects a window on one op.\n 782│    fn run_life(\n    │...\n 798│                .expect(\"apply_once\");\n*799│            // A re-drive of an already-applied op must not re-push.\n 800│            debug_assert!(outcome == Outcome::Applied || outcome == Outcome::Deduped);\n 801│            if crash == Some((op, Window::AfterApplied)) {\n 802│                return; // effect landed; the source crashed before marking it done\n    │...\n 963│            Some((5, Window::AfterApplied)), // effect 5 landed, ack lost\n*964│            Some((5, Window::BeforeIntent)), // re-drive crashes again before 5...\n 965│            Some((8, Window::AfterApplied)),\n 966│            Some((11, Window::BeforeIntent)),\n 967│            None, // a clean life finishes the tail\n### lib.rs\n 70│//! discipline) a brain subscribes to with a resume cursor; sends/opens carry\n*71│//! durable `op_id`s journaled at the broker, so a restarted brain's re-drive\n 72│//! dedups instead of double-writing the wire.\n 73│//!\n 74│//! **D4a (this commit)** moves the node's network presence into the broker\n### linkhost.rs\n 577│                // wire (the remote operator has no TTY here); the operator\n*578│                // approves on the shell's node, then re-drives.\n 579│                Err(ShellLinkError::NeedsApproval(ask, _)) => {\n 580│                    let d = format!(\n*581│                        \"op needs approval on the shell's node ({}); approve there, then re-drive\",\n 582│                        ask.capability\n 583│                    );\n 584│                    (\n### livehost.rs\n 1706│            // W0.4 mutate_info sentinel lock makes status reliably stay offline, so\n*1707│            // un-host reliably fires; the earlier long-pulse+settle workaround is\n 1708│            // removed (the lock, not timing, is what makes this deterministic).\n 1709│            let cfg = fast_cfg();\n 1710│            let run = |reason| {\n### msg.rs\n 864│    /// wake); `false` = IDLE / no hosted session → spool NON-deferred (the live\n*865│    /// relay/poll surfaces it = the spt-core-driven wake). Additive + serde-defaulted\n 866│    /// (`false`): an N-1 broker omits it and the caller spools non-deferred exactly as\n 867│    /// in v0.14.3. Meaningless (ignored) when `delivered == true`.\n 868│    #[serde(default)]\n### propagate.rs\n 380│        let ev = brain.read_peer_reply_until(deadline)?;\n*381│        // Re-arm the budget on each chunk on OUR stream: a whole reply-read budget\n 382│        // of NO progress is a wedge, a streaming pull is healthy (REQ-HAZARD-PUMP-IPC-DEADLINE).\n 383│        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n 384│            deadline = brain.reply_read_deadline();\n### resting.rs\n 1144│\n*1145│            // wake: no gate re-arm; the freshness-pull marker drops instead.\n 1146│            std::fs::remove_file(&gate).unwrap();\n 1147│            let report = daemon_rest_event(\"ling\", RestEvent::Wake, None)\n 1148│                .expect(\"apply ok\")\n### serveprobe.rs\n  6│//!\n *7│//! `subnet status --nodes` settles liveness in two tiers: gossip-recency\n *8│//! first, a live probe for the rows gossip could not settle. The probe used to\n  9│//! be a raw transport dial by node-pubkey — it proved the peer's daemon\n 10│//! endpoint was bound (the box is up), but ONE ALPN serves every subnet, so it\n 11│//! could not tell \"still serving THIS subnet\" from \"up for its own home\". A\n### shellhost.rs\n 119│    // the binary rises — and any (re)launch is \"shell activity\", so the\n*120│    // crash give-up latch re-arms. [impl->REQ-SHELL-2]\n 121│    crate::shellwake::kill_waker_at(&perch);\n 122│    crate::shellwake::clear_gave_up(&perch);\n 123│    let token = mint_link_token();\n### shellwake.rs\n 675│    // (with backoff) exactly give_up_after times, then the durable give-up\n*676│    // latch drops and the resolution never fires; clear_gave_up re-arms.\n 677│    #[test]\n 678│    fn watcher_crash_exits_respawn_then_give_up() {\n 679│        let tmp = tempfile::tempdir().unwrap();\n    │...\n 705│            !perch.join(WAKER_GAVE_UP_FILE).exists(),\n*706│            \"shell activity re-arms\"\n 707│        );\n 708│    }\n 709│\n### sync.rs\n 381│        let ev = brain.read_peer_reply_until(deadline)?;\n*382│        // Re-arm the budget on each chunk that arrives on OUR stream: a whole\n 383│        // reply-read budget of NO progress is a wedge; a streaming pull that keeps\n 384│        // delivering stays healthy however long it runs (REQ-HAZARD-PUMP-IPC-DEADLINE).\n 385│        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n### translation.rs\n 69│    /// Pause this many milliseconds between keystrokes (the choreography's stash\n*70│    /// settle, e.g. `{\"delay_ms\":50}`). Carried in the sequence, applied by the\n 71│    /// broker's atomic applier.\n 72│    Delay { delay_ms: u64 },\n 73│    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\n### wan.rs\n 14│//! 2. **op-id idempotency** (REQ-HAZARD-RESTART-IDEMPOTENT): the wire feed\n*15│//!    replays — a sender's journaled redrive, or this brain restarting and\n 16│//!    resubscribing from an earlier cursor — so delivery dedups on the\n 17│//!    sender-minted durable `op_id`.\n 18│//!\n\n### bin/\n#### xlate_choreo_fixture.rs\n  27│//!     REQ-INJECT-MULTILINE-INTEGRITY): a single big typed payload the broker's\n *28│//!     settle-gate + chunked write must deliver byte-complete (head AND tail intact)\n  29│//!     across a LOOP of injects.\n  30│//!\n  31│//! Privacy assertion support (doyle gate #1): every stdin line we receive is\n    │...\n 142│/// would false-pass). ~12 lines of ~70 cols ≈ 850 B — larger than the field's ~322 B\n*143│/// truncation and many settle-chunks wide, yet it fits inside a 24-row screen grid so a\n 144│/// cold-attach repaint (ADR-0031 = current screen) still carries the HEAD (a taller\n 145│/// payload scrolls HEADSTART off the visible grid).\n 146│fn big_multiline_payload() -> String {\n\n## tests/\n### brain_resume_conn_deadlock.rs\n 264│///\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    │...\n 286│/// — no deadlock, and (at N=3) no write is held long enough for the r4 watchdog to\n*287│/// retire/evict a viewer before we read the counts. State is settled the moment the\n 288│/// barrier `sessions()` returns.\n 289│// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 290│#[test]\n    │...\n 339│    // reply-read loop also drains the replay so no viewer writer blocks/evicts. State\n*340│    // is settled on return — no timing window.\n 341│    hazard_brain\n 342│        .sessions()\n 343│        .expect(\"barrier round-trip flushes the fire-and-forget subscribes\");\n### broker.rs\n 916│        // The SAME (session, op_id) sent THREE times, all ack=false (the fire-and-\n*917│        // forward replay an rc re-drive after a hiccup would produce). The broker\n 918│        // dedups by the key; the PtyWrite runs only on the first apply_once.\n 919│        let line = b\"ONCE-ONLY-LINE\\r\";\n 920│        for _ in 0..3 {\n    │...\n 927│        // Drain output until the line round-trips (or a bounded frame budget runs\n*928│        // out). We do NOT add a trailing fixed-duration settle loop: a blocking\n 929│        // `read_frame` on a Windows named pipe cannot be time-bounded, so once the\n*930│        // echo child goes idle a settle read would hang. A double-type (the bug we\n 931│        // guard) is caught authoritatively by the broker's applied-set\n 932│        // (`applied_count == 1`) — the echo count is corroboration, not the proof —\n 933│        // so an exact echo-count window is unnecessary here."},"isError":false,"timestamp":1783730506266}}
{"type":"message","id":"4fcc51d0","parentId":"39b2f9d0","timestamp":"2026-07-11T00:41:46.273Z","message":{"role":"toolResult","toolCallId":"call_ecAtJpo7JIzN64mCLgy95oqc|fc_08496348794f7e09016a51914ab3a48198b0f4363d20010a12","toolName":"read","content":[{"type":"text","text":"//! Bounded, cancelable, poison-on-failure framed writes for one physical\n//! broker connection (UPDATE-WEDGE round-4).\n//!\n//! ## The failure class this kills\n//!\n//! A broker writer used to hold the connection's `Mutex<SendHalf>` guard\n//! ACROSS a blocking [`write_frame`]. On Windows, `interprocess 2.4.2` routes\n//! that send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — a local-socket\n//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\n//! A connected consumer that stops reading (an `rc --take` whose process\n//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the\n//! logical stall-evict (`stall_evict_controller`) releases only the controller\n//! ROLE — it never cancels the in-flight pipe write or closes the physical\n//! connection, so the writer (and every write queued behind the same gate)\n//! stays wedged until the peer process dies (~128 s in the field capture, on a\n//! brain restart).\n//!\n//! ## The primitive\n//!\n//! [`BrokerConn`] owns the send half outright — it never leaves this module,\n//! so an unbounded bypass write cannot compile. Every write:\n//!\n//! 1. acquires the serialized write GATE with a bounded wait (an ABSOLUTE\n//!    deadline stamped at write entry covers BOTH the gate-wait and the OS\n//!    write);\n//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute\n//!    deadline, then performs the blocking [`write_frame`] OUT of every lock;\n//! 3. on success, returns the half to the gate.\n//!\n//! The watchdog fires OUT OF BAND at the deadline — it never relies on the\n//! write returning, nor on opportunistic stall-evict sampling: it poisons the\n//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows — the\n//! completion APC makes the wedged `SleepEx` loop return with\n//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix — the blocked\n//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires\n//! the same abort (the stuck op it waited behind is the wedge).\n//!\n//! ## Poison / retire invariant (both OSes)\n//!\n//! On deadline, write failure, or cancellation: (1) the WHOLE physical\n//! connection is poisoned, idempotently — a length-prefixed frame may have\n//! been PARTIALLY written, so reuse would corrupt framing; (2) read and write\n//! sides are aborted so `handle_conn` reaches its existing EOF cleanup;\n//! (3) the buffer of the canceled op is released only after the op reports\n//! completion (`write_frame` returns only after the completion APC — the\n//! synchronous return IS the completion handshake); (4) the retired half is\n//! parked in a graveyard slot — never dropped while the watchdog might still\n//! touch the raw handle (drop = `CloseHandle`, and a recycled handle value\n//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\n//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\n//! client off, so the old client's next access fails, a re-issued server read\n//! fails, and the `interprocess` drop-limbo flush cannot block on unread\n//! data), followed by one more `CancelIoEx` covering a read re-issued in the\n//! cancel→disconnect window.\n//!\n//! No new output queue (the bounded per-sink queues + isolated writer threads\n//! already exist — the block this bounds is BELOW them), and no `PIPE_NOWAIT`\n//! (recorded mid-frame corruption risk). Unix keeps its existing semantics for\n//! healthy connections; only the poison/retire path is new.\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n\nuse std::io;\nuse std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\nuse std::sync::{Arc, Condvar, Mutex, MutexGuard};\nuse std::thread::{self, JoinHandle};\nuse std::time::{Duration, Instant};\n\nuse interprocess::local_socket::SendHalf;\n\nuse crate::codec::write_frame;\nuse crate::frame::Envelope;\n\n/// One bounded write's outcome (beyond `Err` = poisoned/timed-out/failed).\n#[derive(Debug, PartialEq, Eq)]\npub(crate) enum ConnWrite {\n    /// The frame was fully written.\n    Done,\n    /// The pre-write gate closure declined (e.g. a superseded controller\n    /// writer's epoch check) — nothing was written; the conn stays healthy.\n    Superseded,\n}\n\n/// The serialized write gate. `half: Some` = free; `None` = a write is in\n/// flight (checked out) or the conn has retired (then `poisoned` is set).\nstruct Gate {\n    half: Option<SendHalf>,\n}\n\n/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\nstruct Dog {\n    /// `Some((op_id, deadline))` while a write is in flight.\n    inflight: Option<(u64, Instant)>,\n    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\n    shutdown: bool,\n}\n\n/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\n/// the watchdog must not keep the conn alive).\nstruct Inner {\n    gate: Mutex<Gate>,\n    gate_cv: Condvar,\n    dog: Mutex<Dog>,\n    dog_cv: Condvar,\n    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\n    /// conn refuses every future write and is never re-gated.\n    poisoned: AtomicBool,\n    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\n    /// gate-waiter exhausted its bound) — as opposed to an organic I/O error\n    /// (peer death). Callers see it as `io::ErrorKind::TimedOut` and can\n    /// count the wedge (the W2 stall-evict tally) without conflating it with\n    /// an ordinary disconnect.\n    timed_out: AtomicBool,\n    /// The physical sever ran (exactly once; Windows `DisconnectNamedPipe`).\n    severed: AtomicBool,\n    /// A retired (poisoned) half parks here so its OS handle stays alive\n    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\n    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\n    graveyard: Mutex<Option<SendHalf>>,\n    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\n    op_seq: AtomicU64,\n    /// The absolute per-write bound (gate-wait + OS write completion).\n    bound: Duration,\n    /// The raw OS handle/fd of the underlying stream, captured at\n    /// construction for the out-of-band abort. The recv and send halves share\n    /// ONE underlying stream object (`interprocess` splits by ref-clone), so\n    /// aborting this handle covers a parked read too. Valid for the whole\n    /// conn lifetime: the half lives in `gate`, a writer's stack, or\n    /// `graveyard` until drop.\n    #[cfg(windows)]\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    /// 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    fn poison_and_cancel(&self) {\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\n            // First poison of this conn: loud, once — the observable fact the\n            // field-acceptance harness (blackhole-controller.ps1) watches for.\n            eprintln!(\n                \"CONN_WRITE_POISONED: physical broker conn retired — a write \\\n                 exceeded its {}ms bound (or failed); in-flight I/O aborted, \\\n                 conn never reused [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n                self.bound.as_millis()\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();\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();\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\n[Showing lines 1-303 of 512. Use :304 to continue]"}],"details":{"truncation":{"content":"//! Bounded, cancelable, poison-on-failure framed writes for one physical\r\n//! broker connection (UPDATE-WEDGE round-4).\r\n//!\r\n//! ## The failure class this kills\r\n//!\r\n//! A broker writer used to hold the connection's `Mutex<SendHalf>` guard\r\n//! ACROSS a blocking [`write_frame`]. On Windows, `interprocess 2.4.2` routes\r\n//! that send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — a local-socket\r\n//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\r\n//! A connected consumer that stops reading (an `rc --take` whose process\r\n//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the\r\n//! logical stall-evict (`stall_evict_controller`) releases only the controller\r\n//! ROLE — it never cancels the in-flight pipe write or closes the physical\r\n//! connection, so the writer (and every write queued behind the same gate)\r\n//! stays wedged until the peer process dies (~128 s in the field capture, on a\r\n//! brain restart).\r\n//!\r\n//! ## The primitive\r\n//!\r\n//! [`BrokerConn`] owns the send half outright — it never leaves this module,\r\n//! so an unbounded bypass write cannot compile. Every write:\r\n//!\r\n//! 1. acquires the serialized write GATE with a bounded wait (an ABSOLUTE\r\n//!    deadline stamped at write entry covers BOTH the gate-wait and the OS\r\n//!    write);\r\n//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute\r\n//!    deadline, then performs the blocking [`write_frame`] OUT of every lock;\r\n//! 3. on success, returns the half to the gate.\r\n//!\r\n//! The watchdog fires OUT OF BAND at the deadline — it never relies on the\r\n//! write returning, nor on opportunistic stall-evict sampling: it poisons the\r\n//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows — the\r\n//! completion APC makes the wedged `SleepEx` loop return with\r\n//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix — the blocked\r\n//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires\r\n//! the same abort (the stuck op it waited behind is the wedge).\r\n//!\r\n//! ## Poison / retire invariant (both OSes)\r\n//!\r\n//! On deadline, write failure, or cancellation: (1) the WHOLE physical\r\n//! connection is poisoned, idempotently — a length-prefixed frame may have\r\n//! been PARTIALLY written, so reuse would corrupt framing; (2) read and write\r\n//! sides are aborted so `handle_conn` reaches its existing EOF cleanup;\r\n//! (3) the buffer of the canceled op is released only after the op reports\r\n//! completion (`write_frame` returns only after the completion APC — the\r\n//! synchronous return IS the completion handshake); (4) the retired half is\r\n//! parked in a graveyard slot — never dropped while the watchdog might still\r\n//! touch the raw handle (drop = `CloseHandle`, and a recycled handle value\r\n//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\r\n//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\r\n//! client off, so the old client's next access fails, a re-issued server read\r\n//! fails, and the `interprocess` drop-limbo flush cannot block on unread\r\n//! data), followed by one more `CancelIoEx` covering a read re-issued in the\r\n//! cancel→disconnect window.\r\n//!\r\n//! No new output queue (the bounded per-sink queues + isolated writer threads\r\n//! already exist — the block this bounds is BELOW them), and no `PIPE_NOWAIT`\r\n//! (recorded mid-frame corruption risk). Unix keeps its existing semantics for\r\n//! healthy connections; only the poison/retire path is new.\r\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n\r\nuse std::io;\r\nuse std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\r\nuse std::sync::{Arc, Condvar, Mutex, MutexGuard};\r\nuse std::thread::{self, JoinHandle};\r\nuse std::time::{Duration, Instant};\r\n\r\nuse interprocess::local_socket::SendHalf;\r\n\r\nuse crate::codec::write_frame;\r\nuse crate::frame::Envelope;\r\n\r\n/// One bounded write's outcome (beyond `Err` = poisoned/timed-out/failed).\r\n#[derive(Debug, PartialEq, Eq)]\r\npub(crate) enum ConnWrite {\r\n    /// The frame was fully written.\r\n    Done,\r\n    /// The pre-write gate closure declined (e.g. a superseded controller\r\n    /// writer's epoch check) — nothing was written; the conn stays healthy.\r\n    Superseded,\r\n}\r\n\r\n/// The serialized write gate. `half: Some` = free; `None` = a write is in\r\n/// flight (checked out) or the conn has retired (then `poisoned` is set).\r\nstruct Gate {\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\r\nstruct Dog {\r\n    /// `Some((op_id, deadline))` while a write is in flight.\r\n    inflight: Option<(u64, Instant)>,\r\n    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\r\n    shutdown: bool,\r\n}\r\n\r\n/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\r\n/// the watchdog must not keep the conn alive).\r\nstruct Inner {\r\n    gate: Mutex<Gate>,\r\n    gate_cv: Condvar,\r\n    dog: Mutex<Dog>,\r\n    dog_cv: Condvar,\r\n    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\r\n    /// conn refuses every future write and is never re-gated.\r\n    poisoned: AtomicBool,\r\n    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\r\n    /// gate-waiter exhausted its bound) — as opposed to an organic I/O error\r\n    /// (peer death). Callers see it as `io::ErrorKind::TimedOut` and can\r\n    /// count the wedge (the W2 stall-evict tally) without conflating it with\r\n    /// an ordinary disconnect.\r\n    timed_out: AtomicBool,\r\n    /// The physical sever ran (exactly once; Windows `DisconnectNamedPipe`).\r\n    severed: AtomicBool,\r\n    /// A retired (poisoned) half parks here so its OS handle stays alive\r\n    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\r\n    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\r\n    graveyard: Mutex<Option<SendHalf>>,\r\n    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\r\n    op_seq: AtomicU64,\r\n    /// The absolute per-write bound (gate-wait + OS write completion).\r\n    bound: Duration,\r\n    /// The raw OS handle/fd of the underlying stream, captured at\r\n    /// construction for the out-of-band abort. The recv and send halves share\r\n    /// ONE underlying stream object (`interprocess` splits by ref-clone), so\r\n    /// aborting this handle covers a parked read too. Valid for the whole\r\n    /// conn lifetime: the half lives in `gate`, a writer's stack, or\r\n    /// `graveyard` until drop.\r\n    #[cfg(windows)]\r\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    /// 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    fn poison_and_cancel(&self) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once — the observable fact the\r\n            // field-acceptance harness (blackhole-controller.ps1) watches for.\r\n            eprintln!(\r\n                \"CONN_WRITE_POISONED: physical broker conn retired — a write \\\r\n                 exceeded its {}ms bound (or failed); in-flight I/O aborted, \\\r\n                 conn never reused [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.bound.as_millis()\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();\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();\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","truncated":true,"truncatedBy":"lines","totalLines":512,"totalBytes":13677,"outputLines":303,"outputBytes":13677,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! Bounded, cancelable, poison-on-failure framed writes for one physical\r\n//! broker connection (UPDATE-WEDGE round-4).\r\n//!\r\n//! ## The failure class this kills\r\n//!\r\n//! A broker writer used to hold the connection's `Mutex<SendHalf>` guard\r\n//! ACROSS a blocking [`write_frame`]. On Windows, `interprocess 2.4.2` routes\r\n//! that send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — a local-socket\r\n//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\r\n//! A connected consumer that stops reading (an `rc --take` whose process\r\n//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the\r\n//! logical stall-evict (`stall_evict_controller`) releases only the controller\r\n//! ROLE — it never cancels the in-flight pipe write or closes the physical\r\n//! connection, so the writer (and every write queued behind the same gate)\r\n//! stays wedged until the peer process dies (~128 s in the field capture, on a\r\n//! brain restart).\r\n//!\r\n//! ## The primitive\r\n//!\r\n//! [`BrokerConn`] owns the send half outright — it never leaves this module,\r\n//! so an unbounded bypass write cannot compile. Every write:\r\n//!\r\n//! 1. acquires the serialized write GATE with a bounded wait (an ABSOLUTE\r\n//!    deadline stamped at write entry covers BOTH the gate-wait and the OS\r\n//!    write);\r\n//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute\r\n//!    deadline, then performs the blocking [`write_frame`] OUT of every lock;\r\n//! 3. on success, returns the half to the gate.\r\n//!\r\n//! The watchdog fires OUT OF BAND at the deadline — it never relies on the\r\n//! write returning, nor on opportunistic stall-evict sampling: it poisons the\r\n//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows — the\r\n//! completion APC makes the wedged `SleepEx` loop return with\r\n//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix — the blocked\r\n//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires\r\n//! the same abort (the stuck op it waited behind is the wedge).\r\n//!\r\n//! ## Poison / retire invariant (both OSes)\r\n//!\r\n//! On deadline, write failure, or cancellation: (1) the WHOLE physical\r\n//! connection is poisoned, idempotently — a length-prefixed frame may have\r\n//! been PARTIALLY written, so reuse would corrupt framing; (2) read and write\r\n//! sides are aborted so `handle_conn` reaches its existing EOF cleanup;\r\n//! (3) the buffer of the canceled op is released only after the op reports\r\n//! completion (`write_frame` returns only after the completion APC — the\r\n//! synchronous return IS the completion handshake); (4) the retired half is\r\n//! parked in a graveyard slot — never dropped while the watchdog might still\r\n//! touch the raw handle (drop = `CloseHandle`, and a recycled handle value\r\n//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\r\n//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\r\n//! client off, so the old client's next access fails, a re-issued server read\r\n//! fails, and the `interprocess` drop-limbo flush cannot block on unread\r\n//! data), followed by one more `CancelIoEx` covering a read re-issued in the\r\n//! cancel→disconnect window.\r\n//!\r\n//! No new output queue (the bounded per-sink queues + isolated writer threads\r\n//! already exist — the block this bounds is BELOW them), and no `PIPE_NOWAIT`\r\n//! (recorded mid-frame corruption risk). Unix keeps its existing semantics for\r\n//! healthy connections; only the poison/retire path is new.\r\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n\r\nuse std::io;\r\nuse std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\r\nuse std::sync::{Arc, Condvar, Mutex, MutexGuard};\r\nuse std::thread::{self, JoinHandle};\r\nuse std::time::{Duration, Instant};\r\n\r\nuse interprocess::local_socket::SendHalf;\r\n\r\nuse crate::codec::write_frame;\r\nuse crate::frame::Envelope;\r\n\r\n/// One bounded write's outcome (beyond `Err` = poisoned/timed-out/failed).\r\n#[derive(Debug, PartialEq, Eq)]\r\npub(crate) enum ConnWrite {\r\n    /// The frame was fully written.\r\n    Done,\r\n    /// The pre-write gate closure declined (e.g. a superseded controller\r\n    /// writer's epoch check) — nothing was written; the conn stays healthy.\r\n    Superseded,\r\n}\r\n\r\n/// The serialized write gate. `half: Some` = free; `None` = a write is in\r\n/// flight (checked out) or the conn has retired (then `poisoned` is set).\r\nstruct Gate {\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\r\nstruct Dog {\r\n    /// `Some((op_id, deadline))` while a write is in flight.\r\n    inflight: Option<(u64, Instant)>,\r\n    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\r\n    shutdown: bool,\r\n}\r\n\r\n/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\r\n/// the watchdog must not keep the conn alive).\r\nstruct Inner {\r\n    gate: Mutex<Gate>,\r\n    gate_cv: Condvar,\r\n    dog: Mutex<Dog>,\r\n    dog_cv: Condvar,\r\n    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\r\n    /// conn refuses every future write and is never re-gated.\r\n    poisoned: AtomicBool,\r\n    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\r\n    /// gate-waiter exhausted its bound) — as opposed to an organic I/O error\r\n    /// (peer death). Callers see it as `io::ErrorKind::TimedOut` and can\r\n    /// count the wedge (the W2 stall-evict tally) without conflating it with\r\n    /// an ordinary disconnect.\r\n    timed_out: AtomicBool,\r\n    /// The physical sever ran (exactly once; Windows `DisconnectNamedPipe`).\r\n    severed: AtomicBool,\r\n    /// A retired (poisoned) half parks here so its OS handle stays alive\r\n    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\r\n    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\r\n    graveyard: Mutex<Option<SendHalf>>,\r\n    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\r\n    op_seq: AtomicU64,\r\n    /// The absolute per-write bound (gate-wait + OS write completion).\r\n    bound: Duration,\r\n    /// The raw OS handle/fd of the underlying stream, captured at\r\n    /// construction for the out-of-band abort. The recv and send halves share\r\n    /// ONE underlying stream object (`interprocess` splits by ref-clone), so\r\n    /// aborting this handle covers a parked read too. Valid for the whole\r\n    /// conn lifetime: the half lives in `gate`, a writer's stack, or\r\n    /// `graveyard` until drop.\r\n    #[cfg(windows)]\r\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    /// 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    fn poison_and_cancel(&self) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once — the observable fact the\r\n            // field-acceptance harness (blackhole-controller.ps1) watches for.\r\n            eprintln!(\r\n                \"CONN_WRITE_POISONED: physical broker conn retired — a write \\\r\n                 exceeded its {}ms bound (or failed); in-flight I/O aborted, \\\r\n                 conn never reused [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.bound.as_millis()\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();\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();\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","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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":512,"totalBytes":13677,"outputLines":303,"outputBytes":13677,"shownRange":{"start":1,"end":303},"nextOffset":304}}},"isError":false,"timestamp":1783730506273}}
{"type":"message","id":"1589101d","parentId":"4fcc51d0","timestamp":"2026-07-11T00:41:47.916Z","message":{"role":"toolResult","toolCallId":"call_pocKRd7C2LVypRNdKWYozfnn|fc_08496348794f7e09016a51914ab36c81988bc7963f9fdf35e3","toolName":"glob","content":[{"type":"text","text":"# spt-core/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n## .worktrees/\n### gate-w4/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n#### docs/\nUPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\nUPDATE-WEDGE-2-ROUND3-CODEREAD.md\nPUMP-W2-GATE-HANDOFF.md\nPUMP-W2-DESIGN.md\nPUMP-W2-DISPATCH-RULING.md\nPUMP-W2-BUILD-DELTA.md\nPUMP-W2-BUILD-GO.md\nPUMP-TRUTH-TRIAGE.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/\n##### spt/tests/\nready_resume_ledger_e2e.rs\nresume_no_control_steal_e2e.rs\nresume_template_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n##### spt-daemon/tests/\nresume.rs\npump.rs\npumpdeadline.rs\ninject_control_wedge.rs\nbrain_resume_conn_deadlock.rs\n### r4-stale-guard/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n#### target/debug/.fingerprint/\n##### spt-daemon-d11c7782a2a7a84e/\ntest-integration-test-resume.json\n##### spt-daemon-b89f5cdd84b1fd4b/\ntest-integration-test-brain_resume_conn_deadlock.json\n##### spt-daemon-bbc741d8b9366187/\ntest-integration-test-inject_control_wedge.json\n##### spt-daemon-34a734c899a9a3bb/\ntest-integration-test-pump.json\n##### spt-daemon-4d0d6baaa2021b91/\ntest-integration-test-pumpdeadline.json\n#### crates/\n##### spt-daemon/tests/\nbrain_resume_conn_deadlock.rs\nresume.rs\npumpdeadline.rs\npump.rs\ninject_control_wedge.rs\n##### spt/tests/\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\nresume_no_control_steal_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n#### docs/\nUPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\nUPDATE-WEDGE-2-ROUND3-CODEREAD.md\nPUMP-W2-GATE-HANDOFF.md\nPUMP-W2-DESIGN.md\nPUMP-W2-DISPATCH-RULING.md\nPUMP-W2-BUILD-GO.md\nPUMP-W2-BUILD-DELTA.md\nPUMP-TRUTH-TRIAGE.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n### gate-3a4335c/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n#### docs/\nUPDATE-WEDGE-2-ROUND3-CODEREAD.md\nUPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\nPUMP-W2-BUILD-GO.md\nPUMP-W2-DESIGN.md\nPUMP-W2-DISPATCH-RULING.md\nPUMP-W2-GATE-HANDOFF.md\nPUMP-TRUTH-TRIAGE.md\nPUMP-W2-BUILD-DELTA.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/spt-live/src/\nresume.rs\n### rca-capture/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n#### docs/\nUPDATE-WEDGE-ROOT-CAUSE.md\nPUMP-TRUTH-TRIAGE.md\nPUMP-W2-DISPATCH-RULING.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/\n##### spt/tests/\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n##### spt-daemon/tests/\nresume.rs\npump.rs\npumpdeadline.rs\ninject_control_wedge.rs\n### f-035/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\nPUMP-SEAM-PLAN.md\n#### docs/\nPUMP-W2-GATE-HANDOFF.md\nPUMP-W2-DISPATCH-RULING.md\nPUMP-W2-DESIGN.md\nPUMP-W2-BUILD-GO.md\nPUMP-W2-BUILD-DELTA.md\nPUMP-TRUTH-TRIAGE.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/\n##### spt/tests/\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n##### spt-daemon/tests/\nresume.rs\npump.rs\npumpdeadline.rs\ninject_control_wedge.rs\n### spec-pilot/\n#### docs/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\n##### process/\nPUMP-W2-DISPATCH-RULING.md\nPUMP-TRUTH-TRIAGE.md\n##### plans/\nPUMP-SEAM-PLAN.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/\n##### spt/tests/\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n##### spt-daemon/tests/\nresume.rs\npumpdeadline.rs\npump.rs\ninject_control_wedge.rs\n### docs-reorg/\n#### docs/\nV0.13.0-P2-SESSION-RESUME-DESIGN.md\n##### process/\nPUMP-TRUTH-TRIAGE.md\nPUMP-W2-DISPATCH-RULING.md\n##### plans/\nPUMP-SEAM-PLAN.md\n##### adr/\n0033-wake-resume-via-reconcile-intent.md\n#### crates/\n##### spt/tests/\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\nattach_wedge_e2e.rs\n##### spt-live/src/\nresume.rs\n##### spt-daemon/tests/\nresume.rs\npumpdeadline.rs\npump.rs\ninject_control_wedge.rs\n## crates/\n### spt-daemon/tests/\ninject_control_wedge.rs\npumpdeadline.rs\npump.rs\nbrain_resume_conn_deadlock.rs\nresume.rs\n### spt/tests/\nresume_no_control_steal_e2e.rs\nattach_wedge_e2e.rs\nresume_template_e2e.rs\nready_resume_ledger_e2e.rs\n### spt-live/src/\nresume.rs\n## target/debug/.fingerprint/\n### spt-fb260aa960ab9be5/\ntest-integration-test-resume_no_control_steal_e2e.json\n### spt-e6d314ac79bfe193/\ntest-integration-test-attach_wedge_e2e.json\n### spt-90fa06efa130ef58/\ntest-integration-test-ready_resume_ledger_e2e.json\n### spt-c37bff65e10f4696/\ntest-integration-test-resume_template_e2e.json\n### spt-daemon-773a6a731ecf5fb4/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-762b6a5b8ebe3679/\ntest-integration-test-brain_resume_conn_deadlock.json\n### spt-daemon-75a32aee02ddc536/\ntest-integration-test-pump.json\n### spt-daemon-9ab87e652490209d/\ntest-integration-test-inject_control_wedge.json\n### spt-daemon-4c873d51006d89fb/\ntest-integration-test-resume.json\n### spt-daemon-06d1f493479a8d2a/\ntest-integration-test-inject_control_wedge.json\n### spt-8ed363bf58fdf981/\ntest-integration-test-attach_wedge_e2e.json\n### spt-daemon-1176e15ae7ca35c0/\ntest-integration-test-resume.json\n### spt-daemon-3e0a946f5aaf1ff0/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-081b723a227dfc3a/\ntest-integration-test-inject_control_wedge.json\n### spt-fe2a0d3684768ea3/\ntest-integration-test-resume_no_control_steal_e2e.json\n### spt-243ab0defaaf3cf7/\ntest-integration-test-resume_template_e2e.json\n### spt-daemon-bf9477dc64929020/\ntest-integration-test-pump.json\n### spt-daemon-23e90ba31d37ae9d/\ntest-integration-test-brain_resume_conn_deadlock.json\n### spt-53e7b9a81ba7412e/\ntest-integration-test-ready_resume_ledger_e2e.json\n### spt-daemon-0b805b18f52e6ce0/\ntest-integration-test-pump.json\n### spt-daemon-6d28d0716fe465e7/\ntest-integration-test-resume.json\n### spt-daemon-c803381af9725b93/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-cf9e48e72c128235/\ntest-integration-test-inject_control_wedge.json\n### spt-daemon-56313ba059c6e1ba/\ntest-integration-test-pump_blackhole_rca.json\n### spt-daemon-5d1fea8415fcbb49/\ntest-integration-test-pump.json\n### spt-daemon-8f0e846fedfcd175/\ntest-integration-test-resume.json\n### spt-daemon-1819f26f198e1fba/\ntest-integration-test-inject_control_wedge.json\n### spt-daemon-c571591015b6100f/\ntest-integration-test-pumpdeadline.json\n### spt-a488c3a2cad9baca/\ntest-integration-test-ready_resume_ledger_e2e.json\n### spt-daemon-faa62cbab87bf72a/\ntest-integration-test-inject_control_wedge.json\n### spt-2dc258ba2283d46d/\ntest-integration-test-attach_wedge_e2e.json\n### spt-daemon-26e3bba57927a3f7/\ntest-integration-test-resume.json\n### spt-daemon-41bd0087851e8eff/\ntest-integration-test-pump.json\n### spt-7a10edb91267cace/\ntest-integration-test-resume_template_e2e.json\n### spt-daemon-c7f8348812b41fb5/\ntest-integration-test-pumpdeadline.json\n### spt-0d360a9d34711544/\ntest-integration-test-ready_resume_ledger_e2e.json\n### spt-daemon-d6353ecedfd7c910/\ntest-integration-test-inject_control_wedge.json\n### spt-5032f406c66e2b04/\ntest-integration-test-resume_template_e2e.json\n### spt-daemon-743e2fe51bf9fb44/\ntest-integration-test-resume.json\n### spt-daemon-5f7a8fd91c4d4623/\ntest-integration-test-pump.json\n### spt-bb688c1c9dadbf64/\ntest-integration-test-attach_wedge_e2e.json\n### spt-daemon-642176d609a4d179/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-8a9aed26e6254a9c/\ntest-integration-test-resume.json\n### spt-daemon-6aad606d9d21daf2/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-e6f6a377a1f42ae2/\ntest-integration-test-inject_control_wedge.json\n### spt-daemon-6f330d2df3bd5d84/\ntest-integration-test-pump.json\n### spt-daemon-422c394632e969e5/\ntest-integration-test-pump.json\n### spt-daemon-e630da1f1fe5bc14/\ntest-integration-test-resume.json\n### spt-daemon-67a41c7da1be7819/\ntest-integration-test-inject_control_wedge.json\n### spt-daemon-274a5fbf816d76fe/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-472204346480622c/\ntest-integration-test-inject_control_wedge.json\n## docs/\nUPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\nUPDATE-WEDGE-2-ROUND3-CODEREAD.md\nPUMP-W2-GATE-HANDOFF.md\nPUMP-W2-BUILD-GO.md\nPUMP-W2-DESIGN.md\nPUMP-W2-BUILD-DELTA.md\nPUMP-TRUTH-TRIAGE.md\nPUMP-W2-DISPATCH-RULING.md\n### adr/\n0033-wake-resume-via-reconcile-intent.md\n## .claude/agent-memory/spt-test-engineer/\np0-pty-input-writer-wedge-tests.md\nresume-template-marker-fixture.md\neffect-journal-wedge-tests.md\n## target-seam/debug/.fingerprint/\n### spt-40ffac30ce86f2c4/\ntest-integration-test-ready_resume_ledger_e2e.json\n### spt-daemon-9c529ff5c832354f/\ntest-integration-test-resume.json\n### spt-daemon-a646501c1c5624b9/\ntest-integration-test-pumpdeadline.json\n### spt-daemon-ba31b6648cd232d7/\ntest-integration-test-pump.json"}],"details":{"scopePath":"spt-core","fileCount":195,"files":["spt-core/.worktrees/gate-w4/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/gate-w4/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","spt-core/.worktrees/gate-w4/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","spt-core/.worktrees/gate-w4/docs/PUMP-W2-GATE-HANDOFF.md","spt-core/.worktrees/gate-w4/docs/PUMP-W2-DESIGN.md","spt-core/.worktrees/gate-w4/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/gate-w4/docs/PUMP-W2-BUILD-DELTA.md","spt-core/.worktrees/gate-w4/docs/PUMP-W2-BUILD-GO.md","spt-core/.worktrees/gate-w4/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/gate-w4/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/gate-w4/crates/spt/tests/resume_no_control_steal_e2e.rs","spt-core/.worktrees/gate-w4/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/gate-w4/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/gate-w4/crates/spt-live/src/resume.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/.worktrees/gate-w4/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/gate-w4/PUMP-SEAM-PLAN.md","spt-core/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/target/debug/.fingerprint/spt-fb260aa960ab9be5/test-integration-test-resume_no_control_steal_e2e.json","spt-core/target/debug/.fingerprint/spt-e6d314ac79bfe193/test-integration-test-attach_wedge_e2e.json","spt-core/target/debug/.fingerprint/spt-90fa06efa130ef58/test-integration-test-ready_resume_ledger_e2e.json","spt-core/target/debug/.fingerprint/spt-c37bff65e10f4696/test-integration-test-resume_template_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-773a6a731ecf5fb4/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-762b6a5b8ebe3679/test-integration-test-brain_resume_conn_deadlock.json","spt-core/target/debug/.fingerprint/spt-daemon-75a32aee02ddc536/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-9ab87e652490209d/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-daemon-4c873d51006d89fb/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-06d1f493479a8d2a/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-8ed363bf58fdf981/test-integration-test-attach_wedge_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-1176e15ae7ca35c0/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-3e0a946f5aaf1ff0/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-081b723a227dfc3a/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-fe2a0d3684768ea3/test-integration-test-resume_no_control_steal_e2e.json","spt-core/target/debug/.fingerprint/spt-243ab0defaaf3cf7/test-integration-test-resume_template_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-bf9477dc64929020/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-23e90ba31d37ae9d/test-integration-test-brain_resume_conn_deadlock.json","spt-core/target/debug/.fingerprint/spt-53e7b9a81ba7412e/test-integration-test-ready_resume_ledger_e2e.json","spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","spt-core/docs/PUMP-W2-GATE-HANDOFF.md","spt-core/docs/PUMP-W2-BUILD-GO.md","spt-core/docs/PUMP-W2-DESIGN.md","spt-core/docs/PUMP-W2-BUILD-DELTA.md","spt-core/crates/spt/tests/resume_no_control_steal_e2e.rs","spt-core/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/crates/spt-daemon/tests/pump.rs","spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/.worktrees/r4-stale-guard/target/debug/.fingerprint/spt-daemon-d11c7782a2a7a84e/test-integration-test-resume.json","spt-core/.worktrees/r4-stale-guard/target/debug/.fingerprint/spt-daemon-b89f5cdd84b1fd4b/test-integration-test-brain_resume_conn_deadlock.json","spt-core/.worktrees/r4-stale-guard/target/debug/.fingerprint/spt-daemon-bbc741d8b9366187/test-integration-test-inject_control_wedge.json","spt-core/.worktrees/r4-stale-guard/target/debug/.fingerprint/spt-daemon-34a734c899a9a3bb/test-integration-test-pump.json","spt-core/.worktrees/r4-stale-guard/target/debug/.fingerprint/spt-daemon-4d0d6baaa2021b91/test-integration-test-pumpdeadline.json","spt-core/.worktrees/gate-3a4335c/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/gate-3a4335c/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","spt-core/.worktrees/gate-3a4335c/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-W2-BUILD-GO.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-W2-DESIGN.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-W2-GATE-HANDOFF.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/gate-3a4335c/docs/PUMP-W2-BUILD-DELTA.md","spt-core/.worktrees/gate-3a4335c/crates/spt-live/src/resume.rs","spt-core/.worktrees/gate-3a4335c/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/gate-3a4335c/PUMP-SEAM-PLAN.md","spt-core/.worktrees/r4-stale-guard/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","spt-core/.worktrees/r4-stale-guard/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/r4-stale-guard/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","spt-core/.worktrees/r4-stale-guard/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-W2-GATE-HANDOFF.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-W2-DESIGN.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-W2-BUILD-GO.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-W2-BUILD-DELTA.md","spt-core/.worktrees/r4-stale-guard/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/r4-stale-guard/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/r4-stale-guard/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/r4-stale-guard/crates/spt/tests/resume_no_control_steal_e2e.rs","spt-core/.worktrees/r4-stale-guard/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/r4-stale-guard/crates/spt-live/src/resume.rs","spt-core/.worktrees/r4-stale-guard/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/r4-stale-guard/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/r4-stale-guard/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/r4-stale-guard/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/.worktrees/r4-stale-guard/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/r4-stale-guard/PUMP-SEAM-PLAN.md","spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","spt-core/.worktrees/rca-capture/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/rca-capture/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/rca-capture/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/rca-capture/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/rca-capture/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/rca-capture/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/rca-capture/crates/spt-live/src/resume.rs","spt-core/.worktrees/rca-capture/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/rca-capture/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/rca-capture/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/rca-capture/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/.worktrees/rca-capture/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/rca-capture/PUMP-SEAM-PLAN.md","spt-core/target/debug/.fingerprint/spt-daemon-0b805b18f52e6ce0/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-6d28d0716fe465e7/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-c803381af9725b93/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-cf9e48e72c128235/test-integration-test-inject_control_wedge.json","spt-core/.worktrees/f-035/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/f-035/docs/PUMP-W2-GATE-HANDOFF.md","spt-core/.worktrees/f-035/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/f-035/docs/PUMP-W2-DESIGN.md","spt-core/.worktrees/f-035/docs/PUMP-W2-BUILD-GO.md","spt-core/.worktrees/f-035/docs/PUMP-W2-BUILD-DELTA.md","spt-core/.worktrees/f-035/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/f-035/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/f-035/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/f-035/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/f-035/crates/spt-live/src/resume.rs","spt-core/.worktrees/f-035/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/f-035/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/f-035/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/f-035/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/.worktrees/f-035/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/f-035/PUMP-SEAM-PLAN.md","spt-core/.worktrees/spec-pilot/docs/process/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/spec-pilot/docs/process/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/spec-pilot/docs/plans/PUMP-SEAM-PLAN.md","spt-core/.worktrees/spec-pilot/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/spec-pilot/docs/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.worktrees/spec-pilot/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/spec-pilot/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/spec-pilot/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/spec-pilot/crates/spt-live/src/resume.rs","spt-core/.worktrees/spec-pilot/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/spec-pilot/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/spec-pilot/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/spec-pilot/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/.worktrees/docs-reorg/docs/process/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/docs-reorg/docs/process/PUMP-W2-DISPATCH-RULING.md","spt-core/.worktrees/docs-reorg/docs/plans/PUMP-SEAM-PLAN.md","spt-core/.worktrees/docs-reorg/docs/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/docs/PUMP-TRUTH-TRIAGE.md","spt-core/.worktrees/docs-reorg/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/.worktrees/docs-reorg/crates/spt/tests/resume_template_e2e.rs","spt-core/.worktrees/docs-reorg/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/.worktrees/docs-reorg/crates/spt/tests/attach_wedge_e2e.rs","spt-core/.worktrees/docs-reorg/crates/spt-live/src/resume.rs","spt-core/.worktrees/docs-reorg/crates/spt-daemon/tests/resume.rs","spt-core/.worktrees/docs-reorg/crates/spt-daemon/tests/pumpdeadline.rs","spt-core/.worktrees/docs-reorg/crates/spt-daemon/tests/pump.rs","spt-core/.worktrees/docs-reorg/crates/spt-daemon/tests/inject_control_wedge.rs","spt-core/docs/PUMP-W2-DISPATCH-RULING.md","spt-core/target/debug/.fingerprint/spt-daemon-56313ba059c6e1ba/test-integration-test-pump_blackhole_rca.json","spt-core/target/debug/.fingerprint/spt-daemon-5d1fea8415fcbb49/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-8f0e846fedfcd175/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-1819f26f198e1fba/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-daemon-c571591015b6100f/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-a488c3a2cad9baca/test-integration-test-ready_resume_ledger_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-faa62cbab87bf72a/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-2dc258ba2283d46d/test-integration-test-attach_wedge_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-26e3bba57927a3f7/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-41bd0087851e8eff/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-7a10edb91267cace/test-integration-test-resume_template_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-c7f8348812b41fb5/test-integration-test-pumpdeadline.json","spt-core/crates/spt/tests/attach_wedge_e2e.rs","spt-core/target/debug/.fingerprint/spt-0d360a9d34711544/test-integration-test-ready_resume_ledger_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-d6353ecedfd7c910/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-5032f406c66e2b04/test-integration-test-resume_template_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-743e2fe51bf9fb44/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-5f7a8fd91c4d4623/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-bb688c1c9dadbf64/test-integration-test-attach_wedge_e2e.json","spt-core/target/debug/.fingerprint/spt-daemon-642176d609a4d179/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-8a9aed26e6254a9c/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-6aad606d9d21daf2/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-e6f6a377a1f42ae2/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-daemon-6f330d2df3bd5d84/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-422c394632e969e5/test-integration-test-pump.json","spt-core/target/debug/.fingerprint/spt-daemon-e630da1f1fe5bc14/test-integration-test-resume.json","spt-core/target/debug/.fingerprint/spt-daemon-67a41c7da1be7819/test-integration-test-inject_control_wedge.json","spt-core/target/debug/.fingerprint/spt-daemon-274a5fbf816d76fe/test-integration-test-pumpdeadline.json","spt-core/target/debug/.fingerprint/spt-daemon-472204346480622c/test-integration-test-inject_control_wedge.json","spt-core/crates/spt-live/src/resume.rs","spt-core/crates/spt-daemon/tests/resume.rs","spt-core/crates/spt/tests/resume_template_e2e.rs","spt-core/crates/spt/tests/ready_resume_ledger_e2e.rs","spt-core/docs/adr/0033-wake-resume-via-reconcile-intent.md","spt-core/V0.13.0-P2-SESSION-RESUME-DESIGN.md","spt-core/.claude/agent-memory/spt-test-engineer/p0-pty-input-writer-wedge-tests.md","spt-core/.claude/agent-memory/spt-test-engineer/resume-template-marker-fixture.md","spt-core/.claude/agent-memory/spt-test-engineer/effect-journal-wedge-tests.md","spt-core/target-seam/debug/.fingerprint/spt-40ffac30ce86f2c4/test-integration-test-ready_resume_ledger_e2e.json","spt-core/target-seam/debug/.fingerprint/spt-daemon-9c529ff5c832354f/test-integration-test-resume.json","spt-core/target-seam/debug/.fingerprint/spt-daemon-a646501c1c5624b9/test-integration-test-pumpdeadline.json","spt-core/target-seam/debug/.fingerprint/spt-daemon-ba31b6648cd232d7/test-integration-test-pump.json","spt-core/PUMP-SEAM-PLAN.md"],"truncated":false,"cwd":"C:\\Users\\decid\\Documents\\projects"},"isError":false,"timestamp":1783730507916}}
{"type":"message","id":"365175f4","parentId":"1589101d","timestamp":"2026-07-11T00:41:57.206Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Extracting historical source references**\n\n<!-- -->"},{"type":"toolCall","id":"call_LZMEmH49sQkSAuG10sgxgKJ5|fc_08496348794f7e09016a519155c7c481988249d78333eed4fb","name":"read","arguments":{"i":"Read round three code RCA","path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","selector":"1-260"}},{"type":"toolCall","id":"call_oG6Jgf1hagm10V95ORFI9sha|fc_08496348794f7e09016a519155c7d48198a8d19e940fd8071b","name":"read","arguments":{"i":"Read round three rig verdict","path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","selector":"1-260"}},{"type":"toolCall","id":"call_4vokafKkZyzDRKS2vUtuCFe5|fc_08496348794f7e09016a519155c7dc819888cc3649a33b8fc4","name":"read","arguments":{"i":"Read root cause capture","path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","selector":"1-300"}},{"type":"toolCall","id":"call_YqUcVyqbXp2ecrApFxT63TiG|fc_08496348794f7e09016a519155c7e08198b6dc8b424adcf9aa","name":"read","arguments":{"i":"Read inject full path","path":"spt-core/crates/spt-daemon/src/broker.rs","selector":"340-480,1330-1505,1880-2170"}},{"type":"toolCall","id":"call_poW4XmnaybxEjLZCJeMMXfmf|fc_08496348794f7e09016a519155c7e481989de427e021aa9eef","name":"read","arguments":{"i":"Read pre-r4 broker path","path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/broker.rs","selector":"60-180,470-550,700-850,1300-1510"}},{"type":"toolCall","id":"call_uP0Gm2tHfmAiYOvwCukRTbhp|fc_08496348794f7e09016a519155c7e8819897ad5bcc3cc36a61","name":"read","arguments":{"i":"Read current resume functions","path":"spt-core/crates/spt-daemon/src/brain.rs","selector":"1010-1095,1580-1635"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":35211,"output":373,"cacheRead":79360,"cacheWrite":0,"totalTokens":114944,"reasoningTokens":59,"cost":{"input":0.17605500000000002,"output":0.01119,"cacheRead":0.03968,"cacheWrite":0,"total":0.22692500000000002}},"stopReason":"toolUse","timestamp":1783730507964,"responseId":"resp_08496348794f7e09016a51914cb7d88198ba4fcb74ae0f12e7","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51914ef39c8198aa76b95cdc4bfff8","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFVngA5yduRdsGT8DiFnlz_1cc-7LweDhMx_cCAhkKqacGfoYo9TnodGn9ekX3gfg9JGS_j9dXDkkWtXKNWRrnFn9ihXaK5OUliaDc3_PCeWV0zIrCyqFAKYwkqbBVKCKo8Y5Wv-WpestabLUvsWn0bi8W9Bs6FOMRkKfYGqhoo1vWbMcM0Ha3f0ciy83UbNpKJPBnjq0CfLlU6VBB0kYsv62u-XC0zt2kvNRDeIVGNPTLnMCkMi-6LyFx8vYgdUr4_6iOcRMqMrYZSPEJQbrnnPfWUf7tjJj4F7KnilPZi5uyK8zwRBC6qlwZcH0ipv_AWKFKmOw1NcRRlG_bu_q3JbhMrFrYm1lr_mNuZzL0xeSbs8ymxEhaXg_J3-fkkBTRUktv3lsSMp0Y0yilc-fJ5sY_QTnd5UTXbJ-8pYmpyUuuAf0ir_C3P9qVwaHOmV7Mf45UymbEtN4tRpv43sl1pVXQMSDiJylnt9Yz0SK8EXBJBavKGfn0Q_O0svfNvpKp8A7Oj0qBIgxyWKxsrP8usuRKIbOz0It-vIYkCF-NNIyfdod0QfeV6P8mZ5B5WlOtPAXO6O9WkSjTbcjoMFgsQOTlSOvHHOgADeH6s2fh6x5euS05aib4MMJ2ZqPgGhedYY4dBW8ttZRYqv3_q98l_CXMHEy9CqykEJIoifDE2HcIXA3J8sHGbYEjtLCoNI7FLwwouqEa4YC7rXsTjY3fiAqJ13QkxAglhkf0BjZgSvmlFxtAnLFNFSuZCQnRc05zBUQZLS4WcoJ7C-U21gF-RZQmLGlXxUocobtRypsnRtEpvkzcwlhglyHhbbbf8sORfA5v5a-gF2TXMyFX6sTlRpw2k683MEiSWRyVVpFt0af0PGhiv_CjAGPFa7CaQM89IR16SAqxWZrX7rpJ2CJ50H28tnKxeBcV1S7W6OE7ToKytGOHysTKYKJ8VpmI4zUVWgFXx6iiWWbcDaQbt2NgLJ9T6-J7oqUJXYLrK-wSS24X9lZfC78ODCAHp8HR2HA6JWhHVn1z7E7EHJ3qgbrAAYff93B2DAihCwyQXs1eJLYVlieq-77R4waveZgx_ewq8osAKunvdOWGbSLrHfBw38pHNDsQqMzwX3YdvtHkK3AhLBQspXGTv-1K73KLlfygTxRaJmU8oi3bUBZSDSHqjmPtKBC5mDJWE4upwSiVBPDg_tsWQqF1utAaWi_5nWjo85FGYsh_AhApNNgBDPcH1aA==","summary":[{"type":"summary_text","text":"**Extracting historical source references**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a519155c7c481988249d78333eed4fb","type":"function_call","status":"completed","arguments":"{\"i\":\"Read round three code RCA\",\"path\":\"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md\",\"selector\":\"1-260\"}","call_id":"call_LZMEmH49sQkSAuG10sgxgKJ5","name":"read"},{"id":"fc_08496348794f7e09016a519155c7d48198a8d19e940fd8071b","type":"function_call","status":"completed","arguments":"{\"i\":\"Read round three rig verdict\",\"path\":\"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\",\"selector\":\"1-260\"}","call_id":"call_oG6Jgf1hagm10V95ORFI9sha","name":"read"},{"id":"fc_08496348794f7e09016a519155c7dc819888cc3649a33b8fc4","type":"function_call","status":"completed","arguments":"{\"i\":\"Read root cause capture\",\"path\":\"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md\",\"selector\":\"1-300\"}","call_id":"call_4vokafKkZyzDRKS2vUtuCFe5","name":"read"},{"id":"fc_08496348794f7e09016a519155c7e08198b6dc8b424adcf9aa","type":"function_call","status":"completed","arguments":"{\"i\":\"Read inject full path\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"selector\":\"340-480,1330-1505,1880-2170\"}","call_id":"call_YqUcVyqbXp2ecrApFxT63TiG","name":"read"},{"id":"fc_08496348794f7e09016a519155c7e481989de427e021aa9eef","type":"function_call","status":"completed","arguments":"{\"i\":\"Read pre-r4 broker path\",\"path\":\"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/broker.rs\",\"selector\":\"60-180,470-550,700-850,1300-1510\"}","call_id":"call_poW4XmnaybxEjLZCJeMMXfmf","name":"read"},{"id":"fc_08496348794f7e09016a519155c7e8819897ad5bcc3cc36a61","type":"function_call","status":"completed","arguments":"{\"i\":\"Read current resume functions\",\"path\":\"spt-core/crates/spt-daemon/src/brain.rs\",\"selector\":\"1010-1095,1580-1635\"}","call_id":"call_uP0Gm2tHfmAiYOvwCukRTbhp","name":"read"}]},"duration":9242.07070000004,"ttft":2354.2963999998756,"contextSnapshot":{"promptTokens":114571,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LZMEmH49sQkSAuG10sgxgKJ5|fc_08496348794f7e09016a519155c7c481988249d78333eed4fb","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md"},"intent":"Read round three code RCA"},"id":"9d219176","parentId":"365175f4","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_oG6Jgf1hagm10V95ORFI9sha|fc_08496348794f7e09016a519155c7d48198a8d19e940fd8071b","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md"},"intent":"Read round three rig verdict"},"id":"22d3e70f","parentId":"9d219176","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4vokafKkZyzDRKS2vUtuCFe5|fc_08496348794f7e09016a519155c7dc819888cc3649a33b8fc4","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md"},"intent":"Read root cause capture"},"id":"00f10990","parentId":"22d3e70f","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YqUcVyqbXp2ecrApFxT63TiG|fc_08496348794f7e09016a519155c7e08198b6dc8b424adcf9aa","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs"},"intent":"Read inject full path"},"id":"a3a0fb7e","parentId":"00f10990","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_poW4XmnaybxEjLZCJeMMXfmf|fc_08496348794f7e09016a519155c7e481989de427e021aa9eef","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/broker.rs"},"intent":"Read pre-r4 broker path"},"id":"084f511c","parentId":"a3a0fb7e","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_uP0Gm2tHfmAiYOvwCukRTbhp|fc_08496348794f7e09016a519155c7e8819897ad5bcc3cc36a61","toolName":"read","startedAt":"2026-07-11T00:41:57.207Z","args":{"path":"spt-core/crates/spt-daemon/src/brain.rs"},"intent":"Read current resume functions"},"id":"8c80fbe9","parentId":"084f511c","timestamp":"2026-07-11T00:41:57.207Z"}
{"type":"message","id":"39f006ea","parentId":"8c80fbe9","timestamp":"2026-07-11T00:41:57.248Z","message":{"role":"toolResult","toolCallId":"call_poW4XmnaybxEjLZCJeMMXfmf|fc_08496348794f7e09016a519155c7e481989de427e021aa9eef","toolName":"read","content":[{"type":"text","text":"use crate::msg::{\n…\n    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,\n    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT,\n    KIND_NET_DIAL_SUBMITTED, KIND_NET_PRESENCE_SUBSCRIBE,\n    KIND_NET_SENT,\n    KIND_ADAPTER_APPLY, KIND_APPLIED,\n    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\n    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n    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    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n};\nuse crate::nethost::{NetHost, NET_EFFECT_SESSION};\nuse crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\nuse crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n\n/// The shared, serialized send side of one brain connection. Output frames,\n/// replay frames, command acks, and exit/error events all write through this.\n/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\n/// handle under the same gate discipline. Round-4\n/// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): no longer a raw\n/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n/// matching stays `Arc::ptr_eq`.\npub(crate) type SharedSend = Arc<BrokerConn>;\n\n/// Default bounded output-log depth (chunks). Large enough that a brain restart\n/// window never evicts unreplayed output in practice; the bound is what keeps a\n/// disconnected session from buffering without limit.\nconst DEFAULT_LOG_CHUNKS: usize = 4096;\n\n/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n/// owns the replay batch), so a viewer attaching to a busy session is not\n/// spuriously evicted.\nconst VIEWER_CHANNEL_DEPTH: usize = 256;\n\n/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\nconst MAX_VIEWERS: usize = 32;\n\n/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\n/// the live stream) or a `Disconnected` one (its writer thread ended after a\n/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n/// is the pure decision the fan-out applies under the log lock.\n// [impl->REQ-HAZARD-VIEWER-ISOLATION]\nfn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n    r.is_err()\n}\n\n/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n/// only its own queue — never the drain. When the queue overflows the sink is\n/// dropped (the `tx` closes → the writer ends) and removed from the map.\nstruct ViewerSink {\n    /// The bounded live-frame queue to this viewer's writer thread.\n    tx: SyncSender<Envelope>,\n    /// The viewer's IPC send half — kept ONLY for identity matching on detach\n    /// (a dropped brain connection removes its viewer rows); never written here.\n    send: SharedSend,\n    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n    /// on its `rx.recv()` Err, reads this and — when true — emits a\n    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n    /// the serving brain skips to live instead of tearing the attach down.\n    evicted: Arc<AtomicBool>,\n    /// The writer thread handle (held for ownership; it exits when `tx` drops or\n    /// a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// The controller live-frame channel depth (frames). Deeper than a viewer's\n/// queue because the controller is the AUTHORITATIVE view — unlike a viewer it is\n/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\n/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n/// never trips the deadline.\nconst CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n\n/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n/// [`ControllerSink::last_ok`]) before the drain declares it stalled and detaches it\n/// (W1, refined by b4 / REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE). This\n/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n/// untouched.\nconst CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n\n/// How long a controller's writer thread may sit BLOCKED inside a single socket\n/// write — the brain subscriber suspended / black-holed — before a take/reattach\n/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n/// protects the fan-out against ONE per-client conn falling behind and is\n/// output-driven (the drain observes a Full channel); THIS bound is measured\n/// full-with-**zero writer progress** and covers the case the output-driven path\n/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n/// output, so `append`/`mark_controller_gone` never runs, and the stalled\n/// controller would otherwise latch `driven_by` forever (reattach refused, `--take`\n/// blocked). The brain aggregates every session + psyche + WAN feed, so its drain\n/// is bursty; 15 s tolerates a normal burst-stall while still bounding the wedge.\n/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\n/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nconst BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n\n/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n/// the slot) is provable in ~1 s instead of 15 (mirrors the\n/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n/// ONLY — never relaxes an assertion.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n…\n\nfn note_miss_respool(respooled_once: &mut HashSet<String>, envelope: &str) -> MissRespool {\n    if respooled_once.contains(envelope) {\n        MissRespool::DeadLetter\n    } else {\n        respooled_once.insert(envelope.to_string());\n        MissRespool::Respool\n    }\n}\n\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n/// the `budget` (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Below the budget\n/// the miss is tolerated (binary preserved, worker continues).\nfn miss_strikes_exhausted(strikes: u32, budget: u32) -> bool {\n    strikes >= budget\n}\n\n/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING\n/// socket write so a backed-up controller can never stall the session drain\n/// thread (the v0.12.x wedge: the controller write was inline on the drain,\n/// holding `Mutex<OutputLog>`). Unlike a viewer it is AUTHORITATIVE: its writer\n/// advances `delivered_through` on each successful write, and it is never evicted\n/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\nstruct ControllerSink {\n    /// The bounded live-frame queue to the controller's writer thread.\n    tx: SyncSender<CtrlMsg>,\n    /// The controller's IPC send half — kept for identity matching on detach and\n    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n    send: SharedSend,\n    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n    by: Option<String>,\n    /// Time of the last SUCCESSFUL handoff (`try_send` Ok) to this controller's\n    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\n    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n    last_ok: Instant,\n    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n    /// and back to `None` the instant the write returns. A reader\n    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n    /// state; the mutex is taken only around the brief set/clear and the read, never\n    /// held across the socket write itself.\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n    /// when a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// A bounded, sequenced output log for one session — the gapless-resume buffer\n/// AND the W2.5 controller/viewer fan-out hub (REQ-RCVIEW-1).\n///\n/// The session's single drain thread is the only producer ([`OutputLog::append`]).\n/// Output fans out to ONE interactive `controller` (the authoritative,\n/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n/// an isolated bounded queue + writer thread, evicted on overflow). Every\n/// controller send and viewer `try_send` happens while the enclosing\n/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\nstruct OutputLog {\n    session_id: u64,\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    /// The single interactive controller's isolated sink (W1: a dedicated writer\n    /// thread + bounded channel + authoritative cursor). `None` ⇒ undriven. The\n    /// controller's identity (`Some(node)` remote / `None` local) lives in\n    /// [`ControllerSink::by`] and keys ownership — a same-`by` re-subscribe\n    /// silently re-takes (no `Displaced`, the gate-#7 self-kick guard); only a\n    /// DIFFERENT remote's `Take` displaces.\n    controller: Option<ControllerSink>,\n…\n}\n…\nimpl OutputLog {\n…\n    /// --attach` case #6 is about (attach to an already-producing TUI). A\n    /// resume-from-floor re-fetch (`from_seq > 0`, a controller recovering a b4\n    /// drop-don't-block gap) is deliberately NOT repainted: it keeps the raw-ring\n    /// exactly-once replay + the strict reject-gap + the irrecoverable-behind\n    /// detection untouched (REQ-HAZARD-CONTROLLER-GAP-RESUME / B2). `None` also when\n    /// nothing has been produced yet (`next_seq == 0`) → an empty initial batch, so\n    /// the first live frame is the contiguous seq 0 (the spawn seat, unchanged).\n    fn repaint_initial(&self, from_seq: u64) -> Option<(u64, Vec<u8>)> {\n        if from_seq != 0 || self.next_seq == 0 {\n            return None;\n        }\n        Some((self.next_seq - 1, self.grid.render_repaint()))\n    }\n\n    /// Append a freshly-produced output chunk: assign the next `seq`, retain it in\n    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n    /// log lock; `None` otherwise.\n    ///\n    /// b4 (REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE): W1 moved the\n    /// controller SOCKET WRITE off the drain (the `controller_writer` thread) but\n    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n    /// exactly like a viewer: the drain runs at full PTY speed regardless of\n    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n    /// (bounded wedge preserved). The writer still does the blocking socket write +\n    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n    /// single drain thread.\n    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n    // [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n    #[must_use]\n    /// Coherence clamp for a poison-RECOVERED ring (B-1, doyle ruling; run only by\n    /// [`recover_log`] on the recovery path). A panic mid-[`append`] can leave the\n    /// ring torn — an over-cap ring (interrupted prune), a last seq not below\n    /// `next_seq`, or a non-monotonic front/back — and serving those bytes blind\n    /// risks garbage to a subscriber. Cheap-check the ring invariants (counts within\n    /// cap, indices ordered and below `next_seq`); on ANY violation RESET the ring\n    /// EMPTY (+ loud log) — `next_seq` is left intact so cursors never rewind, and the\n    /// lost scrollback self-heals on the next PTY output + repaint. Returns `true`\n    /// iff it reset. A coherent ring (the common case — most panics fire OUTSIDE the\n    /// ring mutation, poisoning the lock while the buffer stays intact) is untouched.\n    // [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n    fn clamp_or_reset(&mut self) -> bool {\n        let within_cap = self.ring.len() <= self.cap_chunks;\n        let last_below_next = match self.ring.back() {\n            Some(&(last, _)) => last < self.next_seq,\n            None => true,\n        };\n        let ordered = match (self.ring.front(), self.ring.back()) {\n            (Some(&(front, _)), Some(&(back, _))) => front <= back,\n            _ => true,\n        };\n        if within_cap && last_below_next && ordered {\n            return false;\n        }\n        let had = self.ring.len();\n        self.ring.clear();\n        eprintln!(\n            \"broker: session {} output ring was torn by a poison-recover \\\n             (had {had} chunks, next_seq {}); reset to empty — scrollback self-heals \\\n             on the next output [REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\",\n            self.session_id, self.next_seq\n        );\n        true\n    }\n\n    /// The current high-water sequence (the next seq to be appended) — the snapshot\n    /// point for the inject leg's settle-gate + echo-verify (REQ-INJECT-MULTILINE-\n    /// INTEGRITY). A rising high-water since a baseline == the PTY produced output.\n    fn high_water(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// Concatenate ring bytes at or after `from_seq` — the output PRODUCED since a\n    /// snapshot, used to confirm a settle probe echoed (readiness, Layer 1) and that\n    /// a typed payload's head echoed back (integrity, Layer 2). Best-effort: bytes\n    /// pruned out of the bounded ring are simply absent (the echo we look for is\n    /// recent, well within cap).\n    fn bytes_since(&self, from_seq: u64) -> Vec<u8> {\n        let mut out = Vec::new();\n        for (seq, chunk) in self.ring.iter() {\n            if *seq >= from_seq {\n                out.extend_from_slice(chunk);\n            }\n        }\n        out\n    }\n\n    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        while self.ring.len() > self.cap_chunks {\n            self.ring.pop_front();\n        }\n        let frame = output_envelope(self.session_id, seq, bytes);\n        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n        if !self.viewers.is_empty() {\n            let mut evicted: Vec<u64> = Vec::new();\n            for (vid, sink) in self.viewers.iter() {\n                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n                    evicted.push(*vid);\n                }\n            }\n            if !evicted.is_empty() {\n                for vid in evicted {\n                    // Mark eviction BEFORE dropping the sink: the writer's\n                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n                    if let Some(sink) = self.viewers.remove(&vid) {\n                        sink.evicted.store(true, Ordering::Release);\n                    }\n                }\n                self.stamp_viewer_count();\n            }\n        }\n        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n        // slow controller falls behind the ring) unless it has been Full for the\n        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n        // → EVICT. The drain off-lock turns `Some(epoch)` into `mark_controller_gone`.\n        let now = Instant::now();\n        let epoch = self.controller_epoch.load(Ordering::Acquire);\n        let result = if let Some(c) = self.controller.as_mut() {\n            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n                Ok(()) => {\n                    c.last_ok = now;\n                    None\n                }\n                Err(TrySendError::Full(_)) => {\n                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n                        Some(epoch) // stalled past the deadline → bounded detach\n                    } else {\n                        None // drop this chunk; controller falls behind the ring\n                    }\n                }\n                Err(TrySendError::Disconnected(_)) => Some(epoch),\n            }\n        } else {\n            None\n        };\n        // Advance the render grid AFTER the fan-out — it must never sit between the\n        // drain and the controller/viewer `try_send` (REQ-HAZARD-INJECT-CONTROL-COEXIST\n…\n    }\n…\n    fn stamp_reaped(&self) {\n        if self.endpoint.is_empty() {\n            return;\n        }\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\n        let _ = spt_store::info::set_driven_by(&perch, None);\n        let _ = spt_store::info::set_controlled(&perch, false);\n        let _ = spt_store::info::set_viewer_count(&perch, 0);\n    }\n\n    /// Whether `sub` is the current controller (the resize-exclusivity check).\n    fn is_controller(&self, sub: &SharedSend) -> bool {\n        self.controller\n            .as_ref()\n            .is_some_and(|c| Arc::ptr_eq(&c.send, sub))\n    }\n\n    /// The next seq a re-attaching controller still needs (the persisted resume\n    /// cursor surfaced in `KIND_SESSIONS` → `resume_seq`). W1: read from the\n    /// atomic the controller writer thread advances.\n    fn delivered_through(&self) -> u64 {\n        self.delivered_through.load(Ordering::Acquire)\n    }\n}\n\n/// A viewer's dedicated writer thread (REQ-HAZARD-VIEWER-ISOLATION): write the\n/// `initial` batch (size frame + ring replay) once, then drain live frames from\n/// `rx` with a BLOCKING socket write each. A failed write (dead viewer) ends the\n/// thread; the drain thread's next `try_send` then sees `Disconnected` and\n/// evicts the row. The drain thread itself never blocks on this socket.\nfn viewer_writer(\n    send: SharedSend,\n    session_id: u64,\n    initial: Vec<Envelope>,\n    rx: Receiver<Envelope>,\n    evicted: Arc<AtomicBool>,\n) {\n    for frame in &initial {\n        // Bounded, cancelable conn write (round-4): a viewer that stops\n        // reading poisons + retires ITS conn at the deadline instead of\n        // parking this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    while let Ok(frame) = rx.recv() {\n        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    // `tx` dropped. If this was an EVICTION for overflow (append set the flag\n    // before dropping the sink), emit a KIND_VIEWER_EVICTED marker — DISTINCT from\n    // a session-exit EOF — so the serving brain skips to live (re-subscribes from\n    // the ring floor) instead of dying silently. A NORMAL teardown (session end /\n    // conn drop) leaves the flag false → no marker, exit quietly. This write is in\n    // the dedicated writer thread, OFF the log lock / off the drain (the W1/b4\n    // isolation is preserved). REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT.\n    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n    if evicted.load(Ordering::Acquire) {\n        let marker = evicted_envelope(session_id);\n        let _ = send.write(&marker);\n    }\n}\n\n/// Round-4 observability bridge: a controller writer whose bounded conn write\n/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n/// write, or a gate-wait behind one exhausted the bound) IS the\n/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n/// wedge aged until a logical sampler (`stall_evict_controller`) observed it;\n/// the physical bound now fires FIRST, so the tally is recorded here — same\n/// event class, same `daemon status` surface. An ORGANIC write failure (peer\n/// death, any non-TimedOut error) stays uncounted, exactly as before.\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn note_controller_write_retired(sid: u64, e: &io::Error) {\n    if e.kind() != io::ErrorKind::TimedOut {\n        return;\n    }\n    eprintln!(\n        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} — controller conn write exceeded its \\\n         bound; physical conn poisoned + retired (writer released, EOF cleanup \\\n         follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\"\n    );\n    record_stall_evict();\n}\n\n/// A message to the CONTROLLER writer thread (W1). `Output` carries a sequenced\n/// chunk and advances the authoritative `delivered_through` on a successful\n/// write; `Control` is an out-of-band notice (Displaced, …) that is written but\n/// does NOT move the resume cursor.\nenum CtrlMsg {\n    Output(u64, Envelope),\n    Control(Envelope),\n}\n\n/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically\n/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer\n/// overlap during a controller displace.\nfn advance_delivered(dt: &AtomicU64, seq: u64) {\n    let mut cur = dt.load(Ordering::Acquire);\n    loop {\n        let next = advanced_cursor(cur, seq, true);\n        if next == cur {\n            return;\n        }\n        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {\n            Ok(_) => return,\n            Err(actual) => cur = actual,\n        }\n    }\n}\n\n/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to\n/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap\n/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,\n/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching\n/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the\n/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An\n/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,\n/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every\n/// live frame is contiguous) this is identical to [`advance_delivered`].\n// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\nfn contiguous_advance(dt: &AtomicU64, seq: u64) {\n    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);\n}\n\n/// The interactive CONTROLLER's dedicated writer thread (W1 /\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of\n/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume\n/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket\n/// write each — so a backed-up controller blocks ONLY this thread, never the\n/// session drain (which used to write the controller inline under the log lock).\n/// A failed write (dead operator) ends the thread; the drain's next bounded\n/// send then sees `Disconnected` and detaches the row. `Output` advances\n/// `delivered_through`; `Control` (Displaced/…) does not.\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n#[allow(clippy::too_many_arguments)] // per-writer thread-handoff channels (W2 added the blocked-since signal); a struct would obscure the call\nfn controller_writer(\n    sid: u64,\n    send: SharedSend,\n    initial: Vec<(u64, Vec<u8>)>,\n    rx: Receiver<CtrlMsg>,\n    delivered_through: Arc<AtomicU64>,\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate closure runs AFTER the conn's serialized write\n        // gate is acquired and BEFORE any byte is written — the same\n        // serialization point the old epoch-check-under-`send.lock()` had: both\n        // writers serialize on the one conn gate, and `become_controller` bumps\n        // the epoch BEFORE spawning the new writer, so whichever superseded\n        // writer next holds the gate observes the bump and bails before\n        // committing — no check-then-block-then-write window. (A frame the\n        // stale writer committed BEFORE the bump is irreducible at the broker;\n        // fix #1 removes the contradictory double-take that produced it.)\n        // Round-4: the write itself is bounded + cancelable — a non-draining\n        // consumer poisons + retires the physical conn at the deadline instead\n        // of wedging this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        mark_blocked(true);\n        let outcome =\n            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n        mark_blocked(false);\n        match outcome {\n            Ok(ConnWrite::Done) => {}\n            Ok(ConnWrite::Superseded) => return,\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n                return;\n            }\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        mark_blocked(true);\n        let res = send.write(frame);\n        mark_blocked(false);\n        match res {\n            Ok(_) => {}\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n…\n            }\n        }\n…\n    }\n}"}],"details":{"displayContent":{"text":"use crate::msg::{\n…\n    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,\n    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_DIAL_SUBMIT,\n    KIND_NET_DIAL_SUBMITTED, KIND_NET_PRESENCE_SUBSCRIBE,\n    KIND_NET_SENT,\n    KIND_ADAPTER_APPLY, KIND_APPLIED,\n    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\n    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n    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    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n};\nuse crate::nethost::{NetHost, NET_EFFECT_SESSION};\nuse crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\nuse crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n\n/// The shared, serialized send side of one brain connection. Output frames,\n/// replay frames, command acks, and exit/error events all write through this.\n/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\n/// handle under the same gate discipline. Round-4\n/// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): no longer a raw\n/// `Arc<Mutex<SendHalf>>` — [`BrokerConn`] owns the half and every write is\n/// bounded + cancelable + poison-on-failure (see [`crate::conn`]). Identity\n/// matching stays `Arc::ptr_eq`.\npub(crate) type SharedSend = Arc<BrokerConn>;\n\n/// Default bounded output-log depth (chunks). Large enough that a brain restart\n/// window never evicts unreplayed output in practice; the bound is what keeps a\n/// disconnected session from buffering without limit.\nconst DEFAULT_LOG_CHUNKS: usize = 4096;\n\n/// The viewer-channel depth (frames). A bounded SPSC queue per viewer; when it\n/// OVERFLOWS the viewer is evicted — the drain `try_send`s and never blocks\n/// (REQ-HAZARD-VIEWER-ISOLATION). Generous enough to absorb a normal render\n/// hiccup; a viewer that falls this far behind on the LIVE stream is genuinely\n/// not keeping up. Replay-at-attach does NOT ride this bound (the writer thread\n/// owns the replay batch), so a viewer attaching to a busy session is not\n/// spuriously evicted.\nconst VIEWER_CHANNEL_DEPTH: usize = 256;\n\n/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\nconst MAX_VIEWERS: usize = 32;\n\n/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\n/// the live stream) or a `Disconnected` one (its writer thread ended after a\n/// dead socket). The drain evicts on either and NEVER blocks on a viewer — this\n/// is the pure decision the fan-out applies under the log lock.\n// [impl->REQ-HAZARD-VIEWER-ISOLATION]\nfn viewer_send_evicts<T>(r: &Result<(), TrySendError<T>>) -> bool {\n    r.is_err()\n}\n\n/// One read-only viewer's isolated sink (REQ-HAZARD-VIEWER-ISOLATION). The drain\n/// thread `try_send`s output frames into `tx` (a bounded queue); a dedicated\n/// `writer` thread does the BLOCKING socket write, so a wedged viewer backs up\n/// only its own queue — never the drain. When the queue overflows the sink is\n/// dropped (the `tx` closes → the writer ends) and removed from the map.\nstruct ViewerSink {\n    /// The bounded live-frame queue to this viewer's writer thread.\n    tx: SyncSender<Envelope>,\n    /// The viewer's IPC send half — kept ONLY for identity matching on detach\n    /// (a dropped brain connection removes its viewer rows); never written here.\n    send: SharedSend,\n    /// Set TRUE when this viewer is EVICTED for overflow (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT),\n    /// distinguishing an eviction from a normal teardown (session end / conn drop).\n    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n    /// on its `rx.recv()` Err, reads this and — when true — emits a\n    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n    /// the serving brain skips to live instead of tearing the attach down.\n    evicted: Arc<AtomicBool>,\n    /// The writer thread handle (held for ownership; it exits when `tx` drops or\n    /// a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// The controller live-frame channel depth (frames). Deeper than a viewer's\n/// queue because the controller is the AUTHORITATIVE view — unlike a viewer it is\n/// NOT evicted on a SINGLE full `try_send` (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// b4: the drain does ONE non-blocking `try_send` per chunk and NEVER blocks; a\n/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n/// never trips the deadline.\nconst CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n\n/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n/// [`ControllerSink::last_ok`]) before the drain declares it stalled and detaches it\n/// (W1, refined by b4 / REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE). This\n/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n/// untouched.\nconst CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n\n/// How long a controller's writer thread may sit BLOCKED inside a single socket\n/// write — the brain subscriber suspended / black-holed — before a take/reattach\n/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n/// protects the fan-out against ONE per-client conn falling behind and is\n/// output-driven (the drain observes a Full channel); THIS bound is measured\n/// full-with-**zero writer progress** and covers the case the output-driven path\n/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n/// output, so `append`/`mark_controller_gone` never runs, and the stalled\n/// controller would otherwise latch `driven_by` forever (reattach refused, `--take`\n/// blocked). The brain aggregates every session + psyche + WAN feed, so its drain\n/// is bursty; 15 s tolerates a normal burst-stall while still bounding the wedge.\n/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\n/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nconst BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n\n/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n/// the slot) is provable in ~1 s instead of 15 (mirrors the\n/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n/// ONLY — never relaxes an assertion.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n…\n\nfn note_miss_respool(respooled_once: &mut HashSet<String>, envelope: &str) -> MissRespool {\n    if respooled_once.contains(envelope) {\n        MissRespool::DeadLetter\n    } else {\n        respooled_once.insert(envelope.to_string());\n        MissRespool::Respool\n    }\n}\n\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n/// the `budget` (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Below the budget\n/// the miss is tolerated (binary preserved, worker continues).\nfn miss_strikes_exhausted(strikes: u32, budget: u32) -> bool {\n    strikes >= budget\n}\n\n/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).\n/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING\n/// socket write so a backed-up controller can never stall the session drain\n/// thread (the v0.12.x wedge: the controller write was inline on the drain,\n/// holding `Mutex<OutputLog>`). Unlike a viewer it is AUTHORITATIVE: its writer\n/// advances `delivered_through` on each successful write, and it is never evicted\n/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\nstruct ControllerSink {\n    /// The bounded live-frame queue to the controller's writer thread.\n    tx: SyncSender<CtrlMsg>,\n    /// The controller's IPC send half — kept for identity matching on detach and\n    /// for the `exit`-frame fan-out ([`OutputLog::all_sinks`]).\n    send: SharedSend,\n    /// The controller's identity: `Some(node)` = a remote operator, `None` =\n    /// local (the brain's spawn-reader / resume). Ownership keys on this.\n    by: Option<String>,\n    /// Time of the last SUCCESSFUL handoff (`try_send` Ok) to this controller's\n    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\n    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n    last_ok: Instant,\n    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n    /// and back to `None` the instant the write returns. A reader\n    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n    /// state; the mutex is taken only around the brief set/clear and the read, never\n    /// held across the socket write itself.\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n    /// The writer thread handle (held for ownership; exits when `tx` drops — the\n    /// drain's bounded `send_timeout` then sees `Disconnected` and detaches — or\n    /// when a socket write fails).\n    _writer: JoinHandle<()>,\n}\n\n/// A bounded, sequenced output log for one session — the gapless-resume buffer\n/// AND the W2.5 controller/viewer fan-out hub (REQ-RCVIEW-1).\n///\n/// The session's single drain thread is the only producer ([`OutputLog::append`]).\n/// Output fans out to ONE interactive `controller` (the authoritative,\n/// cursor-advancing, blocking path) plus ANY NUMBER of read-only `viewers` (each\n/// an isolated bounded queue + writer thread, evicted on overflow). Every\n/// controller send and viewer `try_send` happens while the enclosing\n/// `Mutex<OutputLog>` is held, so frames reach each sink in strict `seq` order.\nstruct OutputLog {\n    session_id: u64,\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    /// The single interactive controller's isolated sink (W1: a dedicated writer\n    /// thread + bounded channel + authoritative cursor). `None` ⇒ undriven. The\n    /// controller's identity (`Some(node)` remote / `None` local) lives in\n    /// [`ControllerSink::by`] and keys ownership — a same-`by` re-subscribe\n    /// silently re-takes (no `Displaced`, the gate-#7 self-kick guard); only a\n    /// DIFFERENT remote's `Take` displaces.\n    controller: Option<ControllerSink>,\n…\n}\n…\nimpl OutputLog {\n…\n    /// --attach` case #6 is about (attach to an already-producing TUI). A\n    /// resume-from-floor re-fetch (`from_seq > 0`, a controller recovering a b4\n    /// drop-don't-block gap) is deliberately NOT repainted: it keeps the raw-ring\n    /// exactly-once replay + the strict reject-gap + the irrecoverable-behind\n    /// detection untouched (REQ-HAZARD-CONTROLLER-GAP-RESUME / B2). `None` also when\n    /// nothing has been produced yet (`next_seq == 0`) → an empty initial batch, so\n    /// the first live frame is the contiguous seq 0 (the spawn seat, unchanged).\n    fn repaint_initial(&self, from_seq: u64) -> Option<(u64, Vec<u8>)> {\n        if from_seq != 0 || self.next_seq == 0 {\n            return None;\n        }\n        Some((self.next_seq - 1, self.grid.render_repaint()))\n    }\n\n    /// Append a freshly-produced output chunk: assign the next `seq`, retain it in\n    /// the bounded ring, then FAN OUT to viewers AND the controller — both a SINGLE\n    /// non-blocking `try_send`, so the drain NEVER blocks/sleeps here. Returns\n    /// `Some(epoch)` when the controller must be EVICTED (stalled past the deadline,\n    /// or its writer is gone) so the drain can `mark_controller_gone(epoch)` OFF the\n    /// log lock; `None` otherwise.\n    ///\n    /// b4 (REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE): W1 moved the\n    /// controller SOCKET WRITE off the drain (the `controller_writer` thread) but\n    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n    /// exactly like a viewer: the drain runs at full PTY speed regardless of\n    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n    /// (bounded wedge preserved). The writer still does the blocking socket write +\n    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n    /// single drain thread.\n    // [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n    // [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n    #[must_use]\n    /// Coherence clamp for a poison-RECOVERED ring (B-1, doyle ruling; run only by\n    /// [`recover_log`] on the recovery path). A panic mid-[`append`] can leave the\n    /// ring torn — an over-cap ring (interrupted prune), a last seq not below\n    /// `next_seq`, or a non-monotonic front/back — and serving those bytes blind\n    /// risks garbage to a subscriber. Cheap-check the ring invariants (counts within\n    /// cap, indices ordered and below `next_seq`); on ANY violation RESET the ring\n    /// EMPTY (+ loud log) — `next_seq` is left intact so cursors never rewind, and the\n    /// lost scrollback self-heals on the next PTY output + repaint. Returns `true`\n    /// iff it reset. A coherent ring (the common case — most panics fire OUTSIDE the\n    /// ring mutation, poisoning the lock while the buffer stays intact) is untouched.\n    // [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n    fn clamp_or_reset(&mut self) -> bool {\n        let within_cap = self.ring.len() <= self.cap_chunks;\n        let last_below_next = match self.ring.back() {\n            Some(&(last, _)) => last < self.next_seq,\n            None => true,\n        };\n        let ordered = match (self.ring.front(), self.ring.back()) {\n            (Some(&(front, _)), Some(&(back, _))) => front <= back,\n            _ => true,\n        };\n        if within_cap && last_below_next && ordered {\n            return false;\n        }\n        let had = self.ring.len();\n        self.ring.clear();\n        eprintln!(\n            \"broker: session {} output ring was torn by a poison-recover \\\n             (had {had} chunks, next_seq {}); reset to empty — scrollback self-heals \\\n             on the next output [REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\",\n            self.session_id, self.next_seq\n        );\n        true\n    }\n\n    /// The current high-water sequence (the next seq to be appended) — the snapshot\n    /// point for the inject leg's settle-gate + echo-verify (REQ-INJECT-MULTILINE-\n    /// INTEGRITY). A rising high-water since a baseline == the PTY produced output.\n    fn high_water(&self) -> u64 {\n        self.next_seq\n    }\n\n    /// Concatenate ring bytes at or after `from_seq` — the output PRODUCED since a\n    /// snapshot, used to confirm a settle probe echoed (readiness, Layer 1) and that\n    /// a typed payload's head echoed back (integrity, Layer 2). Best-effort: bytes\n    /// pruned out of the bounded ring are simply absent (the echo we look for is\n    /// recent, well within cap).\n    fn bytes_since(&self, from_seq: u64) -> Vec<u8> {\n        let mut out = Vec::new();\n        for (seq, chunk) in self.ring.iter() {\n            if *seq >= from_seq {\n                out.extend_from_slice(chunk);\n            }\n        }\n        out\n    }\n\n    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        while self.ring.len() > self.cap_chunks {\n            self.ring.pop_front();\n        }\n        let frame = output_envelope(self.session_id, seq, bytes);\n        // Viewers: non-blocking fan-out; a full / disconnected queue = eviction.\n        if !self.viewers.is_empty() {\n            let mut evicted: Vec<u64> = Vec::new();\n            for (vid, sink) in self.viewers.iter() {\n                if viewer_send_evicts(&sink.tx.try_send(frame.clone())) {\n                    evicted.push(*vid);\n                }\n            }\n            if !evicted.is_empty() {\n                for vid in evicted {\n                    // Mark eviction BEFORE dropping the sink: the writer's\n                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n                    if let Some(sink) = self.viewers.remove(&vid) {\n                        sink.evicted.store(true, Ordering::Release);\n                    }\n                }\n                self.stamp_viewer_count();\n            }\n        }\n        // Controller: the SAME non-blocking discipline as a viewer (b4). One\n        // `try_send`, never a sleep. Ok → record the handoff time. Full → DROP (a\n        // slow controller falls behind the ring) unless it has been Full for the\n        // whole deadline since its last success → EVICT. Disconnected (writer gone)\n        // → EVICT. The drain off-lock turns `Some(epoch)` into `mark_controller_gone`.\n        let now = Instant::now();\n        let epoch = self.controller_epoch.load(Ordering::Acquire);\n        let result = if let Some(c) = self.controller.as_mut() {\n            match c.tx.try_send(CtrlMsg::Output(seq, frame)) {\n                Ok(()) => {\n                    c.last_ok = now;\n                    None\n                }\n                Err(TrySendError::Full(_)) => {\n                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n                        Some(epoch) // stalled past the deadline → bounded detach\n                    } else {\n                        None // drop this chunk; controller falls behind the ring\n                    }\n                }\n                Err(TrySendError::Disconnected(_)) => Some(epoch),\n            }\n        } else {\n            None\n        };\n        // Advance the render grid AFTER the fan-out — it must never sit between the\n        // drain and the controller/viewer `try_send` (REQ-HAZARD-INJECT-CONTROL-COEXIST\n…\n    }\n…\n    fn stamp_reaped(&self) {\n        if self.endpoint.is_empty() {\n            return;\n        }\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\n        let _ = spt_store::info::set_driven_by(&perch, None);\n        let _ = spt_store::info::set_controlled(&perch, false);\n        let _ = spt_store::info::set_viewer_count(&perch, 0);\n    }\n\n    /// Whether `sub` is the current controller (the resize-exclusivity check).\n    fn is_controller(&self, sub: &SharedSend) -> bool {\n        self.controller\n            .as_ref()\n            .is_some_and(|c| Arc::ptr_eq(&c.send, sub))\n    }\n\n    /// The next seq a re-attaching controller still needs (the persisted resume\n    /// cursor surfaced in `KIND_SESSIONS` → `resume_seq`). W1: read from the\n    /// atomic the controller writer thread advances.\n    fn delivered_through(&self) -> u64 {\n        self.delivered_through.load(Ordering::Acquire)\n    }\n}\n\n/// A viewer's dedicated writer thread (REQ-HAZARD-VIEWER-ISOLATION): write the\n/// `initial` batch (size frame + ring replay) once, then drain live frames from\n/// `rx` with a BLOCKING socket write each. A failed write (dead viewer) ends the\n/// thread; the drain thread's next `try_send` then sees `Disconnected` and\n/// evicts the row. The drain thread itself never blocks on this socket.\nfn viewer_writer(\n    send: SharedSend,\n    session_id: u64,\n    initial: Vec<Envelope>,\n    rx: Receiver<Envelope>,\n    evicted: Arc<AtomicBool>,\n) {\n    for frame in &initial {\n        // Bounded, cancelable conn write (round-4): a viewer that stops\n        // reading poisons + retires ITS conn at the deadline instead of\n        // parking this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    while let Ok(frame) = rx.recv() {\n        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    // `tx` dropped. If this was an EVICTION for overflow (append set the flag\n    // before dropping the sink), emit a KIND_VIEWER_EVICTED marker — DISTINCT from\n    // a session-exit EOF — so the serving brain skips to live (re-subscribes from\n    // the ring floor) instead of dying silently. A NORMAL teardown (session end /\n    // conn drop) leaves the flag false → no marker, exit quietly. This write is in\n    // the dedicated writer thread, OFF the log lock / off the drain (the W1/b4\n    // isolation is preserved). REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT.\n    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n    if evicted.load(Ordering::Acquire) {\n        let marker = evicted_envelope(session_id);\n        let _ = send.write(&marker);\n    }\n}\n\n/// Round-4 observability bridge: a controller writer whose bounded conn write\n/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n/// write, or a gate-wait behind one exhausted the bound) IS the\n/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n/// wedge aged until a logical sampler (`stall_evict_controller`) observed it;\n/// the physical bound now fires FIRST, so the tally is recorded here — same\n/// event class, same `daemon status` surface. An ORGANIC write failure (peer\n/// death, any non-TimedOut error) stays uncounted, exactly as before.\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn note_controller_write_retired(sid: u64, e: &io::Error) {\n    if e.kind() != io::ErrorKind::TimedOut {\n        return;\n    }\n    eprintln!(\n        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} — controller conn write exceeded its \\\n         bound; physical conn poisoned + retired (writer released, EOF cleanup \\\n         follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\"\n    );\n    record_stall_evict();\n}\n\n/// A message to the CONTROLLER writer thread (W1). `Output` carries a sequenced\n/// chunk and advances the authoritative `delivered_through` on a successful\n/// write; `Control` is an out-of-band notice (Displaced, …) that is written but\n/// does NOT move the resume cursor.\nenum CtrlMsg {\n    Output(u64, Envelope),\n    Control(Envelope),\n}\n\n/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically\n/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer\n/// overlap during a controller displace.\nfn advance_delivered(dt: &AtomicU64, seq: u64) {\n    let mut cur = dt.load(Ordering::Acquire);\n    loop {\n        let next = advanced_cursor(cur, seq, true);\n        if next == cur {\n            return;\n        }\n        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {\n            Ok(_) => return,\n            Err(actual) => cur = actual,\n        }\n    }\n}\n\n/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to\n/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap\n/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,\n/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching\n/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the\n/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An\n/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,\n/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every\n/// live frame is contiguous) this is identical to [`advance_delivered`].\n// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\nfn contiguous_advance(dt: &AtomicU64, seq: u64) {\n    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);\n}\n\n/// The interactive CONTROLLER's dedicated writer thread (W1 /\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of\n/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume\n/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket\n/// write each — so a backed-up controller blocks ONLY this thread, never the\n/// session drain (which used to write the controller inline under the log lock).\n/// A failed write (dead operator) ends the thread; the drain's next bounded\n/// send then sees `Disconnected` and detaches the row. `Output` advances\n/// `delivered_through`; `Control` (Displaced/…) does not.\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n#[allow(clippy::too_many_arguments)] // per-writer thread-handoff channels (W2 added the blocked-since signal); a struct would obscure the call\nfn controller_writer(\n    sid: u64,\n    send: SharedSend,\n    initial: Vec<(u64, Vec<u8>)>,\n    rx: Receiver<CtrlMsg>,\n    delivered_through: Arc<AtomicU64>,\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate closure runs AFTER the conn's serialized write\n        // gate is acquired and BEFORE any byte is written — the same\n        // serialization point the old epoch-check-under-`send.lock()` had: both\n        // writers serialize on the one conn gate, and `become_controller` bumps\n        // the epoch BEFORE spawning the new writer, so whichever superseded\n        // writer next holds the gate observes the bump and bails before\n        // committing — no check-then-block-then-write window. (A frame the\n        // stale writer committed BEFORE the bump is irreducible at the broker;\n        // fix #1 removes the contradictory double-take that produced it.)\n        // Round-4: the write itself is bounded + cancelable — a non-draining\n        // consumer poisons + retires the physical conn at the deadline instead\n        // of wedging this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        mark_blocked(true);\n        let outcome =\n            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n        mark_blocked(false);\n        match outcome {\n            Ok(ConnWrite::Done) => {}\n            Ok(ConnWrite::Superseded) => return,\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n                return;\n            }\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        mark_blocked(true);\n        let res = send.write(frame);\n        mark_blocked(false);\n        match res {\n            Ok(_) => {}\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n…\n            }\n        }\n…\n    }\n}","startLine":53,"lineNumbers":[53,null,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,null,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,null,601,null,678,null,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,null,855,null,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,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,1509,1510,null,1512,1513,null,1522,1523]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\gate-w4\\crates\\spt-daemon\\src\\broker.rs"}}},"isError":false,"timestamp":1783730517248}}
{"type":"message","id":"78d16915","parentId":"39f006ea","timestamp":"2026-07-11T00:41:57.305Z","message":{"role":"toolResult","toolCallId":"call_uP0Gm2tHfmAiYOvwCukRTbhp|fc_08496348794f7e09016a519155c7e8819897ad5bcc3cc36a61","toolName":"read","content":[{"type":"text","text":"    /// all session continuity by querying the persistent side, never a brain→brain\n    /// message. Returns the ids re-attached (empty when the broker hosts none —\n    /// the supervised daemon brain's no-op-today case).\n    ///\n    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,\n    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send\n    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is\n    /// at-least-once; input/effects stay exactly-once via the broker-owned\n    /// `EffectJournal`. The generation is **not** touched — this is a cold start\n    /// (generation custody is the broker's, ADR-0018 V2), not a `handoff`.\n    ///\n    /// Each session is re-attached as a **VIEWER**, never a controller: a caller that\n    /// DRAINS the resumed output (`Brain::read_event`) gets output-continuity from its\n    /// cursor; a viewer never seizes/stall-evicts a controller slot (UPDATE-WEDGE-2,\n    /// REQ-BRAIN-RESUME-NO-CONTROL-STEAL). Genuinely daemon-brain-driven sessions (the\n    /// live-agent adapter future) will re-attach as Control on a drained carrier; that\n    /// set is empty today.\n    ///\n    /// ⚠️ DEADLOCK WARNING — the CALLER MUST DRAIN this conn (UPDATE-WEDGE round 3,\n    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK). This subscribes the sessions onto THIS conn.\n    /// A NON-DRAINING consumer will SELF-DEADLOCK: an actively-streaming session backs\n    /// the conn up, the broker-side subscriber writer blocks holding the conn's single\n    /// `SharedSend` mutex ACROSS a blocking `write_frame` (broker.rs\n    /// `viewer_writer`/`controller_writer`), the dispatch reply path (`send_frame`) needs\n    /// that same lock, so any request/reply the non-draining consumer issues is starved\n    /// (dead-peer-independent — the v0.30.4 field re-wedge). The supervised daemon brain\n    /// is exactly such a non-draining consumer (its `run_brain` heartbeat reads this conn\n    /// only in `net_status()`/`sessions()` drain-and-discard loops; it drives no PTY\n    /// session — brainproc.rs:184 — and nothing on its side consumes the output, §3\n    /// guard), so it MUST call [`Self::resume_session_cursors`] (cursor-only, NO\n    /// subscribe) instead. This subscribing method stays for a DRAINING caller (the\n    /// resume int tests today; the forward live-agent-adapter / Option-B drained carrier).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    // [impl->REQ-DAEMON-2]\n    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    // [doc->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {\n        let reply = self.sessions()?;\n        let mut resumed = Vec::with_capacity(reply.sessions.len());\n        for info in reply.sessions {\n            // Seed the dedup cursor at the broker's resume point BEFORE the subscribe\n            // replay starts arriving, then re-attach (Viewer) from there.\n            self.session_cursors\n                .insert(info.session_id, info.resume_seq);\n            // [impl->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n            self.subscribe_with(info.session_id, info.resume_seq, AttachIntent::Viewer, None)?;\n            resumed.push(info.session_id);\n        }\n        Ok(resumed)\n    }\n\n    /// Cursor-only resume for a NON-DRAINING caller (the supervised daemon brain): query\n    /// every hosted session and seed [`Self::session_cursors`] at each `resume_seq`,\n    /// returning the re-attached ids — but DO **NOT** subscribe. This is the safe resume\n    /// the daemon `run_brain` heartbeat uses in place of [`Self::resume_sessions`]:\n    /// carrying only request/reply on the brain's conn (no UNDRAINED subscription) makes\n    /// the `SharedSend` self-deadlock structurally impossible (UPDATE-WEDGE round 3,\n    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK — see the deadlock warning on\n    /// [`Self::resume_sessions`]). The daemon brain drives no broker PTY session\n    /// (brainproc.rs:184) and nothing on its side consumes session output (§3 guard), so\n    /// the observation cursor is all it needs; no attach also means no controller steal\n    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL, a fortiori). Rides the brain-swap ⇒ seamless.\n    /// When genuinely daemon-brain-driven sessions land (the live-agent adapter future),\n    /// attach THOSE on a DEDICATED drained carrier (Option B, deferred) — never on this\n    /// request/reply conn.\n    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    // [impl->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    pub fn resume_session_cursors(&mut self) -> io::Result<Vec<u64>> {\n        let reply = self.sessions()?;\n        let mut resumed = Vec::with_capacity(reply.sessions.len());\n        for info in reply.sessions {\n            self.session_cursors\n                .insert(info.session_id, info.resume_seq);\n            resumed.push(info.session_id);\n        }\n        Ok(resumed)\n    }\n\n    /// The per-session resume cursor (the next seq still needed for a session\n    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.\n    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {\n        self.session_cursors.get(&session_id).copied()\n    }\n\n    /// The CONTROLLER's contiguous render cursor — the next seq still needed on\n    /// the legacy single-session (strict reject-gap) path. On a b4 drop-don't-block\n…\n    pub fn net_presence_subscribe_tail(&mut self) -> io::Result<()> {\n        self.presence_cursor = None; // baseline on the first live event (no floor)\n        self.send(\n            KIND_NET_PRESENCE_SUBSCRIBE,\n            serde_json::to_value(NetPresenceSubscribeReq { from_seq: u64::MAX })\n                .expect(\"NetPresenceSubscribeReq serializes\"),\n        )\n    }\n\n    /// Re-attach to a session's output from `from_seq` as a LOCAL CONTROLLER\n    /// (intent `Control`, identity `None`) — the legacy single-session serve\n    /// [`Brain::attach`] path.\n    ///\n    /// CAUTION — a `None`-identity Control attach does NOT \"never displace\":\n    /// `resolve_subscribe` (broker.rs) silently re-takes a FREE or SAME-LOCAL\n    /// (`None==None`) controller slot via `become_controller` (only a\n    /// DIFFERENT-REMOTE incumbent falls back to a silent viewer — the gate-#7\n    /// cross-operator guard). That silent re-take fires NO `Displaced` notice and\n    /// ORPHANS the prior local controller (the broker stops writing its output but\n    /// does not close the conn), FREEZING the session immediately; a >15s\n    /// `stall_evict_controller` is a separate, later, output-dependent symptom\n    /// (only an actively-producing stolen session blocks long enough to evict).\n    /// That is correct for `attach` (a serve loop that WILL drive the session), but\n    /// it is why a brain-respawn must NOT resume-re-attach as Control — it would\n    /// silently STEAL every local/free controller (incl. the operator's local\n    /// `spt rc`) it never drains, freezing the session\n    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL). [`Brain::resume_sessions`] re-attaches\n    /// as Viewer instead; Control is reserved for sessions the daemon brain drives.\n    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n        self.subscribe_with(session_id, from_seq, AttachIntent::Control, None)\n    }\n\n    /// Release this connection's controller/viewer role on `session_id`\n    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when\n    /// the viewport ends so the broker clears `driven_by` / `viewer_count`\n    /// promptly. Best-effort: a dropped connection clears the role anyway.\n    pub fn detach_session(&mut self, session_id: u64) -> io::Result<()> {\n        self.send(\n            KIND_UNSUBSCRIBE,\n            serde_json::to_value(UnsubscribeReq { session_id })\n                .expect(\"UnsubscribeReq serializes\"),\n        )\n    }\n\n    /// The role-aware subscribe (REQ-RCVIEW-1): `intent` selects viewer/control/\n    /// take, `by` carries the operator identity (`Some(node)` remote, `None`\n    /// local). The broker replies the resolved [`SubscribeOutcome`] as a\n    /// [`BrokerEvent::Subscribed`].\n    fn subscribe_with(\n        &mut self,\n        session_id: u64,\n        from_seq: u64,\n        intent: AttachIntent,\n        by: Option<String>,\n    ) -> io::Result<()> {\n…\n    }"}],"details":{"displayContent":{"text":"    /// all session continuity by querying the persistent side, never a brain→brain\n    /// message. Returns the ids re-attached (empty when the broker hosts none —\n    /// the supervised daemon brain's no-op-today case).\n    ///\n    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,\n    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send\n    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is\n    /// at-least-once; input/effects stay exactly-once via the broker-owned\n    /// `EffectJournal`. The generation is **not** touched — this is a cold start\n    /// (generation custody is the broker's, ADR-0018 V2), not a `handoff`.\n    ///\n    /// Each session is re-attached as a **VIEWER**, never a controller: a caller that\n    /// DRAINS the resumed output (`Brain::read_event`) gets output-continuity from its\n    /// cursor; a viewer never seizes/stall-evicts a controller slot (UPDATE-WEDGE-2,\n    /// REQ-BRAIN-RESUME-NO-CONTROL-STEAL). Genuinely daemon-brain-driven sessions (the\n    /// live-agent adapter future) will re-attach as Control on a drained carrier; that\n    /// set is empty today.\n    ///\n    /// ⚠️ DEADLOCK WARNING — the CALLER MUST DRAIN this conn (UPDATE-WEDGE round 3,\n    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK). This subscribes the sessions onto THIS conn.\n    /// A NON-DRAINING consumer will SELF-DEADLOCK: an actively-streaming session backs\n    /// the conn up, the broker-side subscriber writer blocks holding the conn's single\n    /// `SharedSend` mutex ACROSS a blocking `write_frame` (broker.rs\n    /// `viewer_writer`/`controller_writer`), the dispatch reply path (`send_frame`) needs\n    /// that same lock, so any request/reply the non-draining consumer issues is starved\n    /// (dead-peer-independent — the v0.30.4 field re-wedge). The supervised daemon brain\n    /// is exactly such a non-draining consumer (its `run_brain` heartbeat reads this conn\n    /// only in `net_status()`/`sessions()` drain-and-discard loops; it drives no PTY\n    /// session — brainproc.rs:184 — and nothing on its side consumes the output, §3\n    /// guard), so it MUST call [`Self::resume_session_cursors`] (cursor-only, NO\n    /// subscribe) instead. This subscribing method stays for a DRAINING caller (the\n    /// resume int tests today; the forward live-agent-adapter / Option-B drained carrier).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    // [impl->REQ-DAEMON-2]\n    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    // [doc->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    pub fn resume_sessions(&mut self) -> io::Result<Vec<u64>> {\n        let reply = self.sessions()?;\n        let mut resumed = Vec::with_capacity(reply.sessions.len());\n        for info in reply.sessions {\n            // Seed the dedup cursor at the broker's resume point BEFORE the subscribe\n            // replay starts arriving, then re-attach (Viewer) from there.\n            self.session_cursors\n                .insert(info.session_id, info.resume_seq);\n            // [impl->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n            self.subscribe_with(info.session_id, info.resume_seq, AttachIntent::Viewer, None)?;\n            resumed.push(info.session_id);\n        }\n        Ok(resumed)\n    }\n\n    /// Cursor-only resume for a NON-DRAINING caller (the supervised daemon brain): query\n    /// every hosted session and seed [`Self::session_cursors`] at each `resume_seq`,\n    /// returning the re-attached ids — but DO **NOT** subscribe. This is the safe resume\n    /// the daemon `run_brain` heartbeat uses in place of [`Self::resume_sessions`]:\n    /// carrying only request/reply on the brain's conn (no UNDRAINED subscription) makes\n    /// the `SharedSend` self-deadlock structurally impossible (UPDATE-WEDGE round 3,\n    /// REQ-BRAIN-RESUME-NO-CONN-DEADLOCK — see the deadlock warning on\n    /// [`Self::resume_sessions`]). The daemon brain drives no broker PTY session\n    /// (brainproc.rs:184) and nothing on its side consumes session output (§3 guard), so\n    /// the observation cursor is all it needs; no attach also means no controller steal\n    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL, a fortiori). Rides the brain-swap ⇒ seamless.\n    /// When genuinely daemon-brain-driven sessions land (the live-agent adapter future),\n    /// attach THOSE on a DEDICATED drained carrier (Option B, deferred) — never on this\n    /// request/reply conn.\n    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    // [impl->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    pub fn resume_session_cursors(&mut self) -> io::Result<Vec<u64>> {\n        let reply = self.sessions()?;\n        let mut resumed = Vec::with_capacity(reply.sessions.len());\n        for info in reply.sessions {\n            self.session_cursors\n                .insert(info.session_id, info.resume_seq);\n            resumed.push(info.session_id);\n        }\n        Ok(resumed)\n    }\n\n    /// The per-session resume cursor (the next seq still needed for a session\n    /// re-attached by [`Brain::resume_sessions`]) — observability / tests.\n    pub fn session_cursor(&self, session_id: u64) -> Option<u64> {\n        self.session_cursors.get(&session_id).copied()\n    }\n\n    /// The CONTROLLER's contiguous render cursor — the next seq still needed on\n    /// the legacy single-session (strict reject-gap) path. On a b4 drop-don't-block\n…\n    pub fn net_presence_subscribe_tail(&mut self) -> io::Result<()> {\n        self.presence_cursor = None; // baseline on the first live event (no floor)\n        self.send(\n            KIND_NET_PRESENCE_SUBSCRIBE,\n            serde_json::to_value(NetPresenceSubscribeReq { from_seq: u64::MAX })\n                .expect(\"NetPresenceSubscribeReq serializes\"),\n        )\n    }\n\n    /// Re-attach to a session's output from `from_seq` as a LOCAL CONTROLLER\n    /// (intent `Control`, identity `None`) — the legacy single-session serve\n    /// [`Brain::attach`] path.\n    ///\n    /// CAUTION — a `None`-identity Control attach does NOT \"never displace\":\n    /// `resolve_subscribe` (broker.rs) silently re-takes a FREE or SAME-LOCAL\n    /// (`None==None`) controller slot via `become_controller` (only a\n    /// DIFFERENT-REMOTE incumbent falls back to a silent viewer — the gate-#7\n    /// cross-operator guard). That silent re-take fires NO `Displaced` notice and\n    /// ORPHANS the prior local controller (the broker stops writing its output but\n    /// does not close the conn), FREEZING the session immediately; a >15s\n    /// `stall_evict_controller` is a separate, later, output-dependent symptom\n    /// (only an actively-producing stolen session blocks long enough to evict).\n    /// That is correct for `attach` (a serve loop that WILL drive the session), but\n    /// it is why a brain-respawn must NOT resume-re-attach as Control — it would\n    /// silently STEAL every local/free controller (incl. the operator's local\n    /// `spt rc`) it never drains, freezing the session\n    /// (REQ-BRAIN-RESUME-NO-CONTROL-STEAL). [`Brain::resume_sessions`] re-attaches\n    /// as Viewer instead; Control is reserved for sessions the daemon brain drives.\n    // [doc->REQ-BRAIN-RESUME-NO-CONTROL-STEAL]\n    fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n        self.subscribe_with(session_id, from_seq, AttachIntent::Control, None)\n    }\n\n    /// Release this connection's controller/viewer role on `session_id`\n    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when\n    /// the viewport ends so the broker clears `driven_by` / `viewer_count`\n    /// promptly. Best-effort: a dropped connection clears the role anyway.\n    pub fn detach_session(&mut self, session_id: u64) -> io::Result<()> {\n        self.send(\n            KIND_UNSUBSCRIBE,\n            serde_json::to_value(UnsubscribeReq { session_id })\n                .expect(\"UnsubscribeReq serializes\"),\n        )\n    }\n\n    /// The role-aware subscribe (REQ-RCVIEW-1): `intent` selects viewer/control/\n    /// take, `by` carries the operator identity (`Some(node)` remote, `None`\n    /// local). The broker replies the resolved [`SubscribeOutcome`] as a\n    /// [`BrokerEvent::Subscribed`].\n    fn subscribe_with(\n        &mut self,\n        session_id: u64,\n        from_seq: u64,\n        intent: AttachIntent,\n        by: Option<String>,\n    ) -> io::Result<()> {\n…\n    }","startLine":1010,"lineNumbers":[1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,null,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,null,1664]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"}}},"isError":false,"timestamp":1783730517305}}
{"type":"message","id":"53a363e7","parentId":"78d16915","timestamp":"2026-07-11T00:41:57.305Z","message":{"role":"toolResult","toolCallId":"call_YqUcVyqbXp2ecrApFxT63TiG|fc_08496348794f7e09016a519155c7e08198b6dc8b424adcf9aa","toolName":"read","content":[{"type":"text","text":"\n/// The effective respawn budget. Production is the const above; the int gate tunes\n/// it via `SPT_TRANSLATE_RESPAWN_BUDGET` (0 = never respawn — the pre-C-1 permanent-\n/// death semantics the g2/g6 gates lock; a small N to assert the bounded-respawn-\n/// then-give-up path fast).\nfn translate_respawn_budget() -> u32 {\n    match std::env::var(\"SPT_TRANSLATE_RESPAWN_BUDGET\") {\n        Ok(n) => n.parse::<u32>().ok().unwrap_or(TRANSLATE_RESPAWN_BUDGET),\n        Err(_) => TRANSLATE_RESPAWN_BUDGET,\n    }\n}\n\n// ── REQ-INJECT-MULTILINE-INTEGRITY (W5-A, doyle 3-layer ruling A) ───────────────\n// The typed idle-inject leg ate HEAD bytes nondeterministically: bytes typed into a\n// PTY whose input reader is not yet attached/settled are swallowed mid-stream (the\n// pre-settle race — 1854B once landed intact, ~1150B usually swallowed = a timing\n// class, not a size cap). A live-SENT inject leaves NO spool copy, so a swallow is\n// silent AND unrecoverable. The fix is three layers: (1) MUST settle-gate before ANY\n// byte (positive readiness probe, not a blind timer); (2) MUST echo-verify the head\n// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n\n/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n/// inject after startup, the pre-settle window; re-drives settle again). An echoing PTY\n/// returns in a tick or two, well under this.\nconst INJECT_SETTLE_DEADLINE: Duration = Duration::from_millis(400);\n/// Poll/probe cadence within the settle-gate and the echo-verify wait.\nconst INJECT_SETTLE_POLL: Duration = Duration::from_millis(40);\n/// The benign readiness probe: a DSR cursor-position query (`ESC[6n`). Invisible to\n/// a TUI input line and never a submit; a live terminal/child answers or re-renders,\n/// producing output — the POSITIVE signal that the input reader is attached.\nconst INJECT_SETTLE_PROBE: &[u8] = b\"\\x1b[6n\";\n\n/// The effective settle-gate deadline; the int gate tunes it via\n/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\nfn inject_settle_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_SETTLE_DEADLINE),\n        Err(_) => INJECT_SETTLE_DEADLINE,\n    }\n}\n\n/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n/// the typed payload's head to echo back on the session ring before declaring a\n/// verify MISS (round-trip + XON-resume latency bound).\nconst INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n\n/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\nfn inject_echo_verify_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_ECHO_VERIFY_DEADLINE),\n        Err(_) => INJECT_ECHO_VERIFY_DEADLINE,\n    }\n}\n\n/// How many bytes of the typed payload's HEAD we look for in the PTY echo. The bug\n/// is head-loss specifically, so a leading-prefix match is the discriminating probe\n/// (a swallowed head means the prefix is ABSENT even though a tail may echo).\nconst ECHO_VERIFY_PREFIX: usize = 24;\n\n/// Total delivery attempts per sequence: the first drive + ONE re-drive on an\n/// echo-verify miss (doyle ruling A layer 2: \"re-drive once; on a second miss, LOUD\n/// spool\"). 2 = one initial + one retry.\nconst INJECT_REDRIVE_ATTEMPTS: u32 = 2;\n\n/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n/// rather than one write, so no single huge write races the terminal's coalescer.\n/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\nconst INJECT_TEXT_CHUNK: usize = 256;\n\nfn inject_text_chunk() -> usize {\n    match std::env::var(\"SPT_INJECT_TEXT_CHUNK\") {\n        Ok(n) => n.parse::<usize>().ok().filter(|c| *c > 0).unwrap_or(INJECT_TEXT_CHUNK),\n        Err(_) => INJECT_TEXT_CHUNK,\n    }\n}\n\n/// Does `haystack` contain `needle` as a contiguous subslice? (Small, allocation-free\n/// — the echo-verify head match over concatenated ring bytes.)\nfn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {\n    if needle.is_empty() {\n        return true;\n    }\n    if needle.len() > haystack.len() {\n        return false;\n    }\n    haystack.windows(needle.len()).any(|w| w == needle)\n}\n\n/// Pure echo-verify decision (Layer 2, REQ-INJECT-MULTILINE-INTEGRITY): did the\n/// typed payload's HEAD echo back? Compare a bounded prefix of `sent_text` against\n/// the PTY echo captured since the sequence began. A TUI re-renders typed text so the\n/// leading bytes appear in output; a swallowed head means the prefix is ABSENT. An\n/// empty payload (a keys-only sequence — no echoable text) is vacuously verified.\nfn echo_verified(sent_text: &[u8], echoed: &[u8]) -> bool {\n    if sent_text.is_empty() {\n        return true;\n    }\n    let n = sent_text.len().min(ECHO_VERIFY_PREFIX);\n    contains_subslice(echoed, &sent_text[..n])\n}\n\n/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n/// C-1 addendum (3), REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Under miss≠fault a\n/// respooled zero-record envelope (the checkpoint duplicate FIRE) redelivers on a\n/// later drain → misses again → would respool forever (infinite floor-holds +\n/// strike churn). So a missed envelope respools AT MOST ONCE; a second miss of the\n/// SAME envelope is DEAD-LETTERED (loud, still counts as a strike — never a silent\n/// drop; REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture). A successful commit REMOVES\n/// the envelope from the set (it succeeded → a future miss may respool it afresh).\n#[derive(Debug, PartialEq, Eq)]\nenum MissRespool {\n    /// First miss of this envelope — respool it once and remember it.\n    Respool,\n    /// A repeat miss of an already-respooled envelope — dead-letter it, do NOT\n    /// respool again (still counts as a strike).\n    DeadLetter,\n}\n\nfn note_miss_respool(respooled_once: &mut HashSet<String>, envelope: &str) -> MissRespool {\n    if respooled_once.contains(envelope) {\n        MissRespool::DeadLetter\n    } else {\n        respooled_once.insert(envelope.to_string());\n        MissRespool::Respool\n    }\n}\n\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n…\nfn viewer_writer(\n    send: SharedSend,\n    session_id: u64,\n    initial: Vec<Envelope>,\n    rx: Receiver<Envelope>,\n    evicted: Arc<AtomicBool>,\n) {\n    for frame in &initial {\n        // Bounded, cancelable conn write (round-4): a viewer that stops\n        // reading poisons + retires ITS conn at the deadline instead of\n        // parking this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    while let Ok(frame) = rx.recv() {\n        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    // `tx` dropped. If this was an EVICTION for overflow (append set the flag\n    // before dropping the sink), emit a KIND_VIEWER_EVICTED marker — DISTINCT from\n    // a session-exit EOF — so the serving brain skips to live (re-subscribes from\n    // the ring floor) instead of dying silently. A NORMAL teardown (session end /\n    // conn drop) leaves the flag false → no marker, exit quietly. This write is in\n    // the dedicated writer thread, OFF the log lock / off the drain (the W1/b4\n    // isolation is preserved). REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT.\n    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n    if evicted.load(Ordering::Acquire) {\n        let marker = evicted_envelope(session_id);\n        let _ = send.write(&marker);\n    }\n}\n\n/// Round-4 observability bridge: a controller writer whose bounded conn write\n/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n/// write, or a gate-wait behind one exhausted the bound) IS the\n/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n/// wedge aged until a logical sampler (`stall_evict_controller`) observed it;\n/// the physical bound now fires FIRST, so the tally is recorded here — same\n/// event class, same `daemon status` surface. An ORGANIC write failure (peer\n/// death, any non-TimedOut error) stays uncounted, exactly as before.\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn note_controller_write_retired(sid: u64, e: &io::Error) {\n    if e.kind() != io::ErrorKind::TimedOut {\n        return;\n    }\n    eprintln!(\n        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} — controller conn write exceeded its \\\n         bound; physical conn poisoned + retired (writer released, EOF cleanup \\\n         follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\"\n    );\n    record_stall_evict();\n}\n\n/// A message to the CONTROLLER writer thread (W1). `Output` carries a sequenced\n/// chunk and advances the authoritative `delivered_through` on a successful\n/// write; `Control` is an out-of-band notice (Displaced, …) that is written but\n/// does NOT move the resume cursor.\nenum CtrlMsg {\n    Output(u64, Envelope),\n    Control(Envelope),\n}\n\n/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically\n/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer\n/// overlap during a controller displace.\nfn advance_delivered(dt: &AtomicU64, seq: u64) {\n    let mut cur = dt.load(Ordering::Acquire);\n    loop {\n        let next = advanced_cursor(cur, seq, true);\n        if next == cur {\n            return;\n        }\n        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {\n            Ok(_) => return,\n            Err(actual) => cur = actual,\n        }\n    }\n}\n\n/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to\n/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap\n/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,\n/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching\n/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the\n/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An\n/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,\n/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every\n/// live frame is contiguous) this is identical to [`advance_delivered`].\n// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\nfn contiguous_advance(dt: &AtomicU64, seq: u64) {\n    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);\n}\n\n/// The interactive CONTROLLER's dedicated writer thread (W1 /\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of\n/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume\n/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket\n/// write each — so a backed-up controller blocks ONLY this thread, never the\n/// session drain (which used to write the controller inline under the log lock).\n/// A failed write (dead operator) ends the thread; the drain's next bounded\n/// send then sees `Disconnected` and detaches the row. `Output` advances\n/// `delivered_through`; `Control` (Displaced/…) does not.\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n#[allow(clippy::too_many_arguments)] // per-writer thread-handoff channels (W2 added the blocked-since signal); a struct would obscure the call\nfn controller_writer(\n    sid: u64,\n    send: SharedSend,\n    initial: Vec<(u64, Vec<u8>)>,\n    rx: Receiver<CtrlMsg>,\n    delivered_through: Arc<AtomicU64>,\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate closure runs AFTER the conn's serialized write\n        // gate is acquired and BEFORE any byte is written — the same\n        // serialization point the old epoch-check-under-`send.lock()` had: both\n        // writers serialize on the one conn gate, and `become_controller` bumps\n        // the epoch BEFORE spawning the new writer, so whichever superseded\n        // writer next holds the gate observes the bump and bails before\n        // committing — no check-then-block-then-write window. (A frame the\n        // stale writer committed BEFORE the bump is irreducible at the broker;\n        // fix #1 removes the contradictory double-take that produced it.)\n        // Round-4: the write itself is bounded + cancelable — a non-draining\n        // consumer poisons + retires the physical conn at the deadline instead\n        // of wedging this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        mark_blocked(true);\n        let outcome =\n            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n        mark_blocked(false);\n        match outcome {\n            Ok(ConnWrite::Done) => {}\n            Ok(ConnWrite::Superseded) => return,\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n                return;\n            }\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        mark_blocked(true);\n        let res = send.write(frame);\n…\n    }\n}\n…\nfn build_translation(\n…\n) -> Option<Arc<Translation>> {\n…\n    match TranslationChild::spawn(argv, cmd_tx) {\n…\n    }\n}\n\n/// The resident translation binary path declared by the manifest at\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\n// [impl->REQ-TRANSLATE-COMMAND]\nfn read_translation_command(install_dir: &std::path::Path) -> Option<Vec<String>> {\n    let s = std::fs::read_to_string(install_dir.join(\"manifest.toml\")).ok()?;\n    let manifest = spt_runtime::Manifest::from_toml_str(&s).ok()?;\n    let decl = manifest.message_idle_translation_binary.as_ref()?;\n    // F-019 / REQ-INSTALL-11 + ADR-0029: resolve `command` (preferred) else the\n    // deprecated `path` to a spawnable argv — adapter-static keys substituted, the\n    // program token resolved against the install dir (same as the initial spawn in\n    // harnesshost) so the W3d live-update RESPAWN runs the real binary, not a\n    // verbatim path Command::new fails to find (which would spool idle inbound).\n    spt_runtime::runtime::resolve_translation_argv(\n        decl,\n        Some(install_dir),\n        Some(manifest.adapter.name.as_str()),\n    )\n}\n\n/// Run the inject worker under a panic guard (B6, REQ-HAZARD-INJECT-WORKER-POISON):\n/// `body` is the [`run_inject_worker`] call. If it PANICS, the guard FAULTs the\n/// translation binary — `fault_translation` terminates the child (no orphaned live\n/// pid) and sets `faulted` (idle inbound then spools, never a silent \"worker-gone\"\n/// force-native refusal against a still-running binary). Without this, a worker\n/// panic drops `event_rx` while the child keeps running: every later inject sees\n/// `event_tx.send` fail and reports delivered=false forever. `child` + `faulted` are\n/// the guard's OWN clones (the `body` moves its own into `run_inject_worker`).\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\nfn run_inject_worker_supervised(\n    child: Arc<TranslationChild>,\n    faulted: Arc<AtomicBool>,\n    endpoint: String,\n    body: impl FnOnce() + std::panic::UnwindSafe,\n) {\n    if std::panic::catch_unwind(body).is_err() {\n        // A worker panic is a REAL fault — stamp the perch (F029 C-1, so the\n        // degraded delivery is harness-reachable, not only on the F-019 stderr)\n        // then terminate + latch.\n        stamp_translation_fault(&endpoint, \"inject worker panicked\");\n        fault_translation(&faulted, &child, \"inject worker panicked\");\n    }\n}\n\n/// Layer 1 (REQ-INJECT-MULTILINE-INTEGRITY, the load-bearing fix): settle-gate before\n/// ANY inject byte of a sequence. The head-swallow race is a PTY whose input reader is\n/// not yet attached/settled when the first bytes are typed. It is NOT startup-only: a\n/// mid-session harness reset (a `/clear` re-entering raw mode, re-attaching the input\n/// reader) RE-CREATES the same window (field-proven on 0.29.0 — a checkpoint-wake payload\n/// injected right after `/clear` lost its head), so an echoing/interactive PTY must\n/// re-settle every delivery, not once per worker. Detect readiness with a POSITIVE\n/// signal, not a blind timer: write the benign DSR cursor-position probe and wait for the\n/// session ring to produce output (the child answered / re-rendered → its reader is live).\n/// Re-probe each tick until output appears or the bounded deadline elapses — a genuinely\n/// quiet/dead child never parks the worker (echo-verify + the commit deadline still bound\n/// delivery). No lock is held across a PTY write. Returns `true` iff the probe was\n/// OBSERVED (ring advanced) — the caller uses that to distinguish an echoing PTY (re-settle\n/// every delivery) from a non-echoing ConPTY (settle is useless + costs the full deadline).\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\n    let baseline = recover_log(log).high_water();\n    let deadline = Instant::now() + inject_settle_deadline();\n    loop {\n        // Probe: a live reader answers DSR / re-renders → the ring advances.\n        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\n        thread::sleep(INJECT_SETTLE_POLL);\n        if recover_log(log).high_water() > baseline {\n            return true; // output produced since baseline → the input reader is live\n        }\n        if Instant::now() >= deadline {\n            eprintln!(\n                \"INJECT_SETTLE_TIMEOUT:{endpoint}: no PTY readiness echo within {}ms — proceeding (echo-verify guards head loss)\",\n                inject_settle_deadline().as_millis()\n            );\n            return false;\n        }\n    }\n}\n\n/// Decide whether to run the settle-gate for this delivery attempt (Layer 1 re-arm,\n/// REQ-INJECT-MULTILINE-INTEGRITY). The reader-reattach race is NOT startup-only, so an\n/// echoing/interactive PTY (the bug-prone class — a `/clear` re-attaches its reader\n/// mid-session) MUST re-settle before EVERY delivery's first byte. We only skip that\n/// steady-state settle where the probe is known UNOBSERVABLE on this PTY (a non-echoing\n/// ConPTY, whose DSR answer never surfaces): there is no reader-reattach race to guard,\n/// and each settle would burn the full deadline. A RE-DRIVE (attempt > 1) always settles\n/// regardless of the latch — it is only reached on a swallowed head, so readiness must be\n/// re-confirmed before retyping.\n// [impl->REQ-HAZARD-INJECT-SETTLE-REARM]\nfn should_settle(attempt: u32, probe_unobservable: bool) -> bool {\n    attempt > 1 || !probe_unobservable\n}\n\n/// Layer 2 (REQ-INJECT-MULTILINE-INTEGRITY): after a `{commit}`, wait a bounded window\n/// for the typed payload's HEAD to echo back on the session ring (bytes produced since\n/// `seq_start`), then verify. A swallowed head means the prefix never appears. Empty\n/// payload (keys-only sequence) is vacuously verified with no wait.\n///\n/// **Fail SAFE on an unobservable echo.** The head-loss signature is a NON-empty echo\n/// whose head is missing (the field \"~322B tail-only suffix\"). But some environments\n/// echo nothing observable at all (Windows ConPTY does not surface the cooked echo of a\n/// PROGRAMMATICALLY-injected line, and a harness may not re-render typed text into the\n/// output stream). There, no echo ever appears; treating that as head-loss would re-drive\n/// and LOUD-spool EVERY real delivery. So when the window elapses with NO output produced,\n/// we assume delivered (the settle-gate remains the root fix); only a non-empty echo that\n/// is missing the head is judged a loss.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\n    if sent_text.is_empty() {\n        return true;\n    }\n    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\n    // spool wiring deterministically on any platform (physical head-loss is a forkpty-\n    // only race). Read only when the env is present, so production never pays for it.\n    if std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some() {\n        return false;\n    }\n    let deadline = Instant::now() + inject_echo_verify_deadline();\n    loop {\n        let echoed = recover_log(log).bytes_since(seq_start);\n        if echo_verified(sent_text, &echoed) {\n            return true;\n        }\n        if Instant::now() >= deadline {\n            // Unverifiable (nothing echoed at all) → fail safe, assume delivered. A\n            // NON-empty echo missing the head is the real head-loss → not verified.\n            return echoed.is_empty();\n        }\n        thread::sleep(INJECT_SETTLE_POLL);\n    }\n}\n\n/// Layer 3 (REQ-INJECT-MULTILINE-INTEGRITY, the ruling's named fallback under the\n/// settle-gate): enqueue payload text in bounded chunks rather than one write, so no\n/// single huge write races the terminal's input coalescer. Order is preserved (single\n/// FIFO, single writer). The reader is already confirmed live by the settle-gate before\n/// the first chunk lands.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn enqueue_text_chunked(input: &InputWriter, bytes: Vec<u8>) {\n    chunk_text(&bytes, inject_text_chunk(), |part| {\n        input.enqueue(part.to_vec());\n    });\n}\n\n/// Pure chunk-split core (unit-testable): a payload at or below `chunk` is emitted\n/// whole; a larger one is split into ordered `chunk`-sized parts (last part short).\n/// Reassembling the emitted parts in order yields the original bytes exactly.\nfn chunk_text(bytes: &[u8], chunk: usize, mut emit: impl FnMut(&[u8])) {\n    if bytes.len() <= chunk {\n        emit(bytes);\n        return;\n    }\n    for part in bytes.chunks(chunk) {\n        emit(part);\n    }\n}\n\n/// The outcome of driving ONE inject sequence (feed the event, drain keystroke\n/// commands to `{commit}` / deadline / death). Carries the payload text TYPED this\n/// attempt so Layer 2 can echo-verify it and (on a re-drive) the caller can re-run.\nenum SeqOutcome {\n    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\n    /// concatenated payload text typed (keys excluded — they carry no echoable text).\n    Committed { sent_text: Vec<u8> },\n    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\n    Missed,\n    /// The binary's stdout closed mid-sequence (a REAL death fault).\n    Disconnected,\n    /// The stdin write to feed the event FAILED — the binary is gone (a REAL fault).\n    SendFailed,\n}\n\n/// Drive one inject sequence for `envelope`: open the floor, feed the event, drain the\n/// binary's `{key}`/`{text}`/`{delay_ms}` commands to the PTY (text paced-chunked under\n/// the settle-gate) until `{commit}` / the commit deadline / stdout death, then flush the\n/// buffered controller input and release the floor in EVERY exit path (the anti-stall\n/// guarantee). Returns the outcome + typed payload text for the caller's echo-verify.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\nfn drive_one_sequence(\n    input: &InputWriter,\n    floor: &Mutex<InjectFloor>,\n    child: &TranslationChild,\n    cmd_rx: &Receiver<KeyCmd>,\n    envelope: String,\n) -> SeqOutcome {\n    lock_floor(floor).open();\n    if child.send(&ToBinary::Event { envelope }).is_err() {\n        flush_inject_floor(floor, input);\n        return SeqOutcome::SendFailed;\n    }\n    let deadline = Instant::now() + inject_commit_deadline();\n    let mut committed = false;\n    let mut disconnected = false;\n    let mut sent_text: Vec<u8> = Vec::new();\n    loop {\n        let now = Instant::now();\n        let remaining = deadline.saturating_duration_since(now);\n        if remaining.is_zero() {\n            break; // deadline → MISS\n        }\n        match cmd_rx.recv_timeout(remaining) {\n            Ok(KeyCmd::Key { key }) => {\n                if let Some(bytes) = key_to_bytes(&key) {\n                    input.enqueue(bytes);\n                }\n            }\n            Ok(KeyCmd::Text { text }) => {\n                // Accumulate the payload for echo-verify, then type it paced-chunked.\n                let bytes = text.into_bytes();\n                sent_text.extend_from_slice(&bytes);\n                enqueue_text_chunked(input, bytes);\n            }\n            Ok(KeyCmd::Delay { delay_ms }) => {\n                let want = Duration::from_millis(delay_ms);\n                let cap = deadline.saturating_duration_since(Instant::now());\n                thread::sleep(want.min(cap));\n            }\n            Ok(KeyCmd::Commit { .. }) => {\n                committed = true;\n                break;\n            }\n            Err(RecvTimeoutError::Timeout) => break,\n            Err(RecvTimeoutError::Disconnected) => {\n                disconnected = true;\n                break;\n            }\n        }\n    }\n    // Flush buffered controller input AFTER the injected bytes + release the floor —\n    // in EVERY exit path (the ANTI-STALL guarantee).\n    flush_inject_floor(floor, input);\n    if disconnected {\n        SeqOutcome::Disconnected\n    } else if committed {\n        SeqOutcome::Committed { sent_text }\n    } else {\n        SeqOutcome::Missed\n    }\n}\n\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-MSG-DELIVERY-AXES]\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\nfn run_inject_worker(\n    input: Arc<InputWriter>,\n    child: Arc<TranslationChild>,\n    floor: Arc<Mutex<InjectFloor>>,\n    event_rx: Receiver<String>,\n    cmd_rx: Receiver<KeyCmd>,\n    faulted: Arc<AtomicBool>,\n    respawns: Arc<AtomicU32>,\n    log: Arc<Mutex<OutputLog>>,\n    endpoint: String,\n) {\n    // C-1 worker-local state (REQ-TRANSLATE-COMMIT-MISS-TOLERANCE): CONSECUTIVE\n    // commit-deadline misses (reset on any successful `{commit}`), and the set of\n    // envelopes already respooled-once so a repeat miss dead-letters instead of\n    // respooling forever. Both are worker-local: a REAL fault exits the worker, and\n    // a respawn builds a fresh worker with a fresh budget + set.\n    let mut strikes: u32 = 0;\n    let mut respooled_once: HashSet<String> = HashSet::new();\n    // W5-A Layer 1 (re-arm fix): settle before EVERY delivery, not once per worker. The\n    // head-swallow race is NOT startup-only — a mid-session `/clear` re-enters the\n    // harness's raw-mode input reader and re-creates the pre-settle window (field-proven\n    // on 0.29.0: a checkpoint-wake payload injected right after `/clear` lost its head\n    // even though the worker had long since settled at spawn). So an echoing/interactive\n    // PTY re-settles per delivery (cheap: a tick or two once the reader answers). The old\n    // one-shot's ONLY legitimate purpose was sparing a NON-echoing ConPTY the full\n    // settle-deadline on every delivery (its DSR answer never surfaces, and it has no\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\n    // keeps it clear, so the bug-prone class re-settles forever.\n    let mut probe_unobservable = false;\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\n    // input into its observable output (a real TUI's input line does; a bare shell / a\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\n…\n}"}],"details":{"displayContent":{"text":"\n/// The effective respawn budget. Production is the const above; the int gate tunes\n/// it via `SPT_TRANSLATE_RESPAWN_BUDGET` (0 = never respawn — the pre-C-1 permanent-\n/// death semantics the g2/g6 gates lock; a small N to assert the bounded-respawn-\n/// then-give-up path fast).\nfn translate_respawn_budget() -> u32 {\n    match std::env::var(\"SPT_TRANSLATE_RESPAWN_BUDGET\") {\n        Ok(n) => n.parse::<u32>().ok().unwrap_or(TRANSLATE_RESPAWN_BUDGET),\n        Err(_) => TRANSLATE_RESPAWN_BUDGET,\n    }\n}\n\n// ── REQ-INJECT-MULTILINE-INTEGRITY (W5-A, doyle 3-layer ruling A) ───────────────\n// The typed idle-inject leg ate HEAD bytes nondeterministically: bytes typed into a\n// PTY whose input reader is not yet attached/settled are swallowed mid-stream (the\n// pre-settle race — 1854B once landed intact, ~1150B usually swallowed = a timing\n// class, not a size cap). A live-SENT inject leaves NO spool copy, so a swallow is\n// silent AND unrecoverable. The fix is three layers: (1) MUST settle-gate before ANY\n// byte (positive readiness probe, not a blind timer); (2) MUST echo-verify the head\n// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n\n/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n/// inject after startup, the pre-settle window; re-drives settle again). An echoing PTY\n/// returns in a tick or two, well under this.\nconst INJECT_SETTLE_DEADLINE: Duration = Duration::from_millis(400);\n/// Poll/probe cadence within the settle-gate and the echo-verify wait.\nconst INJECT_SETTLE_POLL: Duration = Duration::from_millis(40);\n/// The benign readiness probe: a DSR cursor-position query (`ESC[6n`). Invisible to\n/// a TUI input line and never a submit; a live terminal/child answers or re-renders,\n/// producing output — the POSITIVE signal that the input reader is attached.\nconst INJECT_SETTLE_PROBE: &[u8] = b\"\\x1b[6n\";\n\n/// The effective settle-gate deadline; the int gate tunes it via\n/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\nfn inject_settle_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_SETTLE_DEADLINE),\n        Err(_) => INJECT_SETTLE_DEADLINE,\n    }\n}\n\n/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n/// the typed payload's head to echo back on the session ring before declaring a\n/// verify MISS (round-trip + XON-resume latency bound).\nconst INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n\n/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\nfn inject_echo_verify_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_ECHO_VERIFY_DEADLINE),\n        Err(_) => INJECT_ECHO_VERIFY_DEADLINE,\n    }\n}\n\n/// How many bytes of the typed payload's HEAD we look for in the PTY echo. The bug\n/// is head-loss specifically, so a leading-prefix match is the discriminating probe\n/// (a swallowed head means the prefix is ABSENT even though a tail may echo).\nconst ECHO_VERIFY_PREFIX: usize = 24;\n\n/// Total delivery attempts per sequence: the first drive + ONE re-drive on an\n/// echo-verify miss (doyle ruling A layer 2: \"re-drive once; on a second miss, LOUD\n/// spool\"). 2 = one initial + one retry.\nconst INJECT_REDRIVE_ATTEMPTS: u32 = 2;\n\n/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n/// rather than one write, so no single huge write races the terminal's coalescer.\n/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\nconst INJECT_TEXT_CHUNK: usize = 256;\n\nfn inject_text_chunk() -> usize {\n    match std::env::var(\"SPT_INJECT_TEXT_CHUNK\") {\n        Ok(n) => n.parse::<usize>().ok().filter(|c| *c > 0).unwrap_or(INJECT_TEXT_CHUNK),\n        Err(_) => INJECT_TEXT_CHUNK,\n    }\n}\n\n/// Does `haystack` contain `needle` as a contiguous subslice? (Small, allocation-free\n/// — the echo-verify head match over concatenated ring bytes.)\nfn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {\n    if needle.is_empty() {\n        return true;\n    }\n    if needle.len() > haystack.len() {\n        return false;\n    }\n    haystack.windows(needle.len()).any(|w| w == needle)\n}\n\n/// Pure echo-verify decision (Layer 2, REQ-INJECT-MULTILINE-INTEGRITY): did the\n/// typed payload's HEAD echo back? Compare a bounded prefix of `sent_text` against\n/// the PTY echo captured since the sequence began. A TUI re-renders typed text so the\n/// leading bytes appear in output; a swallowed head means the prefix is ABSENT. An\n/// empty payload (a keys-only sequence — no echoable text) is vacuously verified.\nfn echo_verified(sent_text: &[u8], echoed: &[u8]) -> bool {\n    if sent_text.is_empty() {\n        return true;\n    }\n    let n = sent_text.len().min(ECHO_VERIFY_PREFIX);\n    contains_subslice(echoed, &sent_text[..n])\n}\n\n/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n/// C-1 addendum (3), REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Under miss≠fault a\n/// respooled zero-record envelope (the checkpoint duplicate FIRE) redelivers on a\n/// later drain → misses again → would respool forever (infinite floor-holds +\n/// strike churn). So a missed envelope respools AT MOST ONCE; a second miss of the\n/// SAME envelope is DEAD-LETTERED (loud, still counts as a strike — never a silent\n/// drop; REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture). A successful commit REMOVES\n/// the envelope from the set (it succeeded → a future miss may respool it afresh).\n#[derive(Debug, PartialEq, Eq)]\nenum MissRespool {\n    /// First miss of this envelope — respool it once and remember it.\n    Respool,\n    /// A repeat miss of an already-respooled envelope — dead-letter it, do NOT\n    /// respool again (still counts as a strike).\n    DeadLetter,\n}\n\nfn note_miss_respool(respooled_once: &mut HashSet<String>, envelope: &str) -> MissRespool {\n    if respooled_once.contains(envelope) {\n        MissRespool::DeadLetter\n    } else {\n        respooled_once.insert(envelope.to_string());\n        MissRespool::Respool\n    }\n}\n\n/// Pure strike-budget verdict: a MISS becomes a REAL fault once `strikes` reaches\n…\nfn viewer_writer(\n    send: SharedSend,\n    session_id: u64,\n    initial: Vec<Envelope>,\n    rx: Receiver<Envelope>,\n    evicted: Arc<AtomicBool>,\n) {\n    for frame in &initial {\n        // Bounded, cancelable conn write (round-4): a viewer that stops\n        // reading poisons + retires ITS conn at the deadline instead of\n        // parking this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        if !matches!(send.write(frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    while let Ok(frame) = rx.recv() {\n        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n            return;\n        }\n    }\n    // `tx` dropped. If this was an EVICTION for overflow (append set the flag\n    // before dropping the sink), emit a KIND_VIEWER_EVICTED marker — DISTINCT from\n    // a session-exit EOF — so the serving brain skips to live (re-subscribes from\n    // the ring floor) instead of dying silently. A NORMAL teardown (session end /\n    // conn drop) leaves the flag false → no marker, exit quietly. This write is in\n    // the dedicated writer thread, OFF the log lock / off the drain (the W1/b4\n    // isolation is preserved). REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT.\n    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n    if evicted.load(Ordering::Acquire) {\n        let marker = evicted_envelope(session_id);\n        let _ = send.write(&marker);\n    }\n}\n\n/// Round-4 observability bridge: a controller writer whose bounded conn write\n/// was DEADLINE-retired (`TimedOut` — the conn watchdog aborted a wedged\n/// write, or a gate-wait behind one exhausted the bound) IS the\n/// brain-subscriber stall-evict fact the W2 tally counts. Pre-round-4 that\n/// wedge aged until a logical sampler (`stall_evict_controller`) observed it;\n/// the physical bound now fires FIRST, so the tally is recorded here — same\n/// event class, same `daemon status` surface. An ORGANIC write failure (peer\n/// death, any non-TimedOut error) stays uncounted, exactly as before.\n// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn note_controller_write_retired(sid: u64, e: &io::Error) {\n    if e.kind() != io::ErrorKind::TimedOut {\n        return;\n    }\n    eprintln!(\n        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} — controller conn write exceeded its \\\n         bound; physical conn poisoned + retired (writer released, EOF cleanup \\\n         follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\"\n    );\n    record_stall_evict();\n}\n\n/// A message to the CONTROLLER writer thread (W1). `Output` carries a sequenced\n/// chunk and advances the authoritative `delivered_through` on a successful\n/// write; `Control` is an out-of-band notice (Displaced, …) that is written but\n/// does NOT move the resume cursor.\nenum CtrlMsg {\n    Output(u64, Envelope),\n    Control(Envelope),\n}\n\n/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically\n/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer\n/// overlap during a controller displace.\nfn advance_delivered(dt: &AtomicU64, seq: u64) {\n    let mut cur = dt.load(Ordering::Acquire);\n    loop {\n        let next = advanced_cursor(cur, seq, true);\n        if next == cur {\n            return;\n        }\n        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {\n            Ok(_) => return,\n            Err(actual) => cur = actual,\n        }\n    }\n}\n\n/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to\n/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap\n/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,\n/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching\n/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the\n/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An\n/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,\n/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every\n/// live frame is contiguous) this is identical to [`advance_delivered`].\n// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\nfn contiguous_advance(dt: &AtomicU64, seq: u64) {\n    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);\n}\n\n/// The interactive CONTROLLER's dedicated writer thread (W1 /\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of\n/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume\n/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket\n/// write each — so a backed-up controller blocks ONLY this thread, never the\n/// session drain (which used to write the controller inline under the log lock).\n/// A failed write (dead operator) ends the thread; the drain's next bounded\n/// send then sees `Disconnected` and detaches the row. `Output` advances\n/// `delivered_through`; `Control` (Displaced/…) does not.\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n#[allow(clippy::too_many_arguments)] // per-writer thread-handoff channels (W2 added the blocked-since signal); a struct would obscure the call\nfn controller_writer(\n    sid: u64,\n    send: SharedSend,\n    initial: Vec<(u64, Vec<u8>)>,\n    rx: Receiver<CtrlMsg>,\n    delivered_through: Arc<AtomicU64>,\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate closure runs AFTER the conn's serialized write\n        // gate is acquired and BEFORE any byte is written — the same\n        // serialization point the old epoch-check-under-`send.lock()` had: both\n        // writers serialize on the one conn gate, and `become_controller` bumps\n        // the epoch BEFORE spawning the new writer, so whichever superseded\n        // writer next holds the gate observes the bump and bails before\n        // committing — no check-then-block-then-write window. (A frame the\n        // stale writer committed BEFORE the bump is irreducible at the broker;\n        // fix #1 removes the contradictory double-take that produced it.)\n        // Round-4: the write itself is bounded + cancelable — a non-draining\n        // consumer poisons + retires the physical conn at the deadline instead\n        // of wedging this thread in the OS write forever.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        mark_blocked(true);\n        let outcome =\n            send.write_gated(&frame, || controller_epoch.load(Ordering::Acquire) == epoch);\n        mark_blocked(false);\n        match outcome {\n            Ok(ConnWrite::Done) => {}\n            Ok(ConnWrite::Superseded) => return,\n            Err(e) => {\n                note_controller_write_retired(sid, &e);\n                return;\n            }\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        mark_blocked(true);\n        let res = send.write(frame);\n…\n    }\n}\n…\nfn build_translation(\n…\n) -> Option<Arc<Translation>> {\n…\n    match TranslationChild::spawn(argv, cmd_tx) {\n…\n    }\n}\n\n/// The resident translation binary path declared by the manifest at\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\n// [impl->REQ-TRANSLATE-COMMAND]\nfn read_translation_command(install_dir: &std::path::Path) -> Option<Vec<String>> {\n    let s = std::fs::read_to_string(install_dir.join(\"manifest.toml\")).ok()?;\n    let manifest = spt_runtime::Manifest::from_toml_str(&s).ok()?;\n    let decl = manifest.message_idle_translation_binary.as_ref()?;\n    // F-019 / REQ-INSTALL-11 + ADR-0029: resolve `command` (preferred) else the\n    // deprecated `path` to a spawnable argv — adapter-static keys substituted, the\n    // program token resolved against the install dir (same as the initial spawn in\n    // harnesshost) so the W3d live-update RESPAWN runs the real binary, not a\n    // verbatim path Command::new fails to find (which would spool idle inbound).\n    spt_runtime::runtime::resolve_translation_argv(\n        decl,\n        Some(install_dir),\n        Some(manifest.adapter.name.as_str()),\n    )\n}\n\n/// Run the inject worker under a panic guard (B6, REQ-HAZARD-INJECT-WORKER-POISON):\n/// `body` is the [`run_inject_worker`] call. If it PANICS, the guard FAULTs the\n/// translation binary — `fault_translation` terminates the child (no orphaned live\n/// pid) and sets `faulted` (idle inbound then spools, never a silent \"worker-gone\"\n/// force-native refusal against a still-running binary). Without this, a worker\n/// panic drops `event_rx` while the child keeps running: every later inject sees\n/// `event_tx.send` fail and reports delivered=false forever. `child` + `faulted` are\n/// the guard's OWN clones (the `body` moves its own into `run_inject_worker`).\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\nfn run_inject_worker_supervised(\n    child: Arc<TranslationChild>,\n    faulted: Arc<AtomicBool>,\n    endpoint: String,\n    body: impl FnOnce() + std::panic::UnwindSafe,\n) {\n    if std::panic::catch_unwind(body).is_err() {\n        // A worker panic is a REAL fault — stamp the perch (F029 C-1, so the\n        // degraded delivery is harness-reachable, not only on the F-019 stderr)\n        // then terminate + latch.\n        stamp_translation_fault(&endpoint, \"inject worker panicked\");\n        fault_translation(&faulted, &child, \"inject worker panicked\");\n    }\n}\n\n/// Layer 1 (REQ-INJECT-MULTILINE-INTEGRITY, the load-bearing fix): settle-gate before\n/// ANY inject byte of a sequence. The head-swallow race is a PTY whose input reader is\n/// not yet attached/settled when the first bytes are typed. It is NOT startup-only: a\n/// mid-session harness reset (a `/clear` re-entering raw mode, re-attaching the input\n/// reader) RE-CREATES the same window (field-proven on 0.29.0 — a checkpoint-wake payload\n/// injected right after `/clear` lost its head), so an echoing/interactive PTY must\n/// re-settle every delivery, not once per worker. Detect readiness with a POSITIVE\n/// signal, not a blind timer: write the benign DSR cursor-position probe and wait for the\n/// session ring to produce output (the child answered / re-rendered → its reader is live).\n/// Re-probe each tick until output appears or the bounded deadline elapses — a genuinely\n/// quiet/dead child never parks the worker (echo-verify + the commit deadline still bound\n/// delivery). No lock is held across a PTY write. Returns `true` iff the probe was\n/// OBSERVED (ring advanced) — the caller uses that to distinguish an echoing PTY (re-settle\n/// every delivery) from a non-echoing ConPTY (settle is useless + costs the full deadline).\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\n    let baseline = recover_log(log).high_water();\n    let deadline = Instant::now() + inject_settle_deadline();\n    loop {\n        // Probe: a live reader answers DSR / re-renders → the ring advances.\n        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\n        thread::sleep(INJECT_SETTLE_POLL);\n        if recover_log(log).high_water() > baseline {\n            return true; // output produced since baseline → the input reader is live\n        }\n        if Instant::now() >= deadline {\n            eprintln!(\n                \"INJECT_SETTLE_TIMEOUT:{endpoint}: no PTY readiness echo within {}ms — proceeding (echo-verify guards head loss)\",\n                inject_settle_deadline().as_millis()\n            );\n            return false;\n        }\n    }\n}\n\n/// Decide whether to run the settle-gate for this delivery attempt (Layer 1 re-arm,\n/// REQ-INJECT-MULTILINE-INTEGRITY). The reader-reattach race is NOT startup-only, so an\n/// echoing/interactive PTY (the bug-prone class — a `/clear` re-attaches its reader\n/// mid-session) MUST re-settle before EVERY delivery's first byte. We only skip that\n/// steady-state settle where the probe is known UNOBSERVABLE on this PTY (a non-echoing\n/// ConPTY, whose DSR answer never surfaces): there is no reader-reattach race to guard,\n/// and each settle would burn the full deadline. A RE-DRIVE (attempt > 1) always settles\n/// regardless of the latch — it is only reached on a swallowed head, so readiness must be\n/// re-confirmed before retyping.\n// [impl->REQ-HAZARD-INJECT-SETTLE-REARM]\nfn should_settle(attempt: u32, probe_unobservable: bool) -> bool {\n    attempt > 1 || !probe_unobservable\n}\n\n/// Layer 2 (REQ-INJECT-MULTILINE-INTEGRITY): after a `{commit}`, wait a bounded window\n/// for the typed payload's HEAD to echo back on the session ring (bytes produced since\n/// `seq_start`), then verify. A swallowed head means the prefix never appears. Empty\n/// payload (keys-only sequence) is vacuously verified with no wait.\n///\n/// **Fail SAFE on an unobservable echo.** The head-loss signature is a NON-empty echo\n/// whose head is missing (the field \"~322B tail-only suffix\"). But some environments\n/// echo nothing observable at all (Windows ConPTY does not surface the cooked echo of a\n/// PROGRAMMATICALLY-injected line, and a harness may not re-render typed text into the\n/// output stream). There, no echo ever appears; treating that as head-loss would re-drive\n/// and LOUD-spool EVERY real delivery. So when the window elapses with NO output produced,\n/// we assume delivered (the settle-gate remains the root fix); only a non-empty echo that\n/// is missing the head is judged a loss.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\n    if sent_text.is_empty() {\n        return true;\n    }\n    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\n    // spool wiring deterministically on any platform (physical head-loss is a forkpty-\n    // only race). Read only when the env is present, so production never pays for it.\n    if std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some() {\n        return false;\n    }\n    let deadline = Instant::now() + inject_echo_verify_deadline();\n    loop {\n        let echoed = recover_log(log).bytes_since(seq_start);\n        if echo_verified(sent_text, &echoed) {\n            return true;\n        }\n        if Instant::now() >= deadline {\n            // Unverifiable (nothing echoed at all) → fail safe, assume delivered. A\n            // NON-empty echo missing the head is the real head-loss → not verified.\n            return echoed.is_empty();\n        }\n        thread::sleep(INJECT_SETTLE_POLL);\n    }\n}\n\n/// Layer 3 (REQ-INJECT-MULTILINE-INTEGRITY, the ruling's named fallback under the\n/// settle-gate): enqueue payload text in bounded chunks rather than one write, so no\n/// single huge write races the terminal's input coalescer. Order is preserved (single\n/// FIFO, single writer). The reader is already confirmed live by the settle-gate before\n/// the first chunk lands.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn enqueue_text_chunked(input: &InputWriter, bytes: Vec<u8>) {\n    chunk_text(&bytes, inject_text_chunk(), |part| {\n        input.enqueue(part.to_vec());\n    });\n}\n\n/// Pure chunk-split core (unit-testable): a payload at or below `chunk` is emitted\n/// whole; a larger one is split into ordered `chunk`-sized parts (last part short).\n/// Reassembling the emitted parts in order yields the original bytes exactly.\nfn chunk_text(bytes: &[u8], chunk: usize, mut emit: impl FnMut(&[u8])) {\n    if bytes.len() <= chunk {\n        emit(bytes);\n        return;\n    }\n    for part in bytes.chunks(chunk) {\n        emit(part);\n    }\n}\n\n/// The outcome of driving ONE inject sequence (feed the event, drain keystroke\n/// commands to `{commit}` / deadline / death). Carries the payload text TYPED this\n/// attempt so Layer 2 can echo-verify it and (on a re-drive) the caller can re-run.\nenum SeqOutcome {\n    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\n    /// concatenated payload text typed (keys excluded — they carry no echoable text).\n    Committed { sent_text: Vec<u8> },\n    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\n    Missed,\n    /// The binary's stdout closed mid-sequence (a REAL death fault).\n    Disconnected,\n    /// The stdin write to feed the event FAILED — the binary is gone (a REAL fault).\n    SendFailed,\n}\n\n/// Drive one inject sequence for `envelope`: open the floor, feed the event, drain the\n/// binary's `{key}`/`{text}`/`{delay_ms}` commands to the PTY (text paced-chunked under\n/// the settle-gate) until `{commit}` / the commit deadline / stdout death, then flush the\n/// buffered controller input and release the floor in EVERY exit path (the anti-stall\n/// guarantee). Returns the outcome + typed payload text for the caller's echo-verify.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\nfn drive_one_sequence(\n    input: &InputWriter,\n    floor: &Mutex<InjectFloor>,\n    child: &TranslationChild,\n    cmd_rx: &Receiver<KeyCmd>,\n    envelope: String,\n) -> SeqOutcome {\n    lock_floor(floor).open();\n    if child.send(&ToBinary::Event { envelope }).is_err() {\n        flush_inject_floor(floor, input);\n        return SeqOutcome::SendFailed;\n    }\n    let deadline = Instant::now() + inject_commit_deadline();\n    let mut committed = false;\n    let mut disconnected = false;\n    let mut sent_text: Vec<u8> = Vec::new();\n    loop {\n        let now = Instant::now();\n        let remaining = deadline.saturating_duration_since(now);\n        if remaining.is_zero() {\n            break; // deadline → MISS\n        }\n        match cmd_rx.recv_timeout(remaining) {\n            Ok(KeyCmd::Key { key }) => {\n                if let Some(bytes) = key_to_bytes(&key) {\n                    input.enqueue(bytes);\n                }\n            }\n            Ok(KeyCmd::Text { text }) => {\n                // Accumulate the payload for echo-verify, then type it paced-chunked.\n                let bytes = text.into_bytes();\n                sent_text.extend_from_slice(&bytes);\n                enqueue_text_chunked(input, bytes);\n            }\n            Ok(KeyCmd::Delay { delay_ms }) => {\n                let want = Duration::from_millis(delay_ms);\n                let cap = deadline.saturating_duration_since(Instant::now());\n                thread::sleep(want.min(cap));\n            }\n            Ok(KeyCmd::Commit { .. }) => {\n                committed = true;\n                break;\n            }\n            Err(RecvTimeoutError::Timeout) => break,\n            Err(RecvTimeoutError::Disconnected) => {\n                disconnected = true;\n                break;\n            }\n        }\n    }\n    // Flush buffered controller input AFTER the injected bytes + release the floor —\n    // in EVERY exit path (the ANTI-STALL guarantee).\n    flush_inject_floor(floor, input);\n    if disconnected {\n        SeqOutcome::Disconnected\n    } else if committed {\n        SeqOutcome::Committed { sent_text }\n    } else {\n        SeqOutcome::Missed\n    }\n}\n\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-MSG-DELIVERY-AXES]\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\nfn run_inject_worker(\n    input: Arc<InputWriter>,\n    child: Arc<TranslationChild>,\n    floor: Arc<Mutex<InjectFloor>>,\n    event_rx: Receiver<String>,\n    cmd_rx: Receiver<KeyCmd>,\n    faulted: Arc<AtomicBool>,\n    respawns: Arc<AtomicU32>,\n    log: Arc<Mutex<OutputLog>>,\n    endpoint: String,\n) {\n    // C-1 worker-local state (REQ-TRANSLATE-COMMIT-MISS-TOLERANCE): CONSECUTIVE\n    // commit-deadline misses (reset on any successful `{commit}`), and the set of\n    // envelopes already respooled-once so a repeat miss dead-letters instead of\n    // respooling forever. Both are worker-local: a REAL fault exits the worker, and\n    // a respawn builds a fresh worker with a fresh budget + set.\n    let mut strikes: u32 = 0;\n    let mut respooled_once: HashSet<String> = HashSet::new();\n    // W5-A Layer 1 (re-arm fix): settle before EVERY delivery, not once per worker. The\n    // head-swallow race is NOT startup-only — a mid-session `/clear` re-enters the\n    // harness's raw-mode input reader and re-creates the pre-settle window (field-proven\n    // on 0.29.0: a checkpoint-wake payload injected right after `/clear` lost its head\n    // even though the worker had long since settled at spawn). So an echoing/interactive\n    // PTY re-settles per delivery (cheap: a tick or two once the reader answers). The old\n    // one-shot's ONLY legitimate purpose was sparing a NON-echoing ConPTY the full\n    // settle-deadline on every delivery (its DSR answer never surfaces, and it has no\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\n    // keeps it clear, so the bug-prone class re-settles forever.\n    let mut probe_unobservable = false;\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\n    // input into its observable output (a real TUI's input line does; a bare shell / a\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\n…\n}","startLine":340,"lineNumbers":[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,null,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,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,null,1522,1523,null,1818,null,1824,null,1826,null,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,null,2344]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"}}},"isError":false,"timestamp":1783730517305}}
{"type":"message","id":"2aff0a3c","parentId":"53a363e7","timestamp":"2026-07-11T00:41:57.309Z","message":{"role":"toolResult","toolCallId":"call_LZMEmH49sQkSAuG10sgxgKJ5|fc_08496348794f7e09016a519155c7c481988249d78333eed4fb","toolName":"read","content":[{"type":"text","text":"# UPDATE-WEDGE round-3 CODE-READ — reap-drive exonerated; the seam is a SharedSend self-deadlock on the brain conn (todlando, 2026-07-09)\n\nReport-before-fix per RULING 2 §5/§8. doyle asked: pin what the counter-54 reap-drive\n(ce053dc) blocks on each tick, especially whether an UNRESPONSIVE peer lengthens the tick\n>15s. Answer: **it doesn't block on anything — and the real wedge is a different seam the\nreap-drive only widened.**\n\n## 1. The reap-drive does NOT block (doyle's dead-peer-tick hypothesis — FALSIFIED by code)\n\n`run_brain`'s heartbeat loop (brainproc.rs:239-278) per tick: `thread::sleep(500ms)` →\n`brain.net_status()` → spawn-consumers-once → `brain.sessions()` (the counter-54 reap-drive,\nbrainproc.rs:276) → `write_ready`. It hosts NO PTY sessions, so it does not itself drain PTY\noutput. Neither IPC call blocks on a dead peer:\n\n- **`net_status()` is a cheap field read.** `dispatch_net_status` (broker.rs:3573) returns\n  `node_id_hex()` / `addr_json()` / `conn_count()` — no `bounded_block_on`, no peer wait.\n  `conn_count` takes `shared.conns.lock()` only briefly; a dead-peer connect never reaches\n  `register_conn`, so it never holds that lock.\n- **`sessions()` reap takes each OutputLog lock only briefly.** The `KIND_SESSIONS` handler\n  (broker.rs:2853-2926) snapshots each session under its OWN log lock, calls\n  `reap_dead_controller` (968), reads the cursor, releases; `converge_perch_stamps` runs\n  OFF-lock. No peer op anywhere.\n- **`append` is non-blocking under the log lock.** The producer fan-out (broker.rs:790-849)\n  is `try_send` to controller (827) and viewers (802) — never a blocking send — so it holds\n  the log lock only momentarily. The reap's `recover_log` never queues behind it.\n- **The controller writer holds the SOCKET mutex, never the log lock, across its blocking\n  write** (`controller_writer`, broker.rs:1418; docstring 1409-1411: *\"a backed-up controller\n  blocks ONLY this thread, never the session drain which used to write inline under the log\n  lock\"*). So a WEDGED writer cannot block the reap either.\n- **Viewers drop, never block the producer** (`try_send` + eviction, broker.rs:1086/802).\n\nConstants: `BRAIN_HEARTBEAT` = 500ms, `BRAIN_WRITE_DEADLINE` = 15s. Net: the reap-drive tick\nis ~ms even with dead peers present. There is NO code path by which an unresponsive peer\nlengthens it. **`REQ`-shaping the fix as \"make the reap-drive non-blocking\" targets a block\nthat does not exist.**\n\n## 2. The REAL seam — a SharedSend self-deadlock on the brain conn\n\n`SharedSend = Arc<Mutex<SendHalf>>` (broker.rs:78): ONE mutex guards each conn's send half.\nOn the brain conn, that one mutex is contended by TWO writer classes:\n\n- **Subscriber writers** — `viewer_writer` (broker.rs:1333/1342) and `controller_writer`\n  (1451) — take `send.lock()` and hold it ACROSS the blocking `write_frame` to the conn.\n- **The dispatch reply path** — `send_frame` (broker.rs:4221) — locks the SAME `send.lock()`\n  to write `KIND_SESSIONS_REPLY` (2926) / `KIND_NET_STATUS_REPLY` (3592).\n\nThe brain conn is BOTH a subscriber sink (resume re-subscribes N sessions onto it) AND the\nbrain's request/reply channel. So:\n\n> when the brain conn backs up (the brain isn't draining fast enough — it only reads during\n> the 500ms heartbeat's `net_status`/`sessions` calls), a subscriber writer BLOCKS inside\n> `write_frame` while HOLDING `send.lock()` → the dispatch thread cannot acquire `send.lock()`\n> to send the heartbeat reply → `net_status()`/`sessions()` never return → the heartbeat loop\n> is stuck → the brain never drains its conn → the subscriber writer stays blocked. **A\n> self-reinforcing deadlock on the brain conn's send mutex.**\n\nOnce the heartbeat is stuck, the brain drains nothing; every subscriber writer on the brain\nconn wedges; the broker-side controller writers whose sink is the brain conn cross\n`BRAIN_WRITE_DEADLINE` → `BRAIN_SUBSCRIBER_STALL_EVICT`. Matches the field exactly.\n\n### Why counter-54 is the regression window (but not the root)\nce053dc added a SECOND per-heartbeat reply round-trip (`brain.sessions()`) through the\ncontended brain-conn send mutex. It did not create the deadlock — it made the heartbeat\ndepend on TWO reply round-trips per tick surviving the mutex, widening the stall window and\ntightening the coupling. Regression-window-consistent; the underlying send-mutex sharing is\nolder.\n\n### Why the Viewer fix (v0.30.4) did NOT heal it\nThe round-2 Viewer fix removed the resume-STEAL (no more `become_controller`), but the brain\nis STILL a subscriber — `viewer_writer` has the SAME `send.lock()`-across-blocking-write\npattern (1333/1342) AND viewers have NO stall-evict safety valve (only controllers are\nreaped). So a blocked brain-viewer writer holds the send mutex indefinitely → the deadlock\npersists. This is why v0.30.4 field-verify wedged again.\n\n### The dead peer is NOT causal\nThe trigger is active-session OUTPUT backing up the brain conn faster than the 500ms\nheartbeat drains it — on respawn (resume subscribes N + replay burst floods the conn before\nthe loop drains) and in steady state (an active streamer's output between heartbeats fills the\nsocket buffer; on v0.30.3 additionally compounded by the steal). The field always had dead\npeers, but they are coincidental load, not the mechanism.\n\n## 3. Fix shape (proposed — doyle to rule) — BRAIN-SIDE, seamless\n\nThe daemon brain hosts NO PTY sessions (brainproc.rs:184), so it has NO consumer for the\nsubscriptions `resume_sessions` re-establishes — it floods its own request/reply conn with\noutput it never uses. Options, preference order:\n\n- **(A) resume_sessions does NOT re-subscribe.** Drop the undrained continuity subscription\n  entirely — the daemon brain has no consumer for it today. The brain conn then carries only\n  request/reply → no subscriber backpressure → no deadlock. Smallest, brain-side, seamless.\n  Forward-seam: when genuinely daemon-driven sessions land (the live-agent adapter), THOSE\n  attach on a drained carrier (option B).\n- **(B) Drain the brain conn on a DEDICATED reader thread** (`BrainConn::Split` — the exact\n  carrier the pump already uses, brain.rs:230) so the heartbeat reply is never blocked by\n  subscriber backpressure, and subscriptions can persist. Brain-side, seamless.\n- **(C) [broker-side — deployment risk, FLAG] subscriber writers use a bounded/non-blocking\n  write** instead of holding `send.lock()` across a blocking `write_frame`. Durable root but\n  broker-side ⇒ needs a coordinated daemon restart to land (the daemon.rs:368 constraint).\n\nPrefer **A or B** (brain-side ⇒ rides the brain-swap ⇒ seamless; the deployment risk lifts).\nBoth touch ONLY the brain (brain.rs / brainproc.rs). C touches the broker — flagged.\n\n## 4. Confirmer rig (RULING 2 §6, still authorized)\nThe mechanism is a code-proven deadlock, so the rig CONFIRMS + GUARDS rather than discovers.\nbrain+broker+PTY (`brain_decouple` template): N sessions actively producing output, the brain\nresume-subscribed onto its request/reply conn; RED-first — the heartbeat stalls and an\nactive-streaming controller is stall-evicted >15s WITHOUT a dead peer needing to be present\n(proving dead-peer-independence). Assert BOTH severities (respawn interleave + steady-state\noutput backup). Fix A/B must turn both green. Dead-peer arm optional (to show it is not the\ntrigger).\n\n## 5. Sequence\nReported. doyle to rule the shape (A vs B) → mint the REQ (candidate\n`REQ-BRAIN-CONN-NO-SUBSCRIBER-DEADLOCK` or similar) → build the confirmer rig RED-first → fix →\nboth severities green. No REQ / no fix until doyle rules.\n"}],"details":{"displayContent":{"text":"# UPDATE-WEDGE round-3 CODE-READ — reap-drive exonerated; the seam is a SharedSend self-deadlock on the brain conn (todlando, 2026-07-09)\r\n\r\nReport-before-fix per RULING 2 §5/§8. doyle asked: pin what the counter-54 reap-drive\r\n(ce053dc) blocks on each tick, especially whether an UNRESPONSIVE peer lengthens the tick\r\n>15s. Answer: **it doesn't block on anything — and the real wedge is a different seam the\r\nreap-drive only widened.**\r\n\r\n## 1. The reap-drive does NOT block (doyle's dead-peer-tick hypothesis — FALSIFIED by code)\r\n\r\n`run_brain`'s heartbeat loop (brainproc.rs:239-278) per tick: `thread::sleep(500ms)` →\r\n`brain.net_status()` → spawn-consumers-once → `brain.sessions()` (the counter-54 reap-drive,\r\nbrainproc.rs:276) → `write_ready`. It hosts NO PTY sessions, so it does not itself drain PTY\r\noutput. Neither IPC call blocks on a dead peer:\r\n\r\n- **`net_status()` is a cheap field read.** `dispatch_net_status` (broker.rs:3573) returns\r\n  `node_id_hex()` / `addr_json()` / `conn_count()` — no `bounded_block_on`, no peer wait.\r\n  `conn_count` takes `shared.conns.lock()` only briefly; a dead-peer connect never reaches\r\n  `register_conn`, so it never holds that lock.\r\n- **`sessions()` reap takes each OutputLog lock only briefly.** The `KIND_SESSIONS` handler\r\n  (broker.rs:2853-2926) snapshots each session under its OWN log lock, calls\r\n  `reap_dead_controller` (968), reads the cursor, releases; `converge_perch_stamps` runs\r\n  OFF-lock. No peer op anywhere.\r\n- **`append` is non-blocking under the log lock.** The producer fan-out (broker.rs:790-849)\r\n  is `try_send` to controller (827) and viewers (802) — never a blocking send — so it holds\r\n  the log lock only momentarily. The reap's `recover_log` never queues behind it.\r\n- **The controller writer holds the SOCKET mutex, never the log lock, across its blocking\r\n  write** (`controller_writer`, broker.rs:1418; docstring 1409-1411: *\"a backed-up controller\r\n  blocks ONLY this thread, never the session drain which used to write inline under the log\r\n  lock\"*). So a WEDGED writer cannot block the reap either.\r\n- **Viewers drop, never block the producer** (`try_send` + eviction, broker.rs:1086/802).\r\n\r\nConstants: `BRAIN_HEARTBEAT` = 500ms, `BRAIN_WRITE_DEADLINE` = 15s. Net: the reap-drive tick\r\nis ~ms even with dead peers present. There is NO code path by which an unresponsive peer\r\nlengthens it. **`REQ`-shaping the fix as \"make the reap-drive non-blocking\" targets a block\r\nthat does not exist.**\r\n\r\n## 2. The REAL seam — a SharedSend self-deadlock on the brain conn\r\n\r\n`SharedSend = Arc<Mutex<SendHalf>>` (broker.rs:78): ONE mutex guards each conn's send half.\r\nOn the brain conn, that one mutex is contended by TWO writer classes:\r\n\r\n- **Subscriber writers** — `viewer_writer` (broker.rs:1333/1342) and `controller_writer`\r\n  (1451) — take `send.lock()` and hold it ACROSS the blocking `write_frame` to the conn.\r\n- **The dispatch reply path** — `send_frame` (broker.rs:4221) — locks the SAME `send.lock()`\r\n  to write `KIND_SESSIONS_REPLY` (2926) / `KIND_NET_STATUS_REPLY` (3592).\r\n\r\nThe brain conn is BOTH a subscriber sink (resume re-subscribes N sessions onto it) AND the\r\nbrain's request/reply channel. So:\r\n\r\n> when the brain conn backs up (the brain isn't draining fast enough — it only reads during\r\n> the 500ms heartbeat's `net_status`/`sessions` calls), a subscriber writer BLOCKS inside\r\n> `write_frame` while HOLDING `send.lock()` → the dispatch thread cannot acquire `send.lock()`\r\n> to send the heartbeat reply → `net_status()`/`sessions()` never return → the heartbeat loop\r\n> is stuck → the brain never drains its conn → the subscriber writer stays blocked. **A\r\n> self-reinforcing deadlock on the brain conn's send mutex.**\r\n\r\nOnce the heartbeat is stuck, the brain drains nothing; every subscriber writer on the brain\r\nconn wedges; the broker-side controller writers whose sink is the brain conn cross\r\n`BRAIN_WRITE_DEADLINE` → `BRAIN_SUBSCRIBER_STALL_EVICT`. Matches the field exactly.\r\n\r\n### Why counter-54 is the regression window (but not the root)\r\nce053dc added a SECOND per-heartbeat reply round-trip (`brain.sessions()`) through the\r\ncontended brain-conn send mutex. It did not create the deadlock — it made the heartbeat\r\ndepend on TWO reply round-trips per tick surviving the mutex, widening the stall window and\r\ntightening the coupling. Regression-window-consistent; the underlying send-mutex sharing is\r\nolder.\r\n\r\n### Why the Viewer fix (v0.30.4) did NOT heal it\r\nThe round-2 Viewer fix removed the resume-STEAL (no more `become_controller`), but the brain\r\nis STILL a subscriber — `viewer_writer` has the SAME `send.lock()`-across-blocking-write\r\npattern (1333/1342) AND viewers have NO stall-evict safety valve (only controllers are\r\nreaped). So a blocked brain-viewer writer holds the send mutex indefinitely → the deadlock\r\npersists. This is why v0.30.4 field-verify wedged again.\r\n\r\n### The dead peer is NOT causal\r\nThe trigger is active-session OUTPUT backing up the brain conn faster than the 500ms\r\nheartbeat drains it — on respawn (resume subscribes N + replay burst floods the conn before\r\nthe loop drains) and in steady state (an active streamer's output between heartbeats fills the\r\nsocket buffer; on v0.30.3 additionally compounded by the steal). The field always had dead\r\npeers, but they are coincidental load, not the mechanism.\r\n\r\n## 3. Fix shape (proposed — doyle to rule) — BRAIN-SIDE, seamless\r\n\r\nThe daemon brain hosts NO PTY sessions (brainproc.rs:184), so it has NO consumer for the\r\nsubscriptions `resume_sessions` re-establishes — it floods its own request/reply conn with\r\noutput it never uses. Options, preference order:\r\n\r\n- **(A) resume_sessions does NOT re-subscribe.** Drop the undrained continuity subscription\r\n  entirely — the daemon brain has no consumer for it today. The brain conn then carries only\r\n  request/reply → no subscriber backpressure → no deadlock. Smallest, brain-side, seamless.\r\n  Forward-seam: when genuinely daemon-driven sessions land (the live-agent adapter), THOSE\r\n  attach on a drained carrier (option B).\r\n- **(B) Drain the brain conn on a DEDICATED reader thread** (`BrainConn::Split` — the exact\r\n  carrier the pump already uses, brain.rs:230) so the heartbeat reply is never blocked by\r\n  subscriber backpressure, and subscriptions can persist. Brain-side, seamless.\r\n- **(C) [broker-side — deployment risk, FLAG] subscriber writers use a bounded/non-blocking\r\n  write** instead of holding `send.lock()` across a blocking `write_frame`. Durable root but\r\n  broker-side ⇒ needs a coordinated daemon restart to land (the daemon.rs:368 constraint).\r\n\r\nPrefer **A or B** (brain-side ⇒ rides the brain-swap ⇒ seamless; the deployment risk lifts).\r\nBoth touch ONLY the brain (brain.rs / brainproc.rs). C touches the broker — flagged.\r\n\r\n## 4. Confirmer rig (RULING 2 §6, still authorized)\r\nThe mechanism is a code-proven deadlock, so the rig CONFIRMS + GUARDS rather than discovers.\r\nbrain+broker+PTY (`brain_decouple` template): N sessions actively producing output, the brain\r\nresume-subscribed onto its request/reply conn; RED-first — the heartbeat stalls and an\r\nactive-streaming controller is stall-evicted >15s WITHOUT a dead peer needing to be present\r\n(proving dead-peer-independence). Assert BOTH severities (respawn interleave + steady-state\r\noutput backup). Fix A/B must turn both green. Dead-peer arm optional (to show it is not the\r\ntrigger).\r\n\r\n## 5. Sequence\r\nReported. doyle to rule the shape (A vs B) → mint the REQ (candidate\r\n`REQ-BRAIN-CONN-NO-SUBSCRIBER-DEADLOCK` or similar) → build the confirmer rig RED-first → fix →\r\nboth severities green. No REQ / no fix until doyle rules.\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\UPDATE-WEDGE-2-ROUND3-CODEREAD.md"}}},"isError":false,"timestamp":1783730517309}}
{"type":"message","id":"9d5c46d5","parentId":"2aff0a3c","timestamp":"2026-07-11T00:41:57.309Z","message":{"role":"toolResult","toolCallId":"call_oG6Jgf1hagm10V95ORFI9sha|fc_08496348794f7e09016a519155c7d48198a8d19e940fd8071b","toolName":"read","content":[{"type":"text","text":"# UPDATE-WEDGE round-3 RIG VERDICT — net-runtime starvation seam FALSIFIED (todlando, 2026-07-09)\n\nReport-before-fix per `docs/UPDATE-WEDGE-2-ROUND3-RULING.md` §7. Rig lives in the\n`uwedge2` worktree: `crates/spt-daemon/tests/net_worker_starve.rs` + a throwaway\nDIAGNOSTIC probe on `crates/spt-daemon/src/nethost.rs` (`active_dial_tasks()`,\n`net_canary_age_ms()`, `dial_permits_available()` — no REQ, marked throwaway).\n\n## (a) Mechanism confirmed? — NO. FALSIFIED, robustly, across both dead-peer shapes.\n\nThe ruling's decider (§5): do dead-peer `submit_dial`s HOLD/serialize the broker\nnet runtime's 2 worker threads (starving the reactor that services LOCAL loopback\n`rc` I/O), or do the QUIC awaits YIELD? **They yield.**\n\nRig: dialer with a 3s QUIC-op bound; fire K=12 concurrent `submit_dial`s straight\nonto the broker's own `NetHost` (the exact pump path); read the reactor-liveness\ncanary (a runtime task stamping epoch-ms every 25ms — a stale stamp == workers\nmonopolized).\n\n| dead-peer shape | K in-flight | peak canary age | dial permits free |\n|---|---|---|---|\n| proof-hang (accepts QUIC handshake, black-holes the seed-proof — netbroker.rs dead peer) | 12 | **32 ms** | 12 |\n| unreachable (id-only `addr_for_node_hex`, no path — the field's \"peer unresponsive\" ecb39e40/cfd9fc55 shape) | 12 | **33 ms** | 12 |\n\n12 in-flight dead-peer dials (≫ the 2 workers) held the canary at **32–33 ms** vs\nthe **15 000 ms** stall-evict threshold — ~450× headroom. Both connect shapes\n(proof-read-hang AND relay-less/discovery-off unreachable-connect) yield cleanly;\n2 workers multiplex 12 dead dials without perturbation. The 24-permit dial\nsemaphore is never the limiter (12 free throughout). Locked as passing asserts so\na future non-yielding regression re-fires the investigation.\n\n## (b) Boundary map\n\n- **Loopback `rc` I/O** (`open_stream` attach @nethost.rs:1226, `send_stream`\n  controller-output @1301) is driven by `bounded_block_on` → `runtime.handle()\n  .block_on(fut)` on a **broker DISPATCH/reply thread** (NOT a net worker). The\n  future's `tokio::io::duplex` readiness is serviced by the 2-worker net reactor —\n  **PROVEN healthy under a 12-deep dead-peer dial burst** (canary fresh).\n- **Dead-peer connects** (`submit_dial` @nethost.rs:1036) run as `runtime.spawn`\n  tasks on the 2 workers, bounded by `quic_op_timeout`. They are cooperative\n  (yield at every await); 12 coexist with zero reactor impact.\n- **The field stall-evict** is *\"controller writer blocked >15000ms on a\n  suspended/black-holed **brain conn**\"*. The blocked writer is the broker → BRAIN\n  IPC path; it unblocks only when the **brain READS/drains** its side. That drain\n  is the brain main loop's job — it is **not** on the broker net runtime at all.\n\nSo the net runtime is EXONERATED as the wedge seam. The `submit-dial exceeded the\n10s bound` field lines are the *expected, benign* dead-peer timeout — not evidence\nof runtime saturation. The wedge coupling lives at the **brain main-loop drain**.\n\n## (c) Does shape C suffice? — MOOT. C's (and A's) premise is false.\n\nBoth ruled shapes target net-worker starvation by a dial burst:\n- **C** (brain bounds/staggers dead-peer dial *requests*) — there is no burst-driven\n  worker starvation to relieve; staggering dials changes nothing about the brain's\n  drain.\n- **A** (isolate loopback I/O onto its own runtime) — the loopback lane is already\n  proven un-starved by peer work; isolating it fixes a starvation that isn't there.\n\n**Do NOT mint `REQ-BRAIN-DIAL-BOUND-NO-STARVE` or `REQ-NET-LOOPBACK-NO-PEER-STARVE`.**\nNeither closes the field wedge.\n\n## Re-open — where the wedge actually is (HYPOTHESIS for doyle's next ruling)\n\nThe stall is the **brain main loop failing to DRAIN controller output sinks for\n>15s**. The net runtime is clean, so a peer op stalls the drain only if it runs ON\n/ blocks the brain's main (drain) loop. Two sub-candidates, both inside the\nv0.30.3 (counter-54) regression window the field pinned:\n\n1. **counter-54 reap-drive (ce053dc) — PRIME suspect for the STEADY-STATE\n   doyle-only wedge.** v0.30.3 added \"brain self-drives the controller reap\" in the\n   main loop (every ~500ms). If that drive does a blocking net/IPC op or contends a\n   lock with the peer path, it stalls the very drain it shares a thread with — with\n   NO respawn — which is exactly the never-before-v0.30.3 steady-state wedge.\n2. **Startup sequencing (respawn severity) — the round-2-deferred stagger.** The new\n   brain runs net-consumer setup + initial peer dials interleaved with entering its\n   drain loop; resumed controllers go undrained across the dead-peer-dial window →\n   stall-evict on respawn.\n\nNext rig (needs doyle's authorization — brain+broker+PTY stack, `brain_decouple`\ntemplate + a real pump round with a dead peer): assert whether (1) the counter-54\nreap-drive and/or (2) startup sequencing blocks the brain from draining an\nactively-streaming controller >15s. That is the real decider now. The pure-NetHost\nStage-1 rig has done its job: it removed the net runtime from suspicion.\n\n## Runner\n\n`cargo nextest run -p spt-daemon --test net_worker_starve` (or `--test-threads=1`).\nHermetic (relays disabled, no discovery, loopback QUIC); no SPT_HOME/PTY needed.\n"}],"details":{"displayContent":{"text":"# UPDATE-WEDGE round-3 RIG VERDICT — net-runtime starvation seam FALSIFIED (todlando, 2026-07-09)\r\n\r\nReport-before-fix per `docs/UPDATE-WEDGE-2-ROUND3-RULING.md` §7. Rig lives in the\r\n`uwedge2` worktree: `crates/spt-daemon/tests/net_worker_starve.rs` + a throwaway\r\nDIAGNOSTIC probe on `crates/spt-daemon/src/nethost.rs` (`active_dial_tasks()`,\r\n`net_canary_age_ms()`, `dial_permits_available()` — no REQ, marked throwaway).\r\n\r\n## (a) Mechanism confirmed? — NO. FALSIFIED, robustly, across both dead-peer shapes.\r\n\r\nThe ruling's decider (§5): do dead-peer `submit_dial`s HOLD/serialize the broker\r\nnet runtime's 2 worker threads (starving the reactor that services LOCAL loopback\r\n`rc` I/O), or do the QUIC awaits YIELD? **They yield.**\r\n\r\nRig: dialer with a 3s QUIC-op bound; fire K=12 concurrent `submit_dial`s straight\r\nonto the broker's own `NetHost` (the exact pump path); read the reactor-liveness\r\ncanary (a runtime task stamping epoch-ms every 25ms — a stale stamp == workers\r\nmonopolized).\r\n\r\n| dead-peer shape | K in-flight | peak canary age | dial permits free |\r\n|---|---|---|---|\r\n| proof-hang (accepts QUIC handshake, black-holes the seed-proof — netbroker.rs dead peer) | 12 | **32 ms** | 12 |\r\n| unreachable (id-only `addr_for_node_hex`, no path — the field's \"peer unresponsive\" ecb39e40/cfd9fc55 shape) | 12 | **33 ms** | 12 |\r\n\r\n12 in-flight dead-peer dials (≫ the 2 workers) held the canary at **32–33 ms** vs\r\nthe **15 000 ms** stall-evict threshold — ~450× headroom. Both connect shapes\r\n(proof-read-hang AND relay-less/discovery-off unreachable-connect) yield cleanly;\r\n2 workers multiplex 12 dead dials without perturbation. The 24-permit dial\r\nsemaphore is never the limiter (12 free throughout). Locked as passing asserts so\r\na future non-yielding regression re-fires the investigation.\r\n\r\n## (b) Boundary map\r\n\r\n- **Loopback `rc` I/O** (`open_stream` attach @nethost.rs:1226, `send_stream`\r\n  controller-output @1301) is driven by `bounded_block_on` → `runtime.handle()\r\n  .block_on(fut)` on a **broker DISPATCH/reply thread** (NOT a net worker). The\r\n  future's `tokio::io::duplex` readiness is serviced by the 2-worker net reactor —\r\n  **PROVEN healthy under a 12-deep dead-peer dial burst** (canary fresh).\r\n- **Dead-peer connects** (`submit_dial` @nethost.rs:1036) run as `runtime.spawn`\r\n  tasks on the 2 workers, bounded by `quic_op_timeout`. They are cooperative\r\n  (yield at every await); 12 coexist with zero reactor impact.\r\n- **The field stall-evict** is *\"controller writer blocked >15000ms on a\r\n  suspended/black-holed **brain conn**\"*. The blocked writer is the broker → BRAIN\r\n  IPC path; it unblocks only when the **brain READS/drains** its side. That drain\r\n  is the brain main loop's job — it is **not** on the broker net runtime at all.\r\n\r\nSo the net runtime is EXONERATED as the wedge seam. The `submit-dial exceeded the\r\n10s bound` field lines are the *expected, benign* dead-peer timeout — not evidence\r\nof runtime saturation. The wedge coupling lives at the **brain main-loop drain**.\r\n\r\n## (c) Does shape C suffice? — MOOT. C's (and A's) premise is false.\r\n\r\nBoth ruled shapes target net-worker starvation by a dial burst:\r\n- **C** (brain bounds/staggers dead-peer dial *requests*) — there is no burst-driven\r\n  worker starvation to relieve; staggering dials changes nothing about the brain's\r\n  drain.\r\n- **A** (isolate loopback I/O onto its own runtime) — the loopback lane is already\r\n  proven un-starved by peer work; isolating it fixes a starvation that isn't there.\r\n\r\n**Do NOT mint `REQ-BRAIN-DIAL-BOUND-NO-STARVE` or `REQ-NET-LOOPBACK-NO-PEER-STARVE`.**\r\nNeither closes the field wedge.\r\n\r\n## Re-open — where the wedge actually is (HYPOTHESIS for doyle's next ruling)\r\n\r\nThe stall is the **brain main loop failing to DRAIN controller output sinks for\r\n>15s**. The net runtime is clean, so a peer op stalls the drain only if it runs ON\r\n/ blocks the brain's main (drain) loop. Two sub-candidates, both inside the\r\nv0.30.3 (counter-54) regression window the field pinned:\r\n\r\n1. **counter-54 reap-drive (ce053dc) — PRIME suspect for the STEADY-STATE\r\n   doyle-only wedge.** v0.30.3 added \"brain self-drives the controller reap\" in the\r\n   main loop (every ~500ms). If that drive does a blocking net/IPC op or contends a\r\n   lock with the peer path, it stalls the very drain it shares a thread with — with\r\n   NO respawn — which is exactly the never-before-v0.30.3 steady-state wedge.\r\n2. **Startup sequencing (respawn severity) — the round-2-deferred stagger.** The new\r\n   brain runs net-consumer setup + initial peer dials interleaved with entering its\r\n   drain loop; resumed controllers go undrained across the dead-peer-dial window →\r\n   stall-evict on respawn.\r\n\r\nNext rig (needs doyle's authorization — brain+broker+PTY stack, `brain_decouple`\r\ntemplate + a real pump round with a dead peer): assert whether (1) the counter-54\r\nreap-drive and/or (2) startup sequencing blocks the brain from draining an\r\nactively-streaming controller >15s. That is the real decider now. The pure-NetHost\r\nStage-1 rig has done its job: it removed the net runtime from suspicion.\r\n\r\n## Runner\r\n\r\n`cargo nextest run -p spt-daemon --test net_worker_starve` (or `--test-threads=1`).\r\nHermetic (relays disabled, no discovery, loopback QUIC); no SPT_HOME/PTY needed.\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md"}}},"isError":false,"timestamp":1783730517309}}
{"type":"message","id":"eebd5fb4","parentId":"9d5c46d5","timestamp":"2026-07-11T00:41:57.312Z","message":{"role":"toolResult","toolCallId":"call_4vokafKkZyzDRKS2vUtuCFe5|fc_08496348794f7e09016a519155c7dc819888cc3649a33b8fc4","toolName":"read","content":[{"type":"text","text":"# Update-wedge root cause: non-draining controller pipe\n\n**Status:** Root mechanism reproduced and source-pinned on Windows. The historical process identity that stopped draining is not proven by the old logs.\n\n## Ruling\n\nA broker controller writer can block indefinitely while writing a framed event to a connected controller consumer that has stopped reading. Logical stall eviction removes the controller role, but it neither cancels the in-flight Windows named-pipe write nor closes the physical connection. The detached writer retains its `SharedSend` clone and mutex guard until the peer or connection is later torn down.\n\nThis is load-gated. Cold restart and update promotion are not sufficient: the controlled quiescent update completed cleanly. A real nonzero-sequence controller frame plus a non-draining consumer reproduced the field signature deterministically.\n\n## Reproduction evidence\n\nCapture: `C:\\Users\\decid\\AppData\\Local\\spt-core\\rca-active-20260709-222659`\n\nThe four deliberate remote `Take` subscriptions installed epoch-2 controllers:\n\n| Connection | Session | Install marker |\n|---:|---:|---|\n| 227 | 4 | `source=subscribe intent=take by=remote` |\n| 228 | 3 | `source=subscribe intent=take by=remote` |\n| 229 | 1 | `source=subscribe intent=take by=remote` |\n| 230 | 2 | `source=subscribe intent=take by=remote` |\n\nEach writer emitted `CTRL_WRITE_WAIT`, then `CTRL_WRITE_LOCKED wait_us=0`, and no `CTRL_WRITE_DONE`. This rules out mutex wait for the reproduction: the block occurred after lock acquisition, inside the frame write. An independent sessions query approximately 18.5 seconds later observed the aged writes and emitted four `BRAIN_SUBSCRIBER_STALL_EVICT` records. The writes remained blocked after logical eviction. They returned `result=error` only when a later brain restart closed the connections, after approximately 127.953 seconds in `write_frame`.\n\nThe saved `status.active.json` is intentionally the pre-recovery capture state (`broker_image=0.30.0`, `broker_stale=true`, `stall_evict_count=8`). The separately verified post-recovery production state was signed `0.30.5`, `broker_stale=false`, and `stall_evict_count=0`.\n\n## Exact code path\n\n1. `spt rc --take` selects `AttachIntent::Take` in `crates/spt/src/cli.rs` and enters `run_attach_inner` in `crates/spt/src/rc.rs`.\n2. The target attach worker calls `Brain::subscribe_with`; the broker routes `KIND_SUBSCRIBE` through `dispatch_subscribe` and `resolve_subscribe` in `crates/spt-daemon/src/broker.rs`.\n3. `OutputLog::become_controller` creates the existing bounded live-output channel and spawns `controller_writer` with a cloned connection `SharedSend`.\n4. `SharedSend` is `Arc<Mutex<SendHalf>>` (`crates/spt-daemon/src/broker.rs`, near the top-level type aliases).\n5. Both initial replay and live-output paths in `controller_writer` mark the writer blocked, acquire `send.lock()`, and retain the `MutexGuard<SendHalf>` across `write_frame`.\n6. `write_frame` in `crates/spt-daemon/src/codec.rs` serializes the envelope and synchronously calls `write_all` for the four-byte length prefix and JSON body.\n7. On Windows, `interprocess 2.4.2` routes the `SendHalf` write to `WriteFileEx`, then waits through `SleepEx(INFINITE, alertable=true)` for the asynchronous completion callback. This local-socket send path exposes no supported write timeout; `set_timeout` returns `Unsupported`.\n\nInstrumented capture-worktree anchors at commit `5d3723f`:\n\n- `crates/spt-daemon/src/broker.rs:73-77`: `SharedSend`.\n- `broker.rs:1048-1108`: controller installation, queue, send clone, writer spawn.\n- `broker.rs:1664-1716`: initial replay write.\n- `broker.rs:1718-1760`: live-output write.\n- `crates/spt-daemon/src/codec.rs:21-35`: length-prefixed blocking `write_all` calls.\n\n## Why existing deadlines do not bound the write\n\n`CONTROLLER_WRITE_DEADLINE` governs output-driven bounded-channel-full handling. It is not a socket-write deadline. A writer blocked on its first owned frame may never fill that channel.\n\n`BRAIN_WRITE_DEADLINE` is an age predicate over `write_blocked_since`, not an I/O timer. It is sampled opportunistically during controller resolution or session snapshots. Therefore 15 seconds is a minimum stale age when observed, not a guaranteed interrupt time.\n\n`mark_blocked(true)` occurs before mutex acquisition. The diagnostic `CTRL_WRITE_LOCKED` marker was added after acquisition; the captured `wait_us=0` values isolate the block to the OS write.\n\n## Why logical eviction does not release the writer\n\n`stall_evict_controller`, `clear_controller`, and `detach_if` remove the `ControllerSink` and update controller stamps. They do not:\n\n- interrupt or join the writer thread;\n- shut down the connection;\n- invoke `CancelIoEx`;\n- release the writer's stack-owned mutex guard;\n- make the current `write_frame` observe channel disconnection.\n\nDropping the sink drops a stored `JoinHandle` and one `SharedSend` clone. The already-running writer owns another clone and, in the failing state, the live guard. A disconnected queue can only be observed after the current write returns.\n\n## Rejected causes\n\n- **Update/promotion alone:** rejected by the quiescent controlled update, which promoted v56 without a new eviction.\n- **Cold-start orphan controller:** rejected. Spawn connections 3-6 wrote `seq:0`, reached EOF, and detached with `arc_matched=true cleared=true` within milliseconds.\n- **Shared mutex convoy:** rejected for this reproduction by `CTRL_WRITE_LOCKED wait_us=0` on all four writers.\n- **Broker-side scheduler starvation:** not required. Other broker, PTY, and bounded-child operations continued while the controller writes remained blocked.\n\nProcess pressure may explain why a field consumer stopped draining, but it is not the pinned broker-side blocking mechanism.\n\n## Recommended fix shape\n\nImplement one broker-owned bounded/cancelable framed-write primitive for every write on a physical broker connection. Replace raw `Arc<Mutex<SendHalf>>` use with an `Arc`-owned connection object containing:\n\n- the serialized write gate;\n- the send half;\n- an idempotent poisoned/aborted state;\n- per-in-flight-operation identity;\n- a platform abort capability.\n\nThe absolute bound must cover both waiting for the serialization gate and OS write completion. On deadline, partial write, cancellation, or unknown completion:\n\n1. atomically poison the whole physical connection;\n2. abort both read and write sides so `handle_conn` reaches existing EOF cleanup;\n3. wait until the canceled operation reports completion before releasing its buffer/overlapped state;\n4. never reuse the connection, because a timed-out length-prefixed frame may have been partially written;\n5. join the retired writer before reporting physical cleanup complete.\n\nRoute controller, viewer, dispatch reply, and terminal-control writes through the same primitive. Leaving any raw unbounded write behind the same gate preserves the failure class.\n\nDo not add another output queue: the bounded queue and isolated writer already exist, and the block is below them. Do not use `PIPE_NOWAIT`: this repository already records its mid-frame corruption risk. A heartbeat is optional telemetry, not a replacement for a write bound.\n\n### Rollout\n\nBroker-side only; no wire-format change and no coordinated multi-node protocol rollout. The running broker must restart to load the fix. Mixed-version peers remain wire-compatible. Risk is concentrated in Windows cancellation correctness and connection-wide teardown; Unix behavior must retain its existing semantics while using the same poison/retire invariant.\n\n## Regression gate\n\nAdd a Windows integration test in `crates/spt-daemon/tests/brain_decouple.rs`:\n\n```rust\n#[cfg(windows)]\n#[test]\nfn non_draining_controller_stall_evict_releases_writer_and_connection()\n```\n\nReuse the real broker/PTY helpers from `suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks`. Required sequence:\n\n1. use an isolated broker and shrink `SPT_BRAIN_WRITE_DEADLINE_MS` to 400 ms;\n2. spawn a real PTY child that emits `SEED`, then a finite 1-2 MiB burst on separate input triggers;\n3. install a remote controller and drain through `SEED`, proving a real `seq > 0` frame;\n4. trigger the finite burst and stop reading that controller connection;\n5. prove the PTY/ring high-water advances by at least 64 frames;\n6. use an independent draining control connection to trigger and observe exactly one logical stall eviction;\n7. without dropping the black-holed client, require that connection's `handle_conn` thread to finish within two seconds of logical release;\n8. assert a write on the old client connection fails;\n9. attach a fresh controller from the frozen resume cursor and assert the first replayed sequence equals that cursor.\n\nPre-fix RED: logical eviction succeeds, but the old handler/writer does not finish until the test drops the client or process exits.\n\nPost-fix GREEN: the deadline poisons and closes the old physical connection, the writer and handler finish within the bound, and a fresh controller resumes without losing the unaccepted frame.\n\nThe existing test proves logical isolation and viewer progress; it must remain as the cross-platform companion. The new test proves physical writer/connection retirement on the exact Windows failure mode.\n\n## Manual field-acceptance reproduction\n\nHarness: `scripts/rca/blackhole-controller.ps1` in the RCA capture branch.\n\nRun against disposable live endpoints only:\n\n```powershell\npowershell -NoProfile -ExecutionPolicy Bypass -File scripts/rca/blackhole-controller.ps1 \\\n  -Endpoint deployah,doyle,flynn,lia\n```\n\nThe harness launches `spt rc <id> --take` with stdout redirected to anonymous pipes that the parent deliberately never reads. Drive real PTY output in each endpoint. Before the fix, controller writes enter `LOCKED` without `DONE`, logical eviction occurs, and the child connections remain physically alive. After the fix, each blocked connection must be poisoned and terminated within the configured write bound, and a fresh `spt rc --take` must recover without a daemon restart.\n\nDo not run this harness on production sessions: it deliberately steals controller ownership and black-holes output.\n"}],"details":{"displayContent":{"text":"# Update-wedge root cause: non-draining controller pipe\n\n**Status:** Root mechanism reproduced and source-pinned on Windows. The historical process identity that stopped draining is not proven by the old logs.\n\n## Ruling\n\nA broker controller writer can block indefinitely while writing a framed event to a connected controller consumer that has stopped reading. Logical stall eviction removes the controller role, but it neither cancels the in-flight Windows named-pipe write nor closes the physical connection. The detached writer retains its `SharedSend` clone and mutex guard until the peer or connection is later torn down.\n\nThis is load-gated. Cold restart and update promotion are not sufficient: the controlled quiescent update completed cleanly. A real nonzero-sequence controller frame plus a non-draining consumer reproduced the field signature deterministically.\n\n## Reproduction evidence\n\nCapture: `C:\\Users\\decid\\AppData\\Local\\spt-core\\rca-active-20260709-222659`\n\nThe four deliberate remote `Take` subscriptions installed epoch-2 controllers:\n\n| Connection | Session | Install marker |\n|---:|---:|---|\n| 227 | 4 | `source=subscribe intent=take by=remote` |\n| 228 | 3 | `source=subscribe intent=take by=remote` |\n| 229 | 1 | `source=subscribe intent=take by=remote` |\n| 230 | 2 | `source=subscribe intent=take by=remote` |\n\nEach writer emitted `CTRL_WRITE_WAIT`, then `CTRL_WRITE_LOCKED wait_us=0`, and no `CTRL_WRITE_DONE`. This rules out mutex wait for the reproduction: the block occurred after lock acquisition, inside the frame write. An independent sessions query approximately 18.5 seconds later observed the aged writes and emitted four `BRAIN_SUBSCRIBER_STALL_EVICT` records. The writes remained blocked after logical eviction. They returned `result=error` only when a later brain restart closed the connections, after approximately 127.953 seconds in `write_frame`.\n\nThe saved `status.active.json` is intentionally the pre-recovery capture state (`broker_image=0.30.0`, `broker_stale=true`, `stall_evict_count=8`). The separately verified post-recovery production state was signed `0.30.5`, `broker_stale=false`, and `stall_evict_count=0`.\n\n## Exact code path\n\n1. `spt rc --take` selects `AttachIntent::Take` in `crates/spt/src/cli.rs` and enters `run_attach_inner` in `crates/spt/src/rc.rs`.\n2. The target attach worker calls `Brain::subscribe_with`; the broker routes `KIND_SUBSCRIBE` through `dispatch_subscribe` and `resolve_subscribe` in `crates/spt-daemon/src/broker.rs`.\n3. `OutputLog::become_controller` creates the existing bounded live-output channel and spawns `controller_writer` with a cloned connection `SharedSend`.\n4. `SharedSend` is `Arc<Mutex<SendHalf>>` (`crates/spt-daemon/src/broker.rs`, near the top-level type aliases).\n5. Both initial replay and live-output paths in `controller_writer` mark the writer blocked, acquire `send.lock()`, and retain the `MutexGuard<SendHalf>` across `write_frame`.\n6. `write_frame` in `crates/spt-daemon/src/codec.rs` serializes the envelope and synchronously calls `write_all` for the four-byte length prefix and JSON body.\n7. On Windows, `interprocess 2.4.2` routes the `SendHalf` write to `WriteFileEx`, then waits through `SleepEx(INFINITE, alertable=true)` for the asynchronous completion callback. This local-socket send path exposes no supported write timeout; `set_timeout` returns `Unsupported`.\n\nInstrumented capture-worktree anchors at commit `5d3723f`:\n\n- `crates/spt-daemon/src/broker.rs:73-77`: `SharedSend`.\n- `broker.rs:1048-1108`: controller installation, queue, send clone, writer spawn.\n- `broker.rs:1664-1716`: initial replay write.\n- `broker.rs:1718-1760`: live-output write.\n- `crates/spt-daemon/src/codec.rs:21-35`: length-prefixed blocking `write_all` calls.\n\n## Why existing deadlines do not bound the write\n\n`CONTROLLER_WRITE_DEADLINE` governs output-driven bounded-channel-full handling. It is not a socket-write deadline. A writer blocked on its first owned frame may never fill that channel.\n\n`BRAIN_WRITE_DEADLINE` is an age predicate over `write_blocked_since`, not an I/O timer. It is sampled opportunistically during controller resolution or session snapshots. Therefore 15 seconds is a minimum stale age when observed, not a guaranteed interrupt time.\n\n`mark_blocked(true)` occurs before mutex acquisition. The diagnostic `CTRL_WRITE_LOCKED` marker was added after acquisition; the captured `wait_us=0` values isolate the block to the OS write.\n\n## Why logical eviction does not release the writer\n\n`stall_evict_controller`, `clear_controller`, and `detach_if` remove the `ControllerSink` and update controller stamps. They do not:\n\n- interrupt or join the writer thread;\n- shut down the connection;\n- invoke `CancelIoEx`;\n- release the writer's stack-owned mutex guard;\n- make the current `write_frame` observe channel disconnection.\n\nDropping the sink drops a stored `JoinHandle` and one `SharedSend` clone. The already-running writer owns another clone and, in the failing state, the live guard. A disconnected queue can only be observed after the current write returns.\n\n## Rejected causes\n\n- **Update/promotion alone:** rejected by the quiescent controlled update, which promoted v56 without a new eviction.\n- **Cold-start orphan controller:** rejected. Spawn connections 3-6 wrote `seq:0`, reached EOF, and detached with `arc_matched=true cleared=true` within milliseconds.\n- **Shared mutex convoy:** rejected for this reproduction by `CTRL_WRITE_LOCKED wait_us=0` on all four writers.\n- **Broker-side scheduler starvation:** not required. Other broker, PTY, and bounded-child operations continued while the controller writes remained blocked.\n\nProcess pressure may explain why a field consumer stopped draining, but it is not the pinned broker-side blocking mechanism.\n\n## Recommended fix shape\n\nImplement one broker-owned bounded/cancelable framed-write primitive for every write on a physical broker connection. Replace raw `Arc<Mutex<SendHalf>>` use with an `Arc`-owned connection object containing:\n\n- the serialized write gate;\n- the send half;\n- an idempotent poisoned/aborted state;\n- per-in-flight-operation identity;\n- a platform abort capability.\n\nThe absolute bound must cover both waiting for the serialization gate and OS write completion. On deadline, partial write, cancellation, or unknown completion:\n\n1. atomically poison the whole physical connection;\n2. abort both read and write sides so `handle_conn` reaches existing EOF cleanup;\n3. wait until the canceled operation reports completion before releasing its buffer/overlapped state;\n4. never reuse the connection, because a timed-out length-prefixed frame may have been partially written;\n5. join the retired writer before reporting physical cleanup complete.\n\nRoute controller, viewer, dispatch reply, and terminal-control writes through the same primitive. Leaving any raw unbounded write behind the same gate preserves the failure class.\n\nDo not add another output queue: the bounded queue and isolated writer already exist, and the block is below them. Do not use `PIPE_NOWAIT`: this repository already records its mid-frame corruption risk. A heartbeat is optional telemetry, not a replacement for a write bound.\n\n### Rollout\n\nBroker-side only; no wire-format change and no coordinated multi-node protocol rollout. The running broker must restart to load the fix. Mixed-version peers remain wire-compatible. Risk is concentrated in Windows cancellation correctness and connection-wide teardown; Unix behavior must retain its existing semantics while using the same poison/retire invariant.\n\n## Regression gate\n\nAdd a Windows integration test in `crates/spt-daemon/tests/brain_decouple.rs`:\n\n```rust\n#[cfg(windows)]\n#[test]\nfn non_draining_controller_stall_evict_releases_writer_and_connection()\n```\n\nReuse the real broker/PTY helpers from `suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks`. Required sequence:\n\n1. use an isolated broker and shrink `SPT_BRAIN_WRITE_DEADLINE_MS` to 400 ms;\n2. spawn a real PTY child that emits `SEED`, then a finite 1-2 MiB burst on separate input triggers;\n3. install a remote controller and drain through `SEED`, proving a real `seq > 0` frame;\n4. trigger the finite burst and stop reading that controller connection;\n5. prove the PTY/ring high-water advances by at least 64 frames;\n6. use an independent draining control connection to trigger and observe exactly one logical stall eviction;\n7. without dropping the black-holed client, require that connection's `handle_conn` thread to finish within two seconds of logical release;\n8. assert a write on the old client connection fails;\n9. attach a fresh controller from the frozen resume cursor and assert the first replayed sequence equals that cursor.\n\nPre-fix RED: logical eviction succeeds, but the old handler/writer does not finish until the test drops the client or process exits.\n\nPost-fix GREEN: the deadline poisons and closes the old physical connection, the writer and handler finish within the bound, and a fresh controller resumes without losing the unaccepted frame.\n\nThe existing test proves logical isolation and viewer progress; it must remain as the cross-platform companion. The new test proves physical writer/connection retirement on the exact Windows failure mode.\n\n## Manual field-acceptance reproduction\n\nHarness: `scripts/rca/blackhole-controller.ps1` in the RCA capture branch.\n\nRun against disposable live endpoints only:\n\n```powershell\npowershell -NoProfile -ExecutionPolicy Bypass -File scripts/rca/blackhole-controller.ps1 \\\n  -Endpoint deployah,doyle,flynn,lia\n```\n\nThe harness launches `spt rc <id> --take` with stdout redirected to anonymous pipes that the parent deliberately never reads. Drive real PTY output in each endpoint. Before the fix, controller writes enter `LOCKED` without `DONE`, logical eviction occurs, and the child connections remain physically alive. After the fix, each blocked connection must be poisoned and terminated within the configured write bound, and a fresh `spt rc --take` must recover without a daemon restart.\n\nDo not run this harness on production sessions: it deliberately steals controller ownership and black-holes output.\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\rca-capture\\docs\\UPDATE-WEDGE-ROOT-CAUSE.md"}}},"isError":false,"timestamp":1783730517312}}
{"type":"message","id":"a4925685","parentId":"eebd5fb4","timestamp":"2026-07-11T00:42:05.942Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying worker environment and sleep confounders**\n\n<!-- -->"},{"type":"toolCall","id":"call_mttmKcmfXucN4lEyDUK2hgaI|fc_08496348794f7e09016a51915e797881988bbe62825b05e4e1","name":"read","arguments":{"i":"Read inject verification loop","path":"spt-core/crates/spt-daemon/src/broker.rs","selector":"2160-2365"}},{"type":"toolCall","id":"call_GhV4IoXr0Ep0SPheBpz1HUYh|fc_08496348794f7e09016a51915e7988819890d32bba4191040d","name":"read","arguments":{"i":"Read input writer implementation","path":"spt-core/crates/spt-daemon/src/broker.rs","selector":"1520-1885"}},{"type":"toolCall","id":"call_5HPjmc0HmnhZzXQzFEmsbTTt|fc_08496348794f7e09016a51915e799081988ac1394d63f5b0b0","name":"grep","arguments":{"i":"Locate tag commit ancestry","pattern":"b0d7719|0a306c9|ade41be|8e5ff88|d73e19e|ce053dc|fb499db|03c7109|d634b67","path":"spt-core/.git/logs","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_O9YQOQBTBX468XlGkPc9fbNK|fc_08496348794f7e09016a51915e79988198beadb0d6eac7263c","name":"grep","arguments":{"i":"Map old raw writes","pattern":"send\\.lock\\(\\)|write_frame\\(&mut \\*|send_frame\\(","path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_hjwH7BZAQXZxc6aIQZA2AeUH|fc_08496348794f7e09016a51915e79b081988c74b60013987250","name":"read","arguments":{"i":"Read codec blocking writes","path":"spt-core/crates/spt-daemon/src/codec.rs","selector":"1-70"}},{"type":"toolCall","id":"call_s9lRum7f9VaNYqbcwmMb7U0O|fc_08496348794f7e09016a51915e79b881989e21c93fd9ec6004","name":"read","arguments":{"i":"Read r4 regression test evidence","path":"spt-core/crates/spt-daemon/tests/brain_decouple.rs","selector":"370-545"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":24299,"output":385,"cacheRead":114176,"cacheWrite":0,"totalTokens":138860,"reasoningTokens":36,"cost":{"input":0.121495,"output":0.01155,"cacheRead":0.057088,"cacheWrite":0,"total":0.190133}},"stopReason":"toolUse","timestamp":1783730517379,"responseId":"resp_08496348794f7e09016a5191561f9481988361e5e2b0067cc2","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51915767008198acd6180265b52136","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFe-VlSQByVeGtp2jA6T1Zm5YrDOloHQFhX1YrkIfbB5GCFJuPi_yln4jsbqUnLLu_goJoWCrD7TaCE2vP-T39nxoEw5tACxcX-FJWEijqX154_YJ2_IBFBMMyjcZw_HevWWxVEJsdr1XLHbyYYgczP7_QQ7SiWAiqIcd2VdfJQUB2YLSXtWY7y6s2D3iJxuzy4Ez_HAL_om_TIrACrvNwIsWUoSYlmofLthYs1_R9-ozmpmJstDB9LoScxYsOwJ4Nj9XFclw0DdP1JGERDvDhN_j5HIqHLdMF1MFbmBFilUEBapdmUFZqOwGhXDjabFAiZtk_MUR2tNA3FOppt3rHQ9E4yBdC1hlo0hpGGdYtZsmxWUDwmJYvgMgf-GfLwbu9wbmyT0z9zXY0rswatMotMXgcIkZLngYR8bPUHYtInMJuTorfSIgnSJKYXnzP3RVJoO8XAnD5Ul5pSCmQk7RnV0oGhNaywcFpSJIX6I2VaU9czFWQ_IP-fYti_RywwfLX_mzbVqy1IAZRsl1kZ3u5qDY-XMHQPd6mJ9qBIQ3EhOivRt9iaUkaan1Ct85hjAq8hHid22Le5xne6VEIVJpwobT21NFlNYcXQKx2obIABc03m1muX3WlmoWSg92zImTYorAAJn8SyDLoVxv-sWVr2Py_Er1LveWi-vPo6ER_I3TDnNwZFiHNZ9whBZxazc9nGg2MOxjXaNVaRSXOVsPpj6-BYdATZuWSCLsxeXfOjLdtolpDbfoPhI4KNnzNC04d_tl3YqN3d37OKltAcaUoE0z4A-ZWqjY01Kbyn7DubDOkfSFqV-awSReel7SOz4UpS-GvtGskdXgfZDrDXqPYIjzA_oRLKuZ26vUNFHUj1mjc6xuIEks5lfXgWRRqFLXaN2g2wHEzQYe0qSNxIQShEgEil7vIlRcC80XuSYZ3Plq4hJZg6qOk3fWalYaKtC3t3YDDaBPFQytiFg6Lj5aTEkIGKpGX9hCLd3IWHzVtukQa4X8C3OnGApYGNnZP273MNKCaBKOjaJLqPs5Q8rPwSgoih28Rwxky9Rnf2k4TJyqBqk9gMHI1SYWbix4Lm17GmwGRbZQznIQ9Wpc3UGcIvXAdjuvDAnFQBlMI-6J_EnE4=","summary":[{"type":"summary_text","text":"**Verifying worker environment and sleep confounders**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a51915e797881988bbe62825b05e4e1","type":"function_call","status":"completed","arguments":"{\"i\":\"Read inject verification loop\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"selector\":\"2160-2365\"}","call_id":"call_mttmKcmfXucN4lEyDUK2hgaI","name":"read"},{"id":"fc_08496348794f7e09016a51915e7988819890d32bba4191040d","type":"function_call","status":"completed","arguments":"{\"i\":\"Read input writer implementation\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"selector\":\"1520-1885\"}","call_id":"call_GhV4IoXr0Ep0SPheBpz1HUYh","name":"read"},{"id":"fc_08496348794f7e09016a51915e799081988ac1394d63f5b0b0","type":"function_call","status":"completed","arguments":"{\"i\":\"Locate tag commit ancestry\",\"pattern\":\"b0d7719|0a306c9|ade41be|8e5ff88|d73e19e|ce053dc|fb499db|03c7109|d634b67\",\"path\":\"spt-core/.git/logs\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_5HPjmc0HmnhZzXQzFEmsbTTt","name":"grep"},{"id":"fc_08496348794f7e09016a51915e79988198beadb0d6eac7263c","type":"function_call","status":"completed","arguments":"{\"i\":\"Map old raw writes\",\"pattern\":\"send\\\\.lock\\\\(\\\\)|write_frame\\\\(&mut \\\\*|send_frame\\\\(\",\"path\":\"spt-core/.worktrees/gate-w4/crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_O9YQOQBTBX468XlGkPc9fbNK","name":"grep"},{"id":"fc_08496348794f7e09016a51915e79b081988c74b60013987250","type":"function_call","status":"completed","arguments":"{\"i\":\"Read codec blocking writes\",\"path\":\"spt-core/crates/spt-daemon/src/codec.rs\",\"selector\":\"1-70\"}","call_id":"call_hjwH7BZAQXZxc6aIQZA2AeUH","name":"read"},{"id":"fc_08496348794f7e09016a51915e79b881989e21c93fd9ec6004","type":"function_call","status":"completed","arguments":"{\"i\":\"Read r4 regression test evidence\",\"path\":\"spt-core/crates/spt-daemon/tests/brain_decouple.rs\",\"selector\":\"370-545\"}","call_id":"call_s9lRum7f9VaNYqbcwmMb7U0O","name":"read"}]},"duration":8548.598800000269,"ttft":1391.7894000001252,"contextSnapshot":{"promptTokens":138475,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_mttmKcmfXucN4lEyDUK2hgaI|fc_08496348794f7e09016a51915e797881988bbe62825b05e4e1","toolName":"read","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs"},"intent":"Read inject verification loop"},"id":"74e49721","parentId":"a4925685","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GhV4IoXr0Ep0SPheBpz1HUYh|fc_08496348794f7e09016a51915e7988819890d32bba4191040d","toolName":"read","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs"},"intent":"Read input writer implementation"},"id":"0db6103a","parentId":"74e49721","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5HPjmc0HmnhZzXQzFEmsbTTt|fc_08496348794f7e09016a51915e799081988ac1394d63f5b0b0","toolName":"grep","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/.git/logs"},"intent":"Locate tag commit ancestry"},"id":"3cd95234","parentId":"0db6103a","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_O9YQOQBTBX468XlGkPc9fbNK|fc_08496348794f7e09016a51915e79988198beadb0d6eac7263c","toolName":"grep","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src"},"intent":"Map old raw writes"},"id":"32c311f8","parentId":"3cd95234","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hjwH7BZAQXZxc6aIQZA2AeUH|fc_08496348794f7e09016a51915e79b081988c74b60013987250","toolName":"read","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/crates/spt-daemon/src/codec.rs"},"intent":"Read codec blocking writes"},"id":"f8898795","parentId":"32c311f8","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_s9lRum7f9VaNYqbcwmMb7U0O|fc_08496348794f7e09016a51915e79b881989e21c93fd9ec6004","toolName":"read","startedAt":"2026-07-11T00:42:05.943Z","args":{"path":"spt-core/crates/spt-daemon/tests/brain_decouple.rs"},"intent":"Read r4 regression test evidence"},"id":"11f830c9","parentId":"f8898795","timestamp":"2026-07-11T00:42:05.943Z"}
{"type":"message","id":"b05cacee","parentId":"11f830c9","timestamp":"2026-07-11T00:42:05.950Z","message":{"role":"toolResult","toolCallId":"call_hjwH7BZAQXZxc6aIQZA2AeUH|fc_08496348794f7e09016a51915e79b081988c74b60013987250","toolName":"read","content":[{"type":"text","text":"//! Length-prefixed framing for the broker↔brain IPC.\n//!\n//! Frame: a 4-byte big-endian length prefix, then that many bytes of UTF-8\n//! JSON encoding one [`Envelope`]. Same shape as `spt-msg::wire` (ADR-0001\n//! framing) but generic over any [`Read`]/[`Write`] — the IPC carrier is a\n//! local socket (named pipe / UDS), not a `TcpStream`, and tests exercise it\n//! over in-memory buffers and socket pairs.\n//!\n//! The frame *codec* is deliberately transport-agnostic: it is the byte-level\n//! contract, while [`crate::transport`] owns the OS-specific connection.\n// [impl->REQ-DAEMON-2]\n\nuse std::io::{self, Read, Write};\n\nuse crate::frame::Envelope;\n\n/// Maximum framed message size accepted (16 MiB) — guards a garbage/hostile\n/// length prefix from forcing a huge allocation. Matches `spt-msg::wire`.\nconst MAX_FRAME_BYTES: usize = 16 * 1024 * 1024;\n\n/// Serialize and write one [`Envelope`] as a length-prefixed JSON frame.\npub fn write_frame<W: Write>(w: &mut W, env: &Envelope) -> io::Result<()> {\n    let body =\n        serde_json::to_vec(env).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n    if body.len() > MAX_FRAME_BYTES {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"frame too large: {} bytes\", body.len()),\n        ));\n    }\n    let len = body.len() as u32;\n    w.write_all(&len.to_be_bytes())?;\n    w.write_all(&body)?;\n    w.flush()?;\n    Ok(())\n}\n\n/// Read one length-prefixed JSON frame and decode it into an [`Envelope`].\n/// Rejects an oversized length prefix before allocating. A clean EOF on the\n/// length prefix surfaces as [`io::ErrorKind::UnexpectedEof`] (peer hung up).\npub fn read_frame<R: Read>(r: &mut R) -> io::Result<Envelope> {\n    let mut len_buf = [0u8; 4];\n    r.read_exact(&mut len_buf)?;\n    let len = u32::from_be_bytes(len_buf) as usize;\n    if len > MAX_FRAME_BYTES {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"frame too large: {len} bytes\"),\n        ));\n    }\n    let mut buf = vec![0u8; len];\n    r.read_exact(&mut buf)?;\n    serde_json::from_slice(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::frame::IPC_PROTOCOL_VERSION;\n    use serde_json::json;\n\n    // [unit->REQ-DAEMON-2] a frame round-trips byte-exact through the codec.\n    #[test]\n    fn frame_round_trips_through_buffer() {\n        let env = Envelope::new(\"attach\", json!({ \"session\": \"s1\", \"n\": 7 }));\n        let mut buf: Vec<u8> = Vec::new();\n        write_frame(&mut buf, &env).unwrap();\n\n        let mut cursor = io::Cursor::new(buf);\n        let got = read_frame(&mut cursor).unwrap();\n        assert_eq!(got.protocol_version, IPC_PROTOCOL_VERSION);\n        assert_eq!(got.kind, \"attach\");\n        assert_eq!(got.payload, json!({ \"session\": \"s1\", \"n\": 7 }));\n    }\n…\n}\n\n[Showing lines 1-73 of 97. Use :74 to continue]"}],"details":{"truncation":{"content":"//! Length-prefixed framing for the broker↔brain IPC.\r\n//!\r\n//! Frame: a 4-byte big-endian length prefix, then that many bytes of UTF-8\r\n//! JSON encoding one [`Envelope`]. Same shape as `spt-msg::wire` (ADR-0001\r\n//! framing) but generic over any [`Read`]/[`Write`] — the IPC carrier is a\r\n//! local socket (named pipe / UDS), not a `TcpStream`, and tests exercise it\r\n//! over in-memory buffers and socket pairs.\r\n//!\r\n//! The frame *codec* is deliberately transport-agnostic: it is the byte-level\r\n//! contract, while [`crate::transport`] owns the OS-specific connection.\r\n// [impl->REQ-DAEMON-2]\r\n\r\nuse std::io::{self, Read, Write};\r\n\r\nuse crate::frame::Envelope;\r\n\r\n/// Maximum framed message size accepted (16 MiB) — guards a garbage/hostile\r\n/// length prefix from forcing a huge allocation. Matches `spt-msg::wire`.\r\nconst MAX_FRAME_BYTES: usize = 16 * 1024 * 1024;\r\n\r\n/// Serialize and write one [`Envelope`] as a length-prefixed JSON frame.\r\npub fn write_frame<W: Write>(w: &mut W, env: &Envelope) -> io::Result<()> {\r\n    let body =\r\n        serde_json::to_vec(env).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n    if body.len() > MAX_FRAME_BYTES {\r\n        return Err(io::Error::new(\r\n            io::ErrorKind::InvalidData,\r\n            format!(\"frame too large: {} bytes\", body.len()),\r\n        ));\r\n    }\r\n    let len = body.len() as u32;\r\n    w.write_all(&len.to_be_bytes())?;\r\n    w.write_all(&body)?;\r\n    w.flush()?;\r\n    Ok(())\r\n}\r\n\r\n/// Read one length-prefixed JSON frame and decode it into an [`Envelope`].\r\n/// Rejects an oversized length prefix before allocating. A clean EOF on the\r\n/// length prefix surfaces as [`io::ErrorKind::UnexpectedEof`] (peer hung up).\r\npub fn read_frame<R: Read>(r: &mut R) -> io::Result<Envelope> {\r\n    let mut len_buf = [0u8; 4];\r\n    r.read_exact(&mut len_buf)?;\r\n    let len = u32::from_be_bytes(len_buf) as usize;\r\n    if len > MAX_FRAME_BYTES {\r\n        return Err(io::Error::new(\r\n            io::ErrorKind::InvalidData,\r\n            format!(\"frame too large: {len} bytes\"),\r\n        ));\r\n    }\r\n    let mut buf = vec![0u8; len];\r\n    r.read_exact(&mut buf)?;\r\n    serde_json::from_slice(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n    use crate::frame::IPC_PROTOCOL_VERSION;\r\n    use serde_json::json;\r\n\r\n    // [unit->REQ-DAEMON-2] a frame round-trips byte-exact through the codec.\r\n    #[test]\r\n    fn frame_round_trips_through_buffer() {\r\n        let env = Envelope::new(\"attach\", json!({ \"session\": \"s1\", \"n\": 7 }));\r\n        let mut buf: Vec<u8> = Vec::new();\r\n        write_frame(&mut buf, &env).unwrap();\r\n\r\n        let mut cursor = io::Cursor::new(buf);\r\n        let got = read_frame(&mut cursor).unwrap();\r\n        assert_eq!(got.protocol_version, IPC_PROTOCOL_VERSION);\r\n        assert_eq!(got.kind, \"attach\");\r\n        assert_eq!(got.payload, json!({ \"session\": \"s1\", \"n\": 7 }));\r","truncated":true,"truncatedBy":"lines","totalLines":97,"totalBytes":2949,"outputLines":73,"outputBytes":2949,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! Length-prefixed framing for the broker↔brain IPC.\r\n//!\r\n//! Frame: a 4-byte big-endian length prefix, then that many bytes of UTF-8\r\n//! JSON encoding one [`Envelope`]. Same shape as `spt-msg::wire` (ADR-0001\r\n//! framing) but generic over any [`Read`]/[`Write`] — the IPC carrier is a\r\n//! local socket (named pipe / UDS), not a `TcpStream`, and tests exercise it\r\n//! over in-memory buffers and socket pairs.\r\n//!\r\n//! The frame *codec* is deliberately transport-agnostic: it is the byte-level\r\n//! contract, while [`crate::transport`] owns the OS-specific connection.\r\n// [impl->REQ-DAEMON-2]\r\n\r\nuse std::io::{self, Read, Write};\r\n\r\nuse crate::frame::Envelope;\r\n\r\n/// Maximum framed message size accepted (16 MiB) — guards a garbage/hostile\r\n/// length prefix from forcing a huge allocation. Matches `spt-msg::wire`.\r\nconst MAX_FRAME_BYTES: usize = 16 * 1024 * 1024;\r\n\r\n/// Serialize and write one [`Envelope`] as a length-prefixed JSON frame.\r\npub fn write_frame<W: Write>(w: &mut W, env: &Envelope) -> io::Result<()> {\r\n    let body =\r\n        serde_json::to_vec(env).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n    if body.len() > MAX_FRAME_BYTES {\r\n        return Err(io::Error::new(\r\n            io::ErrorKind::InvalidData,\r\n            format!(\"frame too large: {} bytes\", body.len()),\r\n        ));\r\n    }\r\n    let len = body.len() as u32;\r\n    w.write_all(&len.to_be_bytes())?;\r\n    w.write_all(&body)?;\r\n    w.flush()?;\r\n    Ok(())\r\n}\r\n\r\n/// Read one length-prefixed JSON frame and decode it into an [`Envelope`].\r\n/// Rejects an oversized length prefix before allocating. A clean EOF on the\r\n/// length prefix surfaces as [`io::ErrorKind::UnexpectedEof`] (peer hung up).\r\npub fn read_frame<R: Read>(r: &mut R) -> io::Result<Envelope> {\r\n    let mut len_buf = [0u8; 4];\r\n    r.read_exact(&mut len_buf)?;\r\n    let len = u32::from_be_bytes(len_buf) as usize;\r\n    if len > MAX_FRAME_BYTES {\r\n        return Err(io::Error::new(\r\n            io::ErrorKind::InvalidData,\r\n            format!(\"frame too large: {len} bytes\"),\r\n        ));\r\n    }\r\n    let mut buf = vec![0u8; len];\r\n    r.read_exact(&mut buf)?;\r\n    serde_json::from_slice(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n    use crate::frame::IPC_PROTOCOL_VERSION;\r\n    use serde_json::json;\r\n\r\n    // [unit->REQ-DAEMON-2] a frame round-trips byte-exact through the codec.\r\n    #[test]\r\n    fn frame_round_trips_through_buffer() {\r\n        let env = Envelope::new(\"attach\", json!({ \"session\": \"s1\", \"n\": 7 }));\r\n        let mut buf: Vec<u8> = Vec::new();\r\n        write_frame(&mut buf, &env).unwrap();\r\n\r\n        let mut cursor = io::Cursor::new(buf);\r\n        let got = read_frame(&mut cursor).unwrap();\r\n        assert_eq!(got.protocol_version, IPC_PROTOCOL_VERSION);\r\n        assert_eq!(got.kind, \"attach\");\r\n        assert_eq!(got.payload, json!({ \"session\": \"s1\", \"n\": 7 }));\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,null,96]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\codec.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":97,"totalBytes":2949,"outputLines":73,"outputBytes":2949,"shownRange":{"start":1,"end":73},"nextOffset":74}}},"isError":false,"timestamp":1783730525950}}
{"type":"message","id":"af8e3639","parentId":"b05cacee","timestamp":"2026-07-11T00:42:05.956Z","message":{"role":"toolResult","toolCallId":"call_s9lRum7f9VaNYqbcwmMb7U0O|fc_08496348794f7e09016a51915e79b881989e21c93fd9ec6004","toolName":"read","content":[{"type":"text","text":"fn suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks() {\n…\n    // surface reports).\n    assert!(\n        evicts >= 1,\n        \"the stall-evict must be recorded in the broker-global tally (daemon-status \\\n         observability rider); got {evicts}\"\n    );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// UPDATE-WEDGE round-4 — PHYSICAL writer/connection retirement gate.\n//\n// The companion test above proves LOGICAL isolation: a wedged controller is\n// stall-evicted (the ROLE is freed), the viewer keeps ticking, a fresh take\n// completes. But hertz's RCA (`docs/UPDATE-WEDGE-ROOT-CAUSE.md` @ f8596ca,\n// §Regression gate) proved that logical eviction does NOT retire the PHYSICAL\n// connection: `stall_evict_controller` drops the `ControllerSink` (one\n// `SharedSend` clone + the `JoinHandle`) but neither cancels the in-flight\n// Windows named-pipe write, closes the socket, nor `CancelIoEx`'s it. The\n// already-running `controller_writer` keeps its OWN `SharedSend` clone and stays\n// wedged inside `write_frame` (WriteFileEx + SleepEx(INFINITE, alertable) — no\n// supported write timeout) until the client drops or the process exits (~128 s\n// in the field capture). The `handle_conn` reader for that same conn also never\n// reaches EOF while the client end stays open. This is LOAD-GATED: it needs a\n// real seq>0 controller frame plus a non-draining consumer (seq-0 boot + a\n// quiescent update are clean).\n//\n// This test reproduces that EXACT Windows failure mode with a real PTY child and\n// a genuinely black-holed controller connection, then asserts the two things the\n// logical gate cannot see:\n//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\n//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\n//     client ever dropping its end;                                    ← RED pre-fix\n//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n// absolute deadline fires out of band, poisons the whole physical conn, aborts\n// read+write so `handle_conn` reaches EOF cleanup, and a fresh controller resumes\n// from the frozen cursor with NO lost frame.\n//\n// Every read anywhere in this test rides a bounded thread+channel or a polled\n// deadline; the test captures plain values (bools / Options / Instants) as it\n// goes and only asserts AFTER teardown, so it can never hang even on broken code\n// — worst case it fails an assert after its bounded windows elapse.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/// A real PTY child (PowerShell) that emits `SEED`, parks on a first `Read-Host`,\n/// then on a stdin trigger emits a finite ~1.4 MiB burst (1400 lines × 1023 `B`s)\n/// and parks again. The seq-0 boot output (VT init + prompt + SEED) gives a real\n/// `seq>0` controller frame; the burst floods a non-draining controller's socket\n/// so its writer parks INSIDE `write_frame` — the exact black-hole the RCA pinned.\n/// Sibling of [`flood_spawn_req`], but FINITE + input-triggered (the flood child\n/// never stops, so it can't prove a frozen cursor / bounded high-water advance).\n#[cfg(windows)]\nfn seed_burst_spawn_req(endpoint: &str) -> SpawnReq {\n    SpawnReq {\n        program: \"powershell\".to_string(),\n        args: vec![\n            \"-NoProfile\".to_string(),\n            \"-Command\".to_string(),\n            \"Write-Output SEED; $null = Read-Host; \\\n             foreach ($i in 1..1400) { Write-Output ('B' * 1023) }; $null = Read-Host\"\n                .to_string(),\n        ],\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 `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n/// existing [`stall_evict_count`] helper. The transient conn is dropped when the\n/// thread returns.\n#[cfg(windows)]\nfn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\n    let name = name.to_string();\n    let (tx, rx) = std::sync::mpsc::channel();\n    thread::spawn(move || {\n        let mut c = connect(&name);\n        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\n        loop {\n            match read_frame(&mut c) {\n                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\n                    let r: spt_daemon::msg::SessionsReply =\n                        serde_json::from_value(f.payload).unwrap();\n                    let _ = tx.send(r);\n                    return;\n                }\n                Ok(_) => continue,\n                Err(_) => return,\n            }\n        }\n    });\n    rx.recv_timeout(deadline).ok()\n}\n\n// [int->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n#[cfg(windows)]\n#[test]\nfn non_draining_controller_stall_evict_releases_writer_and_connection() {\n    init_home();\n    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms.\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n\n    let name = unique_name();\n    let broker = Broker::bind(&name).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n\n    // ── Step 2: spawn the real SEED+burst PTY child from conn A, then DROP A so the\n    //    ONLY persistent brain conn later is the black-holed controller R. ──\n    let mut a = connect(&name);\n    send(\n        &mut a,\n        KIND_SPAWN,\n        serde_json::to_value(seed_burst_spawn_req(\"uwedge-r4-ep\")).unwrap(),\n    );\n    let sid = loop {\n        let f = read_frame(&mut a).expect(\"frame before spawned\");\n        if f.kind == KIND_SPAWNED {\n            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n        }\n    };\n    drop(a);\n\n    // ── Steps 3+4: remote controller R. On a dedicated thread it subscribes Control\n    //    from_seq 0, drains output until it has seen `SEED` (proving a real controller\n    //    frame flowed), signals main so main can freeze the PRE-burst high-water, then\n    //    on main's go it fires the burst trigger and drains a few burst frames (a real\n    //    seq>0 frame delivered to the controller) and STOPS reading. It hands the\n    //    Stream back to main UNREAD — main holds it, never draining, so the broker's\n    //    R-writer parks in `write_frame` exactly as against a suspended brain. R's\n    //    Stream is NEVER dropped until the very end of the test. ──\n    let r_name = name.clone();\n    let (seed_tx, seed_rx) = std::sync::mpsc::channel::<(bool, u64)>();\n    let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();\n    let (hand_tx, hand_rx) = std::sync::mpsc::channel::<(Stream, u64)>();\n    thread::spawn(move || {\n        let mut r = connect(&r_name);\n        send(\n            &mut r,\n            KIND_SUBSCRIBE,\n            serde_json::to_value(SubscribeReq {\n                session_id: sid,\n                from_seq: 0,\n                intent: AttachIntent::Control,\n                by: Some(\"operator-one\".to_string()),\n            })\n            .unwrap(),\n        );\n        // Read until SUBSCRIBED.\n        loop {\n            match read_frame(&mut r) {\n                Ok(f) if f.kind == KIND_SUBSCRIBED => break,\n                Ok(_) => continue,\n                Err(_) => {\n                    let _ = seed_tx.send((false, 0));\n                    return;\n                }\n            }\n        }\n        // Drain live output until the concatenated decoded bytes contain `SEED`\n        // (search the CONCATENATED stream — ConPTY echoes input and emits VT noise,\n        // so per-frame matching is unreliable). Track the max seq seen.\n        let mut acc: Vec<u8> = Vec::new();\n        let mut max_seq: u64 = 0;\n        let mut saw_seed = false;\n        let seed_deadline = Instant::now() + Duration::from_secs(30);\n        while Instant::now() < seed_deadline {\n            match read_frame(&mut r) {\n                Ok(f) if f.kind == spt_daemon::msg::KIND_OUTPUT => {\n                    let ev: spt_daemon::msg::OutputEvent =\n                        serde_json::from_value(f.payload).unwrap();\n…\n                }\n…\n            }\n        }\n…\n    });\n…\n}\n\n[Showing lines 369-548 of 822. Use :549 to continue]"}],"details":{"truncation":{"content":"    // surface reports).\r\n    assert!(\r\n        evicts >= 1,\r\n        \"the stall-evict must be recorded in the broker-global tally (daemon-status \\\r\n         observability rider); got {evicts}\"\r\n    );\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────────────────────\r\n// UPDATE-WEDGE round-4 — PHYSICAL writer/connection retirement gate.\r\n//\r\n// The companion test above proves LOGICAL isolation: a wedged controller is\r\n// stall-evicted (the ROLE is freed), the viewer keeps ticking, a fresh take\r\n// completes. But hertz's RCA (`docs/UPDATE-WEDGE-ROOT-CAUSE.md` @ f8596ca,\r\n// §Regression gate) proved that logical eviction does NOT retire the PHYSICAL\r\n// connection: `stall_evict_controller` drops the `ControllerSink` (one\r\n// `SharedSend` clone + the `JoinHandle`) but neither cancels the in-flight\r\n// Windows named-pipe write, closes the socket, nor `CancelIoEx`'s it. The\r\n// already-running `controller_writer` keeps its OWN `SharedSend` clone and stays\r\n// wedged inside `write_frame` (WriteFileEx + SleepEx(INFINITE, alertable) — no\r\n// supported write timeout) until the client drops or the process exits (~128 s\r\n// in the field capture). The `handle_conn` reader for that same conn also never\r\n// reaches EOF while the client end stays open. This is LOAD-GATED: it needs a\r\n// real seq>0 controller frame plus a non-draining consumer (seq-0 boot + a\r\n// quiescent update are clean).\r\n//\r\n// This test reproduces that EXACT Windows failure mode with a real PTY child and\r\n// a genuinely black-holed controller connection, then asserts the two things the\r\n// logical gate cannot see:\r\n//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\r\n//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\r\n//     client ever dropping its end;                                    ← RED pre-fix\r\n//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\r\n// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\r\n// absolute deadline fires out of band, poisons the whole physical conn, aborts\r\n// read+write so `handle_conn` reaches EOF cleanup, and a fresh controller resumes\r\n// from the frozen cursor with NO lost frame.\r\n//\r\n// Every read anywhere in this test rides a bounded thread+channel or a polled\r\n// deadline; the test captures plain values (bools / Options / Instants) as it\r\n// goes and only asserts AFTER teardown, so it can never hang even on broken code\r\n// — worst case it fails an assert after its bounded windows elapse.\r\n// ─────────────────────────────────────────────────────────────────────────────\r\n\r\n/// A real PTY child (PowerShell) that emits `SEED`, parks on a first `Read-Host`,\r\n/// then on a stdin trigger emits a finite ~1.4 MiB burst (1400 lines × 1023 `B`s)\r\n/// and parks again. The seq-0 boot output (VT init + prompt + SEED) gives a real\r\n/// `seq>0` controller frame; the burst floods a non-draining controller's socket\r\n/// so its writer parks INSIDE `write_frame` — the exact black-hole the RCA pinned.\r\n/// Sibling of [`flood_spawn_req`], but FINITE + input-triggered (the flood child\r\n/// never stops, so it can't prove a frozen cursor / bounded high-water advance).\r\n#[cfg(windows)]\r\nfn seed_burst_spawn_req(endpoint: &str) -> SpawnReq {\r\n    SpawnReq {\r\n        program: \"powershell\".to_string(),\r\n        args: vec![\r\n            \"-NoProfile\".to_string(),\r\n            \"-Command\".to_string(),\r\n            \"Write-Output SEED; $null = Read-Host; \\\r\n             foreach ($i in 1..1400) { Write-Output ('B' * 1023) }; $null = Read-Host\"\r\n                .to_string(),\r\n        ],\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 `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\r\n/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\r\n/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\r\n/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\r\n/// existing [`stall_evict_count`] helper. The transient conn is dropped when the\r\n/// thread returns.\r\n#[cfg(windows)]\r\nfn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\r\n    let name = name.to_string();\r\n    let (tx, rx) = std::sync::mpsc::channel();\r\n    thread::spawn(move || {\r\n        let mut c = connect(&name);\r\n        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\r\n        loop {\r\n            match read_frame(&mut c) {\r\n                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\r\n                    let r: spt_daemon::msg::SessionsReply =\r\n                        serde_json::from_value(f.payload).unwrap();\r\n                    let _ = tx.send(r);\r\n                    return;\r\n                }\r\n                Ok(_) => continue,\r\n                Err(_) => return,\r\n            }\r\n        }\r\n    });\r\n    rx.recv_timeout(deadline).ok()\r\n}\r\n\r\n// [int->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n#[cfg(windows)]\r\n#[test]\r\nfn non_draining_controller_stall_evict_releases_writer_and_connection() {\r\n    init_home();\r\n    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms.\r\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\r\n\r\n    let name = unique_name();\r\n    let broker = Broker::bind(&name).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n\r\n    // ── Step 2: spawn the real SEED+burst PTY child from conn A, then DROP A so the\r\n    //    ONLY persistent brain conn later is the black-holed controller R. ──\r\n    let mut a = connect(&name);\r\n    send(\r\n        &mut a,\r\n        KIND_SPAWN,\r\n        serde_json::to_value(seed_burst_spawn_req(\"uwedge-r4-ep\")).unwrap(),\r\n    );\r\n    let sid = loop {\r\n        let f = read_frame(&mut a).expect(\"frame before spawned\");\r\n        if f.kind == KIND_SPAWNED {\r\n            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\r\n        }\r\n    };\r\n    drop(a);\r\n\r\n    // ── Steps 3+4: remote controller R. On a dedicated thread it subscribes Control\r\n    //    from_seq 0, drains output until it has seen `SEED` (proving a real controller\r\n    //    frame flowed), signals main so main can freeze the PRE-burst high-water, then\r\n    //    on main's go it fires the burst trigger and drains a few burst frames (a real\r\n    //    seq>0 frame delivered to the controller) and STOPS reading. It hands the\r\n    //    Stream back to main UNREAD — main holds it, never draining, so the broker's\r\n    //    R-writer parks in `write_frame` exactly as against a suspended brain. R's\r\n    //    Stream is NEVER dropped until the very end of the test. ──\r\n    let r_name = name.clone();\r\n    let (seed_tx, seed_rx) = std::sync::mpsc::channel::<(bool, u64)>();\r\n    let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();\r\n    let (hand_tx, hand_rx) = std::sync::mpsc::channel::<(Stream, u64)>();\r\n    thread::spawn(move || {\r\n        let mut r = connect(&r_name);\r\n        send(\r\n            &mut r,\r\n            KIND_SUBSCRIBE,\r\n            serde_json::to_value(SubscribeReq {\r\n                session_id: sid,\r\n                from_seq: 0,\r\n                intent: AttachIntent::Control,\r\n                by: Some(\"operator-one\".to_string()),\r\n            })\r\n            .unwrap(),\r\n        );\r\n        // Read until SUBSCRIBED.\r\n        loop {\r\n            match read_frame(&mut r) {\r\n                Ok(f) if f.kind == KIND_SUBSCRIBED => break,\r\n                Ok(_) => continue,\r\n                Err(_) => {\r\n                    let _ = seed_tx.send((false, 0));\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        // Drain live output until the concatenated decoded bytes contain `SEED`\r\n        // (search the CONCATENATED stream — ConPTY echoes input and emits VT noise,\r\n        // so per-frame matching is unreliable). Track the max seq seen.\r\n        let mut acc: Vec<u8> = Vec::new();\r\n        let mut max_seq: u64 = 0;\r\n        let mut saw_seed = false;\r\n        let seed_deadline = Instant::now() + Duration::from_secs(30);\r\n        while Instant::now() < seed_deadline {\r\n            match read_frame(&mut r) {\r\n                Ok(f) if f.kind == spt_daemon::msg::KIND_OUTPUT => {\r\n                    let ev: spt_daemon::msg::OutputEvent =\r","truncated":true,"truncatedBy":"lines","totalLines":454,"totalBytes":8943,"outputLines":180,"outputBytes":8943,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks() {\n…\n    // surface reports).\r\n    assert!(\r\n        evicts >= 1,\r\n        \"the stall-evict must be recorded in the broker-global tally (daemon-status \\\r\n         observability rider); got {evicts}\"\r\n    );\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────────────────────\r\n// UPDATE-WEDGE round-4 — PHYSICAL writer/connection retirement gate.\r\n//\r\n// The companion test above proves LOGICAL isolation: a wedged controller is\r\n// stall-evicted (the ROLE is freed), the viewer keeps ticking, a fresh take\r\n// completes. But hertz's RCA (`docs/UPDATE-WEDGE-ROOT-CAUSE.md` @ f8596ca,\r\n// §Regression gate) proved that logical eviction does NOT retire the PHYSICAL\r\n// connection: `stall_evict_controller` drops the `ControllerSink` (one\r\n// `SharedSend` clone + the `JoinHandle`) but neither cancels the in-flight\r\n// Windows named-pipe write, closes the socket, nor `CancelIoEx`'s it. The\r\n// already-running `controller_writer` keeps its OWN `SharedSend` clone and stays\r\n// wedged inside `write_frame` (WriteFileEx + SleepEx(INFINITE, alertable) — no\r\n// supported write timeout) until the client drops or the process exits (~128 s\r\n// in the field capture). The `handle_conn` reader for that same conn also never\r\n// reaches EOF while the client end stays open. This is LOAD-GATED: it needs a\r\n// real seq>0 controller frame plus a non-draining consumer (seq-0 boot + a\r\n// quiescent update are clean).\r\n//\r\n// This test reproduces that EXACT Windows failure mode with a real PTY child and\r\n// a genuinely black-holed controller connection, then asserts the two things the\r\n// logical gate cannot see:\r\n//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\r\n//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\r\n//     client ever dropping its end;                                    ← RED pre-fix\r\n//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\r\n// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\r\n// absolute deadline fires out of band, poisons the whole physical conn, aborts\r\n// read+write so `handle_conn` reaches EOF cleanup, and a fresh controller resumes\r\n// from the frozen cursor with NO lost frame.\r\n//\r\n// Every read anywhere in this test rides a bounded thread+channel or a polled\r\n// deadline; the test captures plain values (bools / Options / Instants) as it\r\n// goes and only asserts AFTER teardown, so it can never hang even on broken code\r\n// — worst case it fails an assert after its bounded windows elapse.\r\n// ─────────────────────────────────────────────────────────────────────────────\r\n\r\n/// A real PTY child (PowerShell) that emits `SEED`, parks on a first `Read-Host`,\r\n/// then on a stdin trigger emits a finite ~1.4 MiB burst (1400 lines × 1023 `B`s)\r\n/// and parks again. The seq-0 boot output (VT init + prompt + SEED) gives a real\r\n/// `seq>0` controller frame; the burst floods a non-draining controller's socket\r\n/// so its writer parks INSIDE `write_frame` — the exact black-hole the RCA pinned.\r\n/// Sibling of [`flood_spawn_req`], but FINITE + input-triggered (the flood child\r\n/// never stops, so it can't prove a frozen cursor / bounded high-water advance).\r\n#[cfg(windows)]\r\nfn seed_burst_spawn_req(endpoint: &str) -> SpawnReq {\r\n    SpawnReq {\r\n        program: \"powershell\".to_string(),\r\n        args: vec![\r\n            \"-NoProfile\".to_string(),\r\n            \"-Command\".to_string(),\r\n            \"Write-Output SEED; $null = Read-Host; \\\r\n             foreach ($i in 1..1400) { Write-Output ('B' * 1023) }; $null = Read-Host\"\r\n                .to_string(),\r\n        ],\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 `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\r\n/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\r\n/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\r\n/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\r\n/// existing [`stall_evict_count`] helper. The transient conn is dropped when the\r\n/// thread returns.\r\n#[cfg(windows)]\r\nfn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\r\n    let name = name.to_string();\r\n    let (tx, rx) = std::sync::mpsc::channel();\r\n    thread::spawn(move || {\r\n        let mut c = connect(&name);\r\n        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\r\n        loop {\r\n            match read_frame(&mut c) {\r\n                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\r\n                    let r: spt_daemon::msg::SessionsReply =\r\n                        serde_json::from_value(f.payload).unwrap();\r\n                    let _ = tx.send(r);\r\n                    return;\r\n                }\r\n                Ok(_) => continue,\r\n                Err(_) => return,\r\n            }\r\n        }\r\n    });\r\n    rx.recv_timeout(deadline).ok()\r\n}\r\n\r\n// [int->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n#[cfg(windows)]\r\n#[test]\r\nfn non_draining_controller_stall_evict_releases_writer_and_connection() {\r\n    init_home();\r\n    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms.\r\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\r\n\r\n    let name = unique_name();\r\n    let broker = Broker::bind(&name).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n\r\n    // ── Step 2: spawn the real SEED+burst PTY child from conn A, then DROP A so the\r\n    //    ONLY persistent brain conn later is the black-holed controller R. ──\r\n    let mut a = connect(&name);\r\n    send(\r\n        &mut a,\r\n        KIND_SPAWN,\r\n        serde_json::to_value(seed_burst_spawn_req(\"uwedge-r4-ep\")).unwrap(),\r\n    );\r\n    let sid = loop {\r\n        let f = read_frame(&mut a).expect(\"frame before spawned\");\r\n        if f.kind == KIND_SPAWNED {\r\n            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\r\n        }\r\n    };\r\n    drop(a);\r\n\r\n    // ── Steps 3+4: remote controller R. On a dedicated thread it subscribes Control\r\n    //    from_seq 0, drains output until it has seen `SEED` (proving a real controller\r\n    //    frame flowed), signals main so main can freeze the PRE-burst high-water, then\r\n    //    on main's go it fires the burst trigger and drains a few burst frames (a real\r\n    //    seq>0 frame delivered to the controller) and STOPS reading. It hands the\r\n    //    Stream back to main UNREAD — main holds it, never draining, so the broker's\r\n    //    R-writer parks in `write_frame` exactly as against a suspended brain. R's\r\n    //    Stream is NEVER dropped until the very end of the test. ──\r\n    let r_name = name.clone();\r\n    let (seed_tx, seed_rx) = std::sync::mpsc::channel::<(bool, u64)>();\r\n    let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();\r\n    let (hand_tx, hand_rx) = std::sync::mpsc::channel::<(Stream, u64)>();\r\n    thread::spawn(move || {\r\n        let mut r = connect(&r_name);\r\n        send(\r\n            &mut r,\r\n            KIND_SUBSCRIBE,\r\n            serde_json::to_value(SubscribeReq {\r\n                session_id: sid,\r\n                from_seq: 0,\r\n                intent: AttachIntent::Control,\r\n                by: Some(\"operator-one\".to_string()),\r\n            })\r\n            .unwrap(),\r\n        );\r\n        // Read until SUBSCRIBED.\r\n        loop {\r\n            match read_frame(&mut r) {\r\n                Ok(f) if f.kind == KIND_SUBSCRIBED => break,\r\n                Ok(_) => continue,\r\n                Err(_) => {\r\n                    let _ = seed_tx.send((false, 0));\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n        // Drain live output until the concatenated decoded bytes contain `SEED`\r\n        // (search the CONCATENATED stream — ConPTY echoes input and emits VT noise,\r\n        // so per-frame matching is unreliable). Track the max seq seen.\r\n        let mut acc: Vec<u8> = Vec::new();\r\n        let mut max_seq: u64 = 0;\r\n        let mut saw_seed = false;\r\n        let seed_deadline = Instant::now() + Duration::from_secs(30);\r\n        while Instant::now() < seed_deadline {\r\n            match read_frame(&mut r) {\r\n                Ok(f) if f.kind == spt_daemon::msg::KIND_OUTPUT => {\r\n                    let ev: spt_daemon::msg::OutputEvent =\r\n                        serde_json::from_value(f.payload).unwrap();\n…\n                }\n…\n            }\n        }\n…\n    });\n…\n}","startLine":222,"lineNumbers":[222,null,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,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,null,558,null,561,562,null,601,null,821]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\brain_decouple.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":822,"totalBytes":8943,"outputLines":180,"outputBytes":8943,"shownRange":{"start":369,"end":548},"nextOffset":549}}},"isError":false,"timestamp":1783730525956}}
{"type":"message","id":"b5062420","parentId":"af8e3639","timestamp":"2026-07-11T00:42:05.958Z","message":{"role":"toolResult","toolCallId":"call_O9YQOQBTBX468XlGkPc9fbNK|fc_08496348794f7e09016a51915e79988198beadb0d6eac7263c","toolName":"grep","content":[{"type":"text","text":"# spt-core/.worktrees/gate-w4/crates/spt-daemon/src/\n## broker.rs\n 1465|        // gate is acquired and BEFORE any byte is written — the same\n*1466|        // serialization point the old epoch-check-under-`send.lock()` had: both\n 1467|        // writers serialize on the one conn gate, and `become_controller` bumps\n 1468|        // the epoch BEFORE spawning the new writer, so whichever superseded\n 1469|        // writer next holds the gate observes the bump and bails before\n...\n 3070|                    );\n*3071|                    send_frame(&send, &frame);\n 3072|                }\n 3073|                KIND_ENDPOINT_INPUT => {\n 3074|                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n...\n 3186|                        );\n*3187|                        send_frame(send, &frame);\n 3188|                        return Ok(sid);\n 3189|                    }\n 3190|                    Gate::Racing => thread::sleep(WAKE_DEDUP_POLL),\n...\n 3333|        );\n*3334|        send_frame(send, &frame);\n 3335|        Ok(id)\n 3336|    }\n 3337|\n...\n 3373|        );\n*3374|        send_frame(send, &subscribed_envelope(req.session_id, outcome));\n 3375|        Ok(req.session_id)\n 3376|    }\n 3377|\n...\n 3480|                if req.ack {\n*3481|                    send_frame(\n 3482|                        send,\n 3483|                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),\n 3484|                    );\n...\n 3644|                            );\n*3645|                            send_frame(\n 3646|                                send,\n 3647|                                &endpoint_injected_envelope(&req.endpoint, true, false),\n 3648|                            );\n...\n 3667|                    );\n*3668|                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, true));\n 3669|                } else {\n 3670|                    eprintln!(\n 3671|                        \"ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED (idle window), not injected\",\n...\n 3673|                    );\n*3674|                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));\n 3675|                }\n 3676|                Ok(())\n 3677|            }\n...\n 3680|            None => {\n*3681|                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));\n 3682|                Ok(())\n 3683|            }\n 3684|        }\n...\n 3736|        );\n*3737|        send_frame(send, &frame);\n 3738|    }\n 3739|\n 3740|    /// Report the broker's OWN compiled image version (F-025). `env!` bakes the\n...\n 3756|        );\n*3757|        send_frame(send, &frame);\n 3758|    }\n 3759|\n 3760|    /// Answer the brain-subscriber stall-evict tally query (LIFECYCLE-TRUTH W2\n...\n 3770|        );\n*3771|        send_frame(send, &frame);\n 3772|    }\n 3773|\n 3774|    /// Handle a planned brain-restart request (ADR-0018 D3-3, Q3): raise the\n...\n 3794|        );\n*3795|        send_frame(send, &frame);\n 3796|    }\n 3797|\n 3798|    /// Dial a peer over the broker-owned endpoint (D4a).\n...\n 3823|                let observed = host.conn_remote_addr_json(conn_id);\n*3824|                send_frame(\n 3825|                    send,\n 3826|                    &net_dialed_envelope(conn_id, remote, None, true, observed),\n 3827|                );\n...\n 3854|                let observed = host.conn_remote_addr_json(conn_id);\n*3855|                send_frame(\n 3856|                    send,\n 3857|                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),\n 3858|                );\n...\n 3893|        // Immediate bare ack — the dial spawned; its outcome is a presence event.\n*3894|        send_frame(send, &Envelope::new(KIND_NET_DIAL_SUBMITTED, serde_json::Value::Null));\n 3895|        Ok(())\n 3896|    }\n 3897|\n...\n 3911|        let (conn_id, remote) = host.dial_loopback();\n*3912|        send_frame(\n 3913|            send,\n 3914|            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),\n 3915|        );\n...\n 3956|        };\n*3957|        send_frame(send, &frame);\n 3958|        Ok(())\n 3959|    }\n 3960|\n...\n 4004|                );\n*4005|                send_frame(send, &frame);\n 4006|            }\n 4007|            // A meet failure reuses the classified pair-joined reply shape (ok:\n 4008|            // false) — the CLI renders it before ever prompting for a code (the\n## conn.rs\n 350|    /// serialized gate is acquired and BEFORE any byte is written — the same\n*351|    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n 352|    /// superseded controller writer still bails atomically with respect to\n 353|    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n 354|    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n## nethost.rs\n 1382|        self.bounded_block_on(async move {\n*1383|            let mut send = entry.send.lock().await;\n 1384|            if !bytes.is_empty() {\n 1385|                send.write_all(&bytes).await?;\n 1386|            }"}],"details":{"scopePath":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\gate-w4\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":22,"fileCount":3,"files":["spt-core/.worktrees/gate-w4/crates/spt-daemon/src/broker.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/src/conn.rs","spt-core/.worktrees/gate-w4/crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/conn.rs","count":1},{"path":"spt-core/.worktrees/gate-w4/crates/spt-daemon/src/nethost.rs","count":1}],"truncated":true,"perFileLimitReached":20,"displayContent":"# spt-core/.worktrees/gate-w4/crates/spt-daemon/src/\n## broker.rs\n 1465│        // gate is acquired and BEFORE any byte is written — the same\n*1466│        // serialization point the old epoch-check-under-`send.lock()` had: both\n 1467│        // writers serialize on the one conn gate, and `become_controller` bumps\n 1468│        // the epoch BEFORE spawning the new writer, so whichever superseded\n 1469│        // writer next holds the gate observes the bump and bails before\n     │...\n 3070│                    );\n*3071│                    send_frame(&send, &frame);\n 3072│                }\n 3073│                KIND_ENDPOINT_INPUT => {\n 3074│                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n     │...\n 3186│                        );\n*3187│                        send_frame(send, &frame);\n 3188│                        return Ok(sid);\n 3189│                    }\n 3190│                    Gate::Racing => thread::sleep(WAKE_DEDUP_POLL),\n     │...\n 3333│        );\n*3334│        send_frame(send, &frame);\n 3335│        Ok(id)\n 3336│    }\n 3337│\n     │...\n 3373│        );\n*3374│        send_frame(send, &subscribed_envelope(req.session_id, outcome));\n 3375│        Ok(req.session_id)\n 3376│    }\n 3377│\n     │...\n 3480│                if req.ack {\n*3481│                    send_frame(\n 3482│                        send,\n 3483│                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),\n 3484│                    );\n     │...\n 3644│                            );\n*3645│                            send_frame(\n 3646│                                send,\n 3647│                                &endpoint_injected_envelope(&req.endpoint, true, false),\n 3648│                            );\n     │...\n 3667│                    );\n*3668│                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, true));\n 3669│                } else {\n 3670│                    eprintln!(\n 3671│                        \"ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED (idle window), not injected\",\n     │...\n 3673│                    );\n*3674│                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));\n 3675│                }\n 3676│                Ok(())\n 3677│            }\n     │...\n 3680│            None => {\n*3681│                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));\n 3682│                Ok(())\n 3683│            }\n 3684│        }\n     │...\n 3736│        );\n*3737│        send_frame(send, &frame);\n 3738│    }\n 3739│\n 3740│    /// Report the broker's OWN compiled image version (F-025). `env!` bakes the\n     │...\n 3756│        );\n*3757│        send_frame(send, &frame);\n 3758│    }\n 3759│\n 3760│    /// Answer the brain-subscriber stall-evict tally query (LIFECYCLE-TRUTH W2\n     │...\n 3770│        );\n*3771│        send_frame(send, &frame);\n 3772│    }\n 3773│\n 3774│    /// Handle a planned brain-restart request (ADR-0018 D3-3, Q3): raise the\n     │...\n 3794│        );\n*3795│        send_frame(send, &frame);\n 3796│    }\n 3797│\n 3798│    /// Dial a peer over the broker-owned endpoint (D4a).\n     │...\n 3823│                let observed = host.conn_remote_addr_json(conn_id);\n*3824│                send_frame(\n 3825│                    send,\n 3826│                    &net_dialed_envelope(conn_id, remote, None, true, observed),\n 3827│                );\n     │...\n 3854│                let observed = host.conn_remote_addr_json(conn_id);\n*3855│                send_frame(\n 3856│                    send,\n 3857│                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),\n 3858│                );\n     │...\n 3893│        // Immediate bare ack — the dial spawned; its outcome is a presence event.\n*3894│        send_frame(send, &Envelope::new(KIND_NET_DIAL_SUBMITTED, serde_json::Value::Null));\n 3895│        Ok(())\n 3896│    }\n 3897│\n     │...\n 3911│        let (conn_id, remote) = host.dial_loopback();\n*3912│        send_frame(\n 3913│            send,\n 3914│            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),\n 3915│        );\n     │...\n 3956│        };\n*3957│        send_frame(send, &frame);\n 3958│        Ok(())\n 3959│    }\n 3960│\n     │...\n 4004│                );\n*4005│                send_frame(send, &frame);\n 4006│            }\n 4007│            // A meet failure reuses the classified pair-joined reply shape (ok:\n 4008│            // false) — the CLI renders it before ever prompting for a code (the\n## conn.rs\n 350│    /// serialized gate is acquired and BEFORE any byte is written — the same\n*351│    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n 352│    /// superseded controller writer still bails atomically with respect to\n 353│    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n 354│    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n## nethost.rs\n 1382│        self.bounded_block_on(async move {\n*1383│            let mut send = entry.send.lock().await;\n 1384│            if !bytes.is_empty() {\n 1385│                send.write_all(&bytes).await?;\n 1386│            }"},"isError":false,"timestamp":1783730525958}}
{"type":"message","id":"760c8815","parentId":"b5062420","timestamp":"2026-07-11T00:42:06.010Z","message":{"role":"toolResult","toolCallId":"call_mttmKcmfXucN4lEyDUK2hgaI|fc_08496348794f7e09016a51915e797881988bbe62825b05e4e1","toolName":"read","content":[{"type":"text","text":"fn run_inject_worker(\n…\n) {\n…\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\n    // keeps it clear, so the bug-prone class re-settles forever.\n    let mut probe_unobservable = false;\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\n    // input into its observable output (a real TUI's input line does; a bare shell / a\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\n    // `event_rx` blocks here; events queue → one sequence at a time.\n    for envelope in event_rx {\n        // A well-behaved filter only emits in response to an event, but drop any\n        // unsolicited commands left in the channel so they can't be mis-attributed\n        // to this event's sequence.\n        while cmd_rx.try_recv().is_ok() {}\n\n        let envelope_copy = envelope.clone();\n\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\n        // unchanged (C-1 miss-tolerance).\n        let mut committed = false;\n        let mut disconnected = false;\n        let mut send_failed = false;\n        let mut integrity_lost = false;\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\n            // readiness before retyping). Skip only where the probe is known unobservable\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\n            if should_settle(attempt, probe_unobservable) {\n                let observed = settle_before_inject(&input, &log, &endpoint);\n                if attempt == 1 {\n                    probe_unobservable = !observed;\n                }\n            }\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\n            // Layer 2 sees only THIS sequence's output.\n            let seq_start = recover_log(&log).high_water();\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\n                SeqOutcome::SendFailed => {\n                    send_failed = true;\n                    break;\n                }\n                SeqOutcome::Disconnected => {\n                    disconnected = true;\n                    break;\n                }\n                SeqOutcome::Missed => {\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\n                    // integrity re-drive; the binary never signalled a complete sequence).\n                    break;\n                }\n                SeqOutcome::Committed { sent_text } => {\n                    committed = true;\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\n                        break; // delivered byte-complete (or echo-verify not applicable here)\n                    }\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\n                        eprintln!(\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\n                        );\n                        committed = false; // re-drive; only a verified commit counts\n                        continue;\n                    }\n                    // Second miss → the head is swallowed even after a re-drive.\n                    integrity_lost = true;\n                    break;\n                }\n            }\n        }\n\n        if send_failed {\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\n            respool_and_fault(\n                &endpoint,\n                envelope_copy,\n                &faulted,\n                &child,\n                \"stdin write failed\",\n            );\n            return;\n        }\n\n        if integrity_lost {\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\n            // committed fine; the terminal swallowed the head), so the worker survives.\n            respool_envelope(&endpoint, &envelope_copy);\n            eprintln!(\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\n            );\n            strikes = 0;\n            respawns.store(0, Ordering::Relaxed);\n            respooled_once.remove(&envelope_copy);\n            continue;\n        }\n\n        if committed {\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\n            // (a working binary heals it), and forget any earlier respool of THIS\n            // envelope (it succeeded → a future miss may respool it afresh).\n            strikes = 0;\n            respawns.store(0, Ordering::Relaxed);\n            respooled_once.remove(&envelope_copy);\n            continue;\n        }\n\n        if disconnected {\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\n            // terminate + exit for a bounded respawn.\n            respool_and_fault(\n                &endpoint,\n                envelope_copy,\n                &faulted,\n                &child,\n                \"binary stdout disconnected\",\n            );\n            return;\n        }\n\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\n        strikes += 1;\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\n            MissRespool::Respool => {\n                // First miss of this envelope — respool it ONCE (idle-eligible,\n                // poll-fed) without faulting.\n                respool_envelope(&endpoint, &envelope_copy);\n                eprintln!(\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\n                    inject_miss_strike_budget()\n                );\n            }\n            MissRespool::DeadLetter => {\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\n                // do NOT respool again (never a silent drop; it still counts as a\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\n                eprintln!(\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\n                    inject_miss_strike_budget()\n                );\n            }\n        }\n\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\n            // envelope was already respooled/dead-lettered above — no double respool.)\n            fault_translation_stamped(\n                &endpoint,\n                &faulted,\n                &child,\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\n            );\n            return;\n        }\n        // Miss tolerated (below the budget): the binary survives — continue to the\n        // next queued event through the SAME live binary.\n    }\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\n    child.terminate();\n}\n\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\n/// composed with the fault, on a REAL binary-death fault.\n// [impl->REQ-MSG-DELIVERY-AXES]\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\nfn respool_envelope(endpoint: &str, envelope: &str) {\n    let perch_path =\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\n        eprintln!(\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\n        );\n    }\n}\n\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\n/// in-flight delivery), then stamp the perch + fault the translation binary.\n// [impl->REQ-MSG-DELIVERY-AXES]\nfn respool_and_fault(\n    endpoint: &str,\n    envelope: String,\n    faulted: &AtomicBool,\n    child: &TranslationChild,\n…\n) {\n…\n}\n\n[Showing lines 2159-2368 of 5906. Use :2369 to continue]"}],"details":{"truncation":{"content":"    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\r\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\r\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\r\n    // keeps it clear, so the bug-prone class re-settles forever.\r\n    let mut probe_unobservable = false;\r\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\r\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\r\n    // input into its observable output (a real TUI's input line does; a bare shell / a\r\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\r\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\r\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\r\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\r\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\r\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\r\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\r\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\r\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\r\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\r\n    // `event_rx` blocks here; events queue → one sequence at a time.\r\n    for envelope in event_rx {\r\n        // A well-behaved filter only emits in response to an event, but drop any\r\n        // unsolicited commands left in the channel so they can't be mis-attributed\r\n        // to this event's sequence.\r\n        while cmd_rx.try_recv().is_ok() {}\r\n\r\n        let envelope_copy = envelope.clone();\r\n\r\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\r\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\r\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\r\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\r\n        // unchanged (C-1 miss-tolerance).\r\n        let mut committed = false;\r\n        let mut disconnected = false;\r\n        let mut send_failed = false;\r\n        let mut integrity_lost = false;\r\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\r\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\r\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\r\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\r\n            // readiness before retyping). Skip only where the probe is known unobservable\r\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\r\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\r\n            if should_settle(attempt, probe_unobservable) {\r\n                let observed = settle_before_inject(&input, &log, &endpoint);\r\n                if attempt == 1 {\r\n                    probe_unobservable = !observed;\r\n                }\r\n            }\r\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\r\n            // Layer 2 sees only THIS sequence's output.\r\n            let seq_start = recover_log(&log).high_water();\r\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\r\n                SeqOutcome::SendFailed => {\r\n                    send_failed = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Disconnected => {\r\n                    disconnected = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Missed => {\r\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\r\n                    // integrity re-drive; the binary never signalled a complete sequence).\r\n                    break;\r\n                }\r\n                SeqOutcome::Committed { sent_text } => {\r\n                    committed = true;\r\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\r\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\r\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\r\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\r\n                        break; // delivered byte-complete (or echo-verify not applicable here)\r\n                    }\r\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\r\n                        eprintln!(\r\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\r\n                        );\r\n                        committed = false; // re-drive; only a verified commit counts\r\n                        continue;\r\n                    }\r\n                    // Second miss → the head is swallowed even after a re-drive.\r\n                    integrity_lost = true;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if send_failed {\r\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\r\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"stdin write failed\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        if integrity_lost {\r\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\r\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\r\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\r\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\r\n            // committed fine; the terminal swallowed the head), so the worker survives.\r\n            respool_envelope(&endpoint, &envelope_copy);\r\n            eprintln!(\r\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\r\n            );\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if committed {\r\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\r\n            // (a working binary heals it), and forget any earlier respool of THIS\r\n            // envelope (it succeeded → a future miss may respool it afresh).\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if disconnected {\r\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\r\n            // terminate + exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"binary stdout disconnected\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\r\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\r\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\r\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\r\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\r\n        strikes += 1;\r\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\r\n            MissRespool::Respool => {\r\n                // First miss of this envelope — respool it ONCE (idle-eligible,\r\n                // poll-fed) without faulting.\r\n                respool_envelope(&endpoint, &envelope_copy);\r\n                eprintln!(\r\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n            MissRespool::DeadLetter => {\r\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\r\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\r\n                // do NOT respool again (never a silent drop; it still counts as a\r\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\r\n                eprintln!(\r\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n        }\r\n\r\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\r\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\r\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\r\n            // envelope was already respooled/dead-lettered above — no double respool.)\r\n            fault_translation_stamped(\r\n                &endpoint,\r\n                &faulted,\r\n                &child,\r\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\r\n            );\r\n            return;\r\n        }\r\n        // Miss tolerated (below the budget): the binary survives — continue to the\r\n        // next queued event through the SAME live binary.\r\n    }\r\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\r\n    child.terminate();\r\n}\r\n\r\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\r\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\r\n/// composed with the fault, on a REAL binary-death fault.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\nfn respool_envelope(endpoint: &str, envelope: &str) {\r\n    let perch_path =\r\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\r\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\r\n        eprintln!(\r\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\r\n        );\r\n    }\r\n}\r\n\r\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\r\n/// in-flight delivery), then stamp the perch + fault the translation binary.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\nfn respool_and_fault(\r\n    endpoint: &str,\r\n    envelope: String,\r\n    faulted: &AtomicBool,\r\n    child: &TranslationChild,\r","truncated":true,"truncatedBy":"lines","totalLines":3748,"totalBytes":11272,"outputLines":210,"outputBytes":11272,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn run_inject_worker(\n…\n) {\n…\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\r\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\r\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\r\n    // keeps it clear, so the bug-prone class re-settles forever.\r\n    let mut probe_unobservable = false;\r\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\r\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\r\n    // input into its observable output (a real TUI's input line does; a bare shell / a\r\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\r\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\r\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\r\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\r\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\r\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\r\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\r\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\r\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\r\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\r\n    // `event_rx` blocks here; events queue → one sequence at a time.\r\n    for envelope in event_rx {\r\n        // A well-behaved filter only emits in response to an event, but drop any\r\n        // unsolicited commands left in the channel so they can't be mis-attributed\r\n        // to this event's sequence.\r\n        while cmd_rx.try_recv().is_ok() {}\r\n\r\n        let envelope_copy = envelope.clone();\r\n\r\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\r\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\r\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\r\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\r\n        // unchanged (C-1 miss-tolerance).\r\n        let mut committed = false;\r\n        let mut disconnected = false;\r\n        let mut send_failed = false;\r\n        let mut integrity_lost = false;\r\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\r\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\r\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\r\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\r\n            // readiness before retyping). Skip only where the probe is known unobservable\r\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\r\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\r\n            if should_settle(attempt, probe_unobservable) {\r\n                let observed = settle_before_inject(&input, &log, &endpoint);\r\n                if attempt == 1 {\r\n                    probe_unobservable = !observed;\r\n                }\r\n            }\r\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\r\n            // Layer 2 sees only THIS sequence's output.\r\n            let seq_start = recover_log(&log).high_water();\r\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\r\n                SeqOutcome::SendFailed => {\r\n                    send_failed = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Disconnected => {\r\n                    disconnected = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Missed => {\r\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\r\n                    // integrity re-drive; the binary never signalled a complete sequence).\r\n                    break;\r\n                }\r\n                SeqOutcome::Committed { sent_text } => {\r\n                    committed = true;\r\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\r\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\r\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\r\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\r\n                        break; // delivered byte-complete (or echo-verify not applicable here)\r\n                    }\r\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\r\n                        eprintln!(\r\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\r\n                        );\r\n                        committed = false; // re-drive; only a verified commit counts\r\n                        continue;\r\n                    }\r\n                    // Second miss → the head is swallowed even after a re-drive.\r\n                    integrity_lost = true;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if send_failed {\r\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\r\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"stdin write failed\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        if integrity_lost {\r\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\r\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\r\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\r\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\r\n            // committed fine; the terminal swallowed the head), so the worker survives.\r\n            respool_envelope(&endpoint, &envelope_copy);\r\n            eprintln!(\r\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\r\n            );\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if committed {\r\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\r\n            // (a working binary heals it), and forget any earlier respool of THIS\r\n            // envelope (it succeeded → a future miss may respool it afresh).\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if disconnected {\r\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\r\n            // terminate + exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"binary stdout disconnected\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\r\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\r\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\r\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\r\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\r\n        strikes += 1;\r\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\r\n            MissRespool::Respool => {\r\n                // First miss of this envelope — respool it ONCE (idle-eligible,\r\n                // poll-fed) without faulting.\r\n                respool_envelope(&endpoint, &envelope_copy);\r\n                eprintln!(\r\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n            MissRespool::DeadLetter => {\r\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\r\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\r\n                // do NOT respool again (never a silent drop; it still counts as a\r\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\r\n                eprintln!(\r\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n        }\r\n\r\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\r\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\r\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\r\n            // envelope was already respooled/dead-lettered above — no double respool.)\r\n            fault_translation_stamped(\r\n                &endpoint,\r\n                &faulted,\r\n                &child,\r\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\r\n            );\r\n            return;\r\n        }\r\n        // Miss tolerated (below the budget): the binary survives — continue to the\r\n        // next queued event through the SAME live binary.\r\n    }\r\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\r\n    child.terminate();\r\n}\r\n\r\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\r\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\r\n/// composed with the fault, on a REAL binary-death fault.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\nfn respool_envelope(endpoint: &str, envelope: &str) {\r\n    let perch_path =\r\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\r\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\r\n        eprintln!(\r\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\r\n        );\r\n    }\r\n}\r\n\r\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\r\n/// in-flight delivery), then stamp the perch + fault the translation binary.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\nfn respool_and_fault(\r\n    endpoint: &str,\r\n    envelope: String,\r\n    faulted: &AtomicBool,\r\n    child: &TranslationChild,\r\n…\n) {\n…\n}","startLine":2133,"lineNumbers":[2133,null,2143,null,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368,null,2370,null,2373]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":5906,"totalBytes":11272,"outputLines":210,"outputBytes":11272,"shownRange":{"start":2159,"end":2368},"nextOffset":2369}}},"isError":false,"timestamp":1783730526010}}
{"type":"message","id":"815570f3","parentId":"760c8815","timestamp":"2026-07-11T00:42:06.011Z","message":{"role":"toolResult","toolCallId":"call_5HPjmc0HmnhZzXQzFEmsbTTt|fc_08496348794f7e09016a51915e799081988ac1394d63f5b0b0","toolName":"grep","content":[{"type":"text","text":"# spt-core/.git/logs/refs/\n\n## heads/\n### update-wedge-drain-drive\n 1|0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n*2|3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n### uwedge2-resume-viewer\n 1|0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n*2|31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\n*3|fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n 4|7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n 5|14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n 6|2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n*7|2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n\n### fix/\n#### update-wedge-bounded-conn-write\n 4|ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n*5|52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\n*6|d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n\n### release-merge/\n#### uwedge-r4\n*1|0000000000000000000000000000000000000000 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783672786 -0700\tbranch: Created from 0a306c9\n*2|0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 53cfab5cc56f04a25ce6a722fd85a0da20a34a1e Reavo End <decidel@gmail.com> 1783672901 -0700\tcommit (merge): merge: r4 update-wedge bounded conn write (f10e5e9) onto v0.30.5 main\n\n### triage/\n#### msg-identity-milestone\n*1|0000000000000000000000000000000000000000 d73e19e12f7192a08d6ee502d21d03809a57a921 Reavo End <decidel@gmail.com> 1783689751 -0700\tbranch: Created from d73e19e\n*2|d73e19e12f7192a08d6ee502d21d03809a57a921 d4f046c7e225312b038d6828a173cff6ea1f381d Reavo End <decidel@gmail.com> 1783690299 -0700\tcommit: triage(msg-identity): register milestone REQs + dispatch doc (messaging delivery-integrity + endpoint identity)\n\n## remotes/origin/\n### main\n 178|3ea09bf51b5c1786d8a575a42ed2c4166af8346e 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783607971 -0700\tfetch origin main --quiet: fast-forward\n*179|31367027c53d106d12b8f177a58f7d7d747749bb b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa Reavo End <decidel@gmail.com> 1783634258 -0700\tfetch origin main: fast-forward\n*180|b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783648960 -0700\tfetch origin main: fast-forward\n*181|0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 d73e19e12f7192a08d6ee502d21d03809a57a921 Reavo End <decidel@gmail.com> 1783684900 -0700\tfetch origin main: fast-forward\n*182|d73e19e12f7192a08d6ee502d21d03809a57a921 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710757 -0700\tfetch origin main: fast-forward\n 183|17b770a18c858182038284f08e8ba0d93bb91263 2ea0ac0e3a513735901fb515a9fc17b82db5b110 Reavo End <decidel@gmail.com> 1783728270 -0700\tfetch origin: fast-forward\n### update-wedge-drain-drive\n*1|0000000000000000000000000000000000000000 ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606863 -0700\tupdate by push\n### uwedge2-resume-viewer\n*1|0000000000000000000000000000000000000000 fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632838 -0700\tupdate by push\n*2|fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633072 -0700\tupdate by push\n\n### fix/\n#### update-wedge-bounded-conn-write\n*1|0000000000000000000000000000000000000000 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667861 -0700\tupdate by push\n*2|d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783669069 -0700\tupdate by push"}],"details":{"scopePath":"spt-core/.git/logs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":19,"fileCount":9,"files":["spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","spt-core/.git/logs/refs/heads/release-merge/uwedge-r4","spt-core/.git/logs/refs/heads/triage/msg-identity-milestone","spt-core/.git/logs/refs/heads/update-wedge-drain-drive","spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","spt-core/.git/logs/refs/remotes/origin/fix/update-wedge-bounded-conn-write","spt-core/.git/logs/refs/remotes/origin/main","spt-core/.git/logs/refs/remotes/origin/update-wedge-drain-drive","spt-core/.git/logs/refs/remotes/origin/uwedge2-resume-viewer"],"fileMatches":[{"path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","count":2},{"path":"spt-core/.git/logs/refs/heads/release-merge/uwedge-r4","count":2},{"path":"spt-core/.git/logs/refs/heads/triage/msg-identity-milestone","count":2},{"path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive","count":1},{"path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","count":3},{"path":"spt-core/.git/logs/refs/remotes/origin/fix/update-wedge-bounded-conn-write","count":2},{"path":"spt-core/.git/logs/refs/remotes/origin/main","count":4},{"path":"spt-core/.git/logs/refs/remotes/origin/update-wedge-drain-drive","count":1},{"path":"spt-core/.git/logs/refs/remotes/origin/uwedge2-resume-viewer","count":2}],"truncated":false,"displayContent":"# spt-core/.git/logs/refs/\n\n## heads/\n### update-wedge-drain-drive\n 1│0000000000000000000000000000000000000000 3ea09bf51b5c1786d8a575a42ed2c4166af8346e Reavo End <decidel@gmail.com> 1783600816 -0700\tbranch: Created from 3ea09bf\n*2│3ea09bf51b5c1786d8a575a42ed2c4166af8346e ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606318 -0700\tcommit: UPDATE-WEDGE: brain self-drives the controller reap so a killed prior gen can't strand the promote (counter-54)\n### uwedge2-resume-viewer\n 1│0000000000000000000000000000000000000000 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783631244 -0700\tbranch: Created from origin/main\n*2│31367027c53d106d12b8f177a58f7d7d747749bb fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632828 -0700\tcommit: fix(update-wedge-2): resume re-attaches as Viewer, never steal a controller (REQ-BRAIN-RESUME-NO-CONTROL-STEAL)\n*3│fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633055 -0700\tcommit: doc(update-wedge-2): correct REQ-BRAIN-RESUME-NO-CONTROL-STEAL title survivor-model\n 4│7782bdadc72b6798261d35423a3b983092555983 14cb14705958143629d8d3568b2b643a3ffa30c9 Reavo End <decidel@gmail.com> 1783640311 -0700\tcommit: diag(update-wedge-3): net-runtime starvation seam FALSIFIED by rig — re-open at brain drain\n 5│14cb14705958143629d8d3568b2b643a3ffa30c9 2e395f00e90d6f8d5535e8c296f69b226b6712a8 Reavo End <decidel@gmail.com> 1783641329 -0700\tcommit: docs(update-wedge-3): code-read — reap-drive exonerated, seam is SharedSend self-deadlock on brain conn\n 6│2e395f00e90d6f8d5535e8c296f69b226b6712a8 2e29fcbd4abb9400dc5c3d009bf427cd1d477123 Reavo End <decidel@gmail.com> 1783641880 -0700\tcommit: reqs(update-wedge-3): mint REQ-BRAIN-RESUME-NO-CONN-DEADLOCK [doc,impl,int] + defer Option-C hazard\n*7│2e29fcbd4abb9400dc5c3d009bf427cd1d477123 03c71093ca70e76a86d42087e753e3cca0793095 Reavo End <decidel@gmail.com> 1783643743 -0700\tcommit: fix(update-wedge-3): daemon resume is cursor-only — kills the SharedSend brain-conn deadlock (REQ-BRAIN-RESUME-NO-CONN-DEADLOCK)\n\n### fix/\n#### update-wedge-bounded-conn-write\n 4│ff2599a1c0a5faf2b7eef89d66f12a75abf5c347 52b0d8fd9526f7c79181a683ef20d412e0455549 Reavo End <decidel@gmail.com> 1783666714 -0700\tcommit: test(uwedge-r4): RED-first non-draining-controller physical-retirement gate\n*5│52b0d8fd9526f7c79181a683ef20d412e0455549 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667683 -0700\tcommit: fix(uwedge-r4): bounded+cancelable+poison-on-failure writes on every physical broker conn\n*6│d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783668290 -0700\tcommit: test(uwedge-r4): false_promote wedge re-sourced to a static seed (doyle Option C)\n\n### release-merge/\n#### uwedge-r4\n*1│0000000000000000000000000000000000000000 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783672786 -0700\tbranch: Created from 0a306c9\n*2│0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 53cfab5cc56f04a25ce6a722fd85a0da20a34a1e Reavo End <decidel@gmail.com> 1783672901 -0700\tcommit (merge): merge: r4 update-wedge bounded conn write (f10e5e9) onto v0.30.5 main\n\n### triage/\n#### msg-identity-milestone\n*1│0000000000000000000000000000000000000000 d73e19e12f7192a08d6ee502d21d03809a57a921 Reavo End <decidel@gmail.com> 1783689751 -0700\tbranch: Created from d73e19e\n*2│d73e19e12f7192a08d6ee502d21d03809a57a921 d4f046c7e225312b038d6828a173cff6ea1f381d Reavo End <decidel@gmail.com> 1783690299 -0700\tcommit: triage(msg-identity): register milestone REQs + dispatch doc (messaging delivery-integrity + endpoint identity)\n\n## remotes/origin/\n### main\n 178│3ea09bf51b5c1786d8a575a42ed2c4166af8346e 31367027c53d106d12b8f177a58f7d7d747749bb Reavo End <decidel@gmail.com> 1783607971 -0700\tfetch origin main --quiet: fast-forward\n*179│31367027c53d106d12b8f177a58f7d7d747749bb b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa Reavo End <decidel@gmail.com> 1783634258 -0700\tfetch origin main: fast-forward\n*180│b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 Reavo End <decidel@gmail.com> 1783648960 -0700\tfetch origin main: fast-forward\n*181│0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047 d73e19e12f7192a08d6ee502d21d03809a57a921 Reavo End <decidel@gmail.com> 1783684900 -0700\tfetch origin main: fast-forward\n*182│d73e19e12f7192a08d6ee502d21d03809a57a921 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710757 -0700\tfetch origin main: fast-forward\n 183│17b770a18c858182038284f08e8ba0d93bb91263 2ea0ac0e3a513735901fb515a9fc17b82db5b110 Reavo End <decidel@gmail.com> 1783728270 -0700\tfetch origin: fast-forward\n### update-wedge-drain-drive\n*1│0000000000000000000000000000000000000000 ce053dc930104afda280504b7e489f27462d47bb Reavo End <decidel@gmail.com> 1783606863 -0700\tupdate by push\n### uwedge2-resume-viewer\n*1│0000000000000000000000000000000000000000 fb499dbfc29521daab72cb49e1768aeb5f3c74bc Reavo End <decidel@gmail.com> 1783632838 -0700\tupdate by push\n*2│fb499dbfc29521daab72cb49e1768aeb5f3c74bc 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633072 -0700\tupdate by push\n\n### fix/\n#### update-wedge-bounded-conn-write\n*1│0000000000000000000000000000000000000000 d634b67b2962ab919d0a03a280d63695e48214b3 Reavo End <decidel@gmail.com> 1783667861 -0700\tupdate by push\n*2│d634b67b2962ab919d0a03a280d63695e48214b3 f10e5e92ffbb016dfa4bc337a22b1f6cdcd75a0a Reavo End <decidel@gmail.com> 1783669069 -0700\tupdate by push"},"isError":false,"timestamp":1783730526011}}
{"type":"message","id":"1a9d52b5","parentId":"815570f3","timestamp":"2026-07-11T00:42:06.052Z","message":{"role":"toolResult","toolCallId":"call_GhV4IoXr0Ep0SPheBpz1HUYh|fc_08496348794f7e09016a51915e7988819890d32bba4191040d","toolName":"read","content":[{"type":"text","text":"fn controller_writer(\n…\n) {\n…\n    while let Ok(msg) = rx.recv() {\n…\n        if let Some(seq) = seq {\n…\n            // gap — see [`contiguous_advance`].\n            contiguous_advance(&delivered_through, seq);\n        }\n    }\n}\n\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\n/// was removed as a delivery path; it did not submit on a modern TUI).\n///\n/// Holds the shared inject floor (the controller-input buffer, also touched by\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\n/// (events serialize — never two uncommitted sequences interleaved), the shared\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\n/// while the worker drives an event), and the FAULT flag the worker raises when a\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\nstruct Translation {\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\n    floor: Arc<Mutex<InjectFloor>>,\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\n    /// and are driven one-at-a-time so two sequences never interleave.\n    event_tx: Sender<String>,\n    /// The live child — shared so `dispatch_input` fires content-free pings while\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\n    child: Arc<TranslationChild>,\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\n    /// stops pinging.\n    faulted: Arc<AtomicBool>,\n}\n\n/// A child the broker hosts under a PTY — the §B-owned resources for one\n/// session, plus its gapless-resume output log.\nstruct HostedSession {\n    /// The PTY + child (PTY master/writer + spawned child process).\n    session: Arc<PtySession>,\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\n    /// so a paste burst that fills the harness's input buffer parks only the\n    /// writer thread, never the broker dispatch thread.\n    input: Arc<InputWriter>,\n    /// The output drain pump (kept so teardown can stop it).\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\n    drain: Drain,\n    /// The sequenced, buffered output + current subscriber.\n    log: Arc<Mutex<OutputLog>>,\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\n    endpoint: String,\n    /// The translation-binary attachment (Layer C), or `None` for a session with\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\n    /// (no raw PTY inject; ADR-0022 amendment).\n    translation: Option<Arc<Translation>>,\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\n    /// from the exact same command (`build_translation`) without re-reading the\n    /// manifest (works for a test session with a direct argv AND a production\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\n    /// restart to the swapped-in argv.\n    translation_argv: Option<Vec<String>>,\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\n    translation_respawns: Arc<AtomicU32>,\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\n    /// legacy/test/shell spawn. A live adapter update finds every live session\n    /// whose `adapter` matches to cycle its resident children.\n    adapter: String,\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\n    /// apply target for a live update's CRC swap.\n    install_dir: Option<std::path::PathBuf>,\n}\n\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\ntype InputRecord = Vec<u8>;\n\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\n/// few hundred KB of real paste.\nconst INPUT_QUEUE_DEPTH: usize = 4096;\n\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\n/// is the invariant, not its exact value.\nfn input_queue_depth() -> usize {\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\n        Ok(n) => n\n            .parse::<usize>()\n            .ok()\n            .filter(|d| *d > 0)\n            .unwrap_or(INPUT_QUEUE_DEPTH),\n        Err(_) => INPUT_QUEUE_DEPTH,\n    }\n}\n\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\nstruct InputWriter {\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\n    tx: SyncSender<InputRecord>,\n    /// True while the FIFO is saturated and input is being dropped; cleared on\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\n    /// `input_backpressure` so the operator sees the drop.\n    backpressure: AtomicBool,\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\n    endpoint: String,\n    /// The writer thread handle (held for ownership; the thread drains until the\n    /// last `tx` clone drops at session teardown, then exits).\n    _writer: JoinHandle<()>,\n}\n\nimpl InputWriter {\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\n    /// whose PTY write handle this thread now exclusively owns.\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\n        let writer = thread::spawn(move || input_writer(session, rx));\n        Arc::new(InputWriter {\n            tx,\n            backpressure: AtomicBool::new(false),\n            endpoint,\n            _writer: writer,\n        })\n    }\n\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\n    /// thread never blocks here, however stuck the harness is.\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n    fn enqueue(&self, bytes: InputRecord) -> bool {\n        match self.tx.try_send(bytes) {\n            Ok(()) => {\n                // Heal: input is flowing again — clear a prior backpressure stamp.\n                if self.backpressure.swap(false, Ordering::AcqRel) {\n                    self.stamp_backpressure(false);\n                }\n                true\n            }\n            Err(TrySendError::Full(_)) => {\n                // DROP + surface, but stamp only on the rising edge.\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\n                    self.stamp_backpressure(true);\n                }\n                false\n            }\n            // The writer thread is gone (session teardown) — nothing to do.\n            Err(TrySendError::Disconnected(_)) => false,\n        }\n    }\n\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\n    /// broker is the single writer). Best-effort: a session served without a\n    /// perch (raw broker tests) has nowhere to record.\n    fn stamp_backpressure(&self, dropping: bool) {\n        if self.endpoint.is_empty() {\n            return;\n        }\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\n    }\n}\n\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\n/// records from `rx` and apply each via the BLOCKING\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\n/// wedged harness parks this thread (and only this thread); a write error is\n/// swallowed (the same best-effort the inline callers had). The thread exits\n/// when the last `tx` clone drops at session teardown.\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\n    while let Ok(bytes) = rx.recv() {\n        let _ = session.write_input(&bytes);\n    }\n}\n\n/// Drain the inject floor's buffered controller input to the PTY **after** an\n/// injected sequence, then release the floor. Each step takes the buffered bytes\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\n/// hold a lock across a PTY write — now never block on it either); input arriving\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\n/// drains empty and the floor frees.\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\n/// pid is alive (the F-e mis-diagnosed \"translation-binary liveness decay\").\n/// Recovering the guard (the effect-journal poison-tolerance idiom, bug #16 /\n/// REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) keeps the floor usable so ONE panic cannot\n/// cascade the whole session's delivery dead.\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\nfn lock_floor(floor: &Mutex<InjectFloor>) -> std::sync::MutexGuard<'_, InjectFloor> {\n    floor.lock().unwrap_or_else(|p| p.into_inner())\n}\n\n/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\n/// (B-1, REQ-HAZARD-BROKER-FLOOR-LOCK-POISON). A brain-only self-update keeps the\n/// broker process — and every one of its `Mutex`es — ALIVE (REQ-UPD-3), so a single\n/// panic while another thread held one of these locks would otherwise POISON it\n/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\n/// dies, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read\n/// deadline elapsed\", brain.rs) while non-locked ops keep working. The `into_inner`\n/// idiom (identical to [`lock_floor`] and the effect journal, bug #16) keeps the\n/// broker serving. Safe for the SHORT map ops it guards (`sessions` / `pair_holds`\n/// inserts+removes stay coherent on recovery — the same argument as the floor). The\n/// per-session `OutputLog` RING needs the extra coherence clamp of [`recover_log`].\n/// CLASS INVARIANT (KNOWN-HAZARDS): NO bare `.lock().unwrap()` on a broker-resident\n/// lock reachable from serve/dispatch — use `recover`/`recover_log` or a documented\n/// fail-fast justification.\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\nfn recover<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {\n    m.lock().unwrap_or_else(|p| p.into_inner())\n}\n\n/// [`recover`] for the per-session [`OutputLog`] RING, with a COHERENCE CLAMP on the\n/// poison-recovery path (B-1, doyle ruling). A panic mid-[`OutputLog::append`] can\n/// leave the ring torn (a bumped `next_seq` past the ring, an over-cap ring from an\n/// interrupted prune, a non-monotonic front/back), and replaying those bytes to a\n/// subscriber risks garbage. So ONLY on the recovered (poisoned) branch, run\n/// [`OutputLog::clamp_or_reset`]: cheap-check the ring invariants and RESET it empty\n/// (+ loud log) on any violation. Lost scrollback self-heals on the next PTY output +\n/// repaint — strictly better than a permanent wedge (fail-fast reintroduces the very\n/// wedge B-1 kills) or a torn serve (blind recover). The healthy `Ok` path is a pure\n/// lock — no clamp, no cost on the hot append path.\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\nfn recover_log(m: &Mutex<OutputLog>) -> std::sync::MutexGuard<'_, OutputLog> {\n    match m.lock() {\n        Ok(g) => g,\n        Err(p) => {\n            let mut g = p.into_inner();\n            let _ = g.clamp_or_reset(); // bool is for the unit test; the effect is the reset\n            g\n        }\n    }\n}\n\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\n    loop {\n        let chunk = lock_floor(floor).take_or_release();\n        match chunk {\n            Some(bytes) => {\n                input.enqueue(bytes);\n            }\n            None => break,\n        }\n    }\n}\n\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\n///\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\n/// deadline-miss or binary death: flush+release anyway (operator input is never\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\n/// (session teardown), reaping the child.\n///\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\n/// sequence and the operator's live controller share one PTY without clobbering\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\n/// the \"never park forever\" bound applied to injection.\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\n/// (reused; only the translation child is created). Extracted verbatim from\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\n/// rebuild a session's translation child from the NEW binary without disturbing\n/// the PTY / output log / session (brain-parity).\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\nfn build_translation(\n    argv: &[String],\n    endpoint_id: &str,\n    input: &Arc<InputWriter>,\n    respawns: Arc<AtomicU32>,\n    log: &Arc<Mutex<OutputLog>>,\n) -> Option<Arc<Translation>> {\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\n    match TranslationChild::spawn(argv, cmd_tx) {\n        Ok(child) => {\n            let child = Arc::new(child);\n            // First line: identity (content-free of any PTY bytes).\n            let _ = child.send(&ToBinary::Init {\n                endpoint_id: endpoint_id.to_string(),\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\n            });\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\n            let faulted = Arc::new(AtomicBool::new(false));\n            let (event_tx, event_rx) = channel::<String>();\n            let w_input = Arc::clone(input);\n            let w_child = Arc::clone(&child);\n            let w_floor = Arc::clone(&floor);\n            let w_faulted = Arc::clone(&faulted);\n            let w_respawns = Arc::clone(&respawns);\n            let w_log = Arc::clone(log);\n            let w_endpoint = endpoint_id.to_string();\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\n            // recovery missed, anything) must NOT silently kill the thread and orphan\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\n            let sup_child = Arc::clone(&child);\n            let sup_faulted = Arc::clone(&faulted);\n            let sup_endpoint = endpoint_id.to_string();\n            thread::spawn(move || {\n                run_inject_worker_supervised(\n                    sup_child,\n                    sup_faulted,\n                    sup_endpoint,\n                    std::panic::AssertUnwindSafe(move || {\n                        run_inject_worker(\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\n                            w_log, w_endpoint,\n                        );\n                    }),\n                );\n            });\n            Some(Arc::new(Translation {\n                floor,\n                event_tx,\n                child,\n                faulted,\n            }))\n        }\n        Err(e) => {\n            eprintln!(\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\n            );\n            None\n        }\n    }\n}\n\n/// The resident translation binary path declared by the manifest at\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\n\n[Showing lines 1519-1888 of 5906. Use :1889 to continue]"}],"details":{"truncation":{"content":"            // gap — see [`contiguous_advance`].\r\n            contiguous_advance(&delivered_through, seq);\r\n        }\r\n    }\r\n}\r\n\r\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\r\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\r\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\r\n/// was removed as a delivery path; it did not submit on a modern TUI).\r\n///\r\n/// Holds the shared inject floor (the controller-input buffer, also touched by\r\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\r\n/// (events serialize — never two uncommitted sequences interleaved), the shared\r\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\r\n/// while the worker drives an event), and the FAULT flag the worker raises when a\r\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\r\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\r\nstruct Translation {\r\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\r\n    /// and are driven one-at-a-time so two sequences never interleave.\r\n    event_tx: Sender<String>,\r\n    /// The live child — shared so `dispatch_input` fires content-free pings while\r\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\r\n    child: Arc<TranslationChild>,\r\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\r\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\r\n    /// stops pinging.\r\n    faulted: Arc<AtomicBool>,\r\n}\r\n\r\n/// A child the broker hosts under a PTY — the §B-owned resources for one\r\n/// session, plus its gapless-resume output log.\r\nstruct HostedSession {\r\n    /// The PTY + child (PTY master/writer + spawned child process).\r\n    session: Arc<PtySession>,\r\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\r\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\r\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\r\n    /// so a paste burst that fills the harness's input buffer parks only the\r\n    /// writer thread, never the broker dispatch thread.\r\n    input: Arc<InputWriter>,\r\n    /// The output drain pump (kept so teardown can stop it).\r\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\r\n    drain: Drain,\r\n    /// The sequenced, buffered output + current subscriber.\r\n    log: Arc<Mutex<OutputLog>>,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\r\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\r\n    endpoint: String,\r\n    /// The translation-binary attachment (Layer C), or `None` for a session with\r\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\r\n    /// (no raw PTY inject; ADR-0022 amendment).\r\n    translation: Option<Arc<Translation>>,\r\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\r\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\r\n    /// from the exact same command (`build_translation`) without re-reading the\r\n    /// manifest (works for a test session with a direct argv AND a production\r\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\r\n    /// restart to the swapped-in argv.\r\n    translation_argv: Option<Vec<String>>,\r\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\r\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\r\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\r\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\r\n    translation_respawns: Arc<AtomicU32>,\r\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\r\n    /// legacy/test/shell spawn. A live adapter update finds every live session\r\n    /// whose `adapter` matches to cycle its resident children.\r\n    adapter: String,\r\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\r\n    /// apply target for a live update's CRC swap.\r\n    install_dir: Option<std::path::PathBuf>,\r\n}\r\n\r\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\r\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\ntype InputRecord = Vec<u8>;\r\n\r\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\r\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\r\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\r\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\r\n/// few hundred KB of real paste.\r\nconst INPUT_QUEUE_DEPTH: usize = 4096;\r\n\r\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\r\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\r\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\r\n/// is the invariant, not its exact value.\r\nfn input_queue_depth() -> usize {\r\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\r\n        Ok(n) => n\r\n            .parse::<usize>()\r\n            .ok()\r\n            .filter(|d| *d > 0)\r\n            .unwrap_or(INPUT_QUEUE_DEPTH),\r\n        Err(_) => INPUT_QUEUE_DEPTH,\r\n    }\r\n}\r\n\r\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\r\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\r\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\r\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\r\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\r\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\r\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\r\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\r\nstruct InputWriter {\r\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\r\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\r\n    tx: SyncSender<InputRecord>,\r\n    /// True while the FIFO is saturated and input is being dropped; cleared on\r\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\r\n    /// `input_backpressure` so the operator sees the drop.\r\n    backpressure: AtomicBool,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\r\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\r\n    endpoint: String,\r\n    /// The writer thread handle (held for ownership; the thread drains until the\r\n    /// last `tx` clone drops at session teardown, then exits).\r\n    _writer: JoinHandle<()>,\r\n}\r\n\r\nimpl InputWriter {\r\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\r\n    /// whose PTY write handle this thread now exclusively owns.\r\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\r\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\r\n        let writer = thread::spawn(move || input_writer(session, rx));\r\n        Arc::new(InputWriter {\r\n            tx,\r\n            backpressure: AtomicBool::new(false),\r\n            endpoint,\r\n            _writer: writer,\r\n        })\r\n    }\r\n\r\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\r\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\r\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\r\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\r\n    /// thread never blocks here, however stuck the harness is.\r\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\r\n    fn enqueue(&self, bytes: InputRecord) -> bool {\r\n        match self.tx.try_send(bytes) {\r\n            Ok(()) => {\r\n                // Heal: input is flowing again — clear a prior backpressure stamp.\r\n                if self.backpressure.swap(false, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(false);\r\n                }\r\n                true\r\n            }\r\n            Err(TrySendError::Full(_)) => {\r\n                // DROP + surface, but stamp only on the rising edge.\r\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(true);\r\n                }\r\n                false\r\n            }\r\n            // The writer thread is gone (session teardown) — nothing to do.\r\n            Err(TrySendError::Disconnected(_)) => false,\r\n        }\r\n    }\r\n\r\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\r\n    /// broker is the single writer). Best-effort: a session served without a\r\n    /// perch (raw broker tests) has nowhere to record.\r\n    fn stamp_backpressure(&self, dropping: bool) {\r\n        if self.endpoint.is_empty() {\r\n            return;\r\n        }\r\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\r\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\r\n    }\r\n}\r\n\r\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\r\n/// records from `rx` and apply each via the BLOCKING\r\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\r\n/// wedged harness parks this thread (and only this thread); a write error is\r\n/// swallowed (the same best-effort the inline callers had). The thread exits\r\n/// when the last `tx` clone drops at session teardown.\r\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\r\n    while let Ok(bytes) = rx.recv() {\r\n        let _ = session.write_input(&bytes);\r\n    }\r\n}\r\n\r\n/// Drain the inject floor's buffered controller input to the PTY **after** an\r\n/// injected sequence, then release the floor. Each step takes the buffered bytes\r\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\r\n/// hold a lock across a PTY write — now never block on it either); input arriving\r\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\r\n/// drains empty and the floor frees.\r\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\r\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\r\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\r\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\r\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\r\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\r\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\r\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\r\n/// pid is alive (the F-e mis-diagnosed \"translation-binary liveness decay\").\r\n/// Recovering the guard (the effect-journal poison-tolerance idiom, bug #16 /\r\n/// REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) keeps the floor usable so ONE panic cannot\r\n/// cascade the whole session's delivery dead.\r\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\r\nfn lock_floor(floor: &Mutex<InjectFloor>) -> std::sync::MutexGuard<'_, InjectFloor> {\r\n    floor.lock().unwrap_or_else(|p| p.into_inner())\r\n}\r\n\r\n/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\r\n/// (B-1, REQ-HAZARD-BROKER-FLOOR-LOCK-POISON). A brain-only self-update keeps the\r\n/// broker process — and every one of its `Mutex`es — ALIVE (REQ-UPD-3), so a single\r\n/// panic while another thread held one of these locks would otherwise POISON it\r\n/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\r\n/// dies, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read\r\n/// deadline elapsed\", brain.rs) while non-locked ops keep working. The `into_inner`\r\n/// idiom (identical to [`lock_floor`] and the effect journal, bug #16) keeps the\r\n/// broker serving. Safe for the SHORT map ops it guards (`sessions` / `pair_holds`\r\n/// inserts+removes stay coherent on recovery — the same argument as the floor). The\r\n/// per-session `OutputLog` RING needs the extra coherence clamp of [`recover_log`].\r\n/// CLASS INVARIANT (KNOWN-HAZARDS): NO bare `.lock().unwrap()` on a broker-resident\r\n/// lock reachable from serve/dispatch — use `recover`/`recover_log` or a documented\r\n/// fail-fast justification.\r\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\r\nfn recover<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {\r\n    m.lock().unwrap_or_else(|p| p.into_inner())\r\n}\r\n\r\n/// [`recover`] for the per-session [`OutputLog`] RING, with a COHERENCE CLAMP on the\r\n/// poison-recovery path (B-1, doyle ruling). A panic mid-[`OutputLog::append`] can\r\n/// leave the ring torn (a bumped `next_seq` past the ring, an over-cap ring from an\r\n/// interrupted prune, a non-monotonic front/back), and replaying those bytes to a\r\n/// subscriber risks garbage. So ONLY on the recovered (poisoned) branch, run\r\n/// [`OutputLog::clamp_or_reset`]: cheap-check the ring invariants and RESET it empty\r\n/// (+ loud log) on any violation. Lost scrollback self-heals on the next PTY output +\r\n/// repaint — strictly better than a permanent wedge (fail-fast reintroduces the very\r\n/// wedge B-1 kills) or a torn serve (blind recover). The healthy `Ok` path is a pure\r\n/// lock — no clamp, no cost on the hot append path.\r\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\r\nfn recover_log(m: &Mutex<OutputLog>) -> std::sync::MutexGuard<'_, OutputLog> {\r\n    match m.lock() {\r\n        Ok(g) => g,\r\n        Err(p) => {\r\n            let mut g = p.into_inner();\r\n            let _ = g.clamp_or_reset(); // bool is for the unit test; the effect is the reset\r\n            g\r\n        }\r\n    }\r\n}\r\n\r\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\r\n    loop {\r\n        let chunk = lock_floor(floor).take_or_release();\r\n        match chunk {\r\n            Some(bytes) => {\r\n                input.enqueue(bytes);\r\n            }\r\n            None => break,\r\n        }\r\n    }\r\n}\r\n\r\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\r\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\r\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\r\n///\r\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\r\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\r\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\r\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\r\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\r\n/// deadline-miss or binary death: flush+release anyway (operator input is never\r\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\r\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\r\n/// (session teardown), reaping the child.\r\n///\r\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\r\n/// sequence and the operator's live controller share one PTY without clobbering\r\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\r\n/// the \"never park forever\" bound applied to injection.\r\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\r\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\r\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\r\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\r\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\r\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\r\n/// (reused; only the translation child is created). Extracted verbatim from\r\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\r\n/// rebuild a session's translation child from the NEW binary without disturbing\r\n/// the PTY / output log / session (brain-parity).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\nfn build_translation(\r\n    argv: &[String],\r\n    endpoint_id: &str,\r\n    input: &Arc<InputWriter>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: &Arc<Mutex<OutputLog>>,\r\n) -> Option<Arc<Translation>> {\r\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\r\n    match TranslationChild::spawn(argv, cmd_tx) {\r\n        Ok(child) => {\r\n            let child = Arc::new(child);\r\n            // First line: identity (content-free of any PTY bytes).\r\n            let _ = child.send(&ToBinary::Init {\r\n                endpoint_id: endpoint_id.to_string(),\r\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\r\n            });\r\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\r\n            let faulted = Arc::new(AtomicBool::new(false));\r\n            let (event_tx, event_rx) = channel::<String>();\r\n            let w_input = Arc::clone(input);\r\n            let w_child = Arc::clone(&child);\r\n            let w_floor = Arc::clone(&floor);\r\n            let w_faulted = Arc::clone(&faulted);\r\n            let w_respawns = Arc::clone(&respawns);\r\n            let w_log = Arc::clone(log);\r\n            let w_endpoint = endpoint_id.to_string();\r\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\r\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\r\n            // recovery missed, anything) must NOT silently kill the thread and orphan\r\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\r\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\r\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\r\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\r\n            let sup_child = Arc::clone(&child);\r\n            let sup_faulted = Arc::clone(&faulted);\r\n            let sup_endpoint = endpoint_id.to_string();\r\n            thread::spawn(move || {\r\n                run_inject_worker_supervised(\r\n                    sup_child,\r\n                    sup_faulted,\r\n                    sup_endpoint,\r\n                    std::panic::AssertUnwindSafe(move || {\r\n                        run_inject_worker(\r\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\r\n                            w_log, w_endpoint,\r\n                        );\r\n                    }),\r\n                );\r\n            });\r\n            Some(Arc::new(Translation {\r\n                floor,\r\n                event_tx,\r\n                child,\r\n                faulted,\r\n            }))\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\r\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\r\n            );\r\n            None\r\n        }\r\n    }\r\n}\r\n\r\n/// The resident translation binary path declared by the manifest at\r\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\r\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\r\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\r\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r","truncated":true,"truncatedBy":"lines","totalLines":4388,"totalBytes":19956,"outputLines":370,"outputBytes":19956,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn controller_writer(\n…\n) {\n…\n    while let Ok(msg) = rx.recv() {\n…\n        if let Some(seq) = seq {\n…\n            // gap — see [`contiguous_advance`].\r\n            contiguous_advance(&delivered_through, seq);\r\n        }\r\n    }\r\n}\r\n\r\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\r\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\r\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\r\n/// was removed as a delivery path; it did not submit on a modern TUI).\r\n///\r\n/// Holds the shared inject floor (the controller-input buffer, also touched by\r\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\r\n/// (events serialize — never two uncommitted sequences interleaved), the shared\r\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\r\n/// while the worker drives an event), and the FAULT flag the worker raises when a\r\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\r\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\r\nstruct Translation {\r\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\r\n    /// and are driven one-at-a-time so two sequences never interleave.\r\n    event_tx: Sender<String>,\r\n    /// The live child — shared so `dispatch_input` fires content-free pings while\r\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\r\n    child: Arc<TranslationChild>,\r\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\r\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\r\n    /// stops pinging.\r\n    faulted: Arc<AtomicBool>,\r\n}\r\n\r\n/// A child the broker hosts under a PTY — the §B-owned resources for one\r\n/// session, plus its gapless-resume output log.\r\nstruct HostedSession {\r\n    /// The PTY + child (PTY master/writer + spawned child process).\r\n    session: Arc<PtySession>,\r\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\r\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\r\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\r\n    /// so a paste burst that fills the harness's input buffer parks only the\r\n    /// writer thread, never the broker dispatch thread.\r\n    input: Arc<InputWriter>,\r\n    /// The output drain pump (kept so teardown can stop it).\r\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\r\n    drain: Drain,\r\n    /// The sequenced, buffered output + current subscriber.\r\n    log: Arc<Mutex<OutputLog>>,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\r\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\r\n    endpoint: String,\r\n    /// The translation-binary attachment (Layer C), or `None` for a session with\r\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\r\n    /// (no raw PTY inject; ADR-0022 amendment).\r\n    translation: Option<Arc<Translation>>,\r\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\r\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\r\n    /// from the exact same command (`build_translation`) without re-reading the\r\n    /// manifest (works for a test session with a direct argv AND a production\r\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\r\n    /// restart to the swapped-in argv.\r\n    translation_argv: Option<Vec<String>>,\r\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\r\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\r\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\r\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\r\n    translation_respawns: Arc<AtomicU32>,\r\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\r\n    /// legacy/test/shell spawn. A live adapter update finds every live session\r\n    /// whose `adapter` matches to cycle its resident children.\r\n    adapter: String,\r\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\r\n    /// apply target for a live update's CRC swap.\r\n    install_dir: Option<std::path::PathBuf>,\r\n}\r\n\r\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\r\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\ntype InputRecord = Vec<u8>;\r\n\r\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\r\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\r\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\r\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\r\n/// few hundred KB of real paste.\r\nconst INPUT_QUEUE_DEPTH: usize = 4096;\r\n\r\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\r\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\r\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\r\n/// is the invariant, not its exact value.\r\nfn input_queue_depth() -> usize {\r\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\r\n        Ok(n) => n\r\n            .parse::<usize>()\r\n            .ok()\r\n            .filter(|d| *d > 0)\r\n            .unwrap_or(INPUT_QUEUE_DEPTH),\r\n        Err(_) => INPUT_QUEUE_DEPTH,\r\n    }\r\n}\r\n\r\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\r\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\r\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\r\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\r\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\r\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\r\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\r\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\r\nstruct InputWriter {\r\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\r\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\r\n    tx: SyncSender<InputRecord>,\r\n    /// True while the FIFO is saturated and input is being dropped; cleared on\r\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\r\n    /// `input_backpressure` so the operator sees the drop.\r\n    backpressure: AtomicBool,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\r\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\r\n    endpoint: String,\r\n    /// The writer thread handle (held for ownership; the thread drains until the\r\n    /// last `tx` clone drops at session teardown, then exits).\r\n    _writer: JoinHandle<()>,\r\n}\r\n\r\nimpl InputWriter {\r\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\r\n    /// whose PTY write handle this thread now exclusively owns.\r\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\r\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\r\n        let writer = thread::spawn(move || input_writer(session, rx));\r\n        Arc::new(InputWriter {\r\n            tx,\r\n            backpressure: AtomicBool::new(false),\r\n            endpoint,\r\n            _writer: writer,\r\n        })\r\n    }\r\n\r\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\r\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\r\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\r\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\r\n    /// thread never blocks here, however stuck the harness is.\r\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\r\n    fn enqueue(&self, bytes: InputRecord) -> bool {\r\n        match self.tx.try_send(bytes) {\r\n            Ok(()) => {\r\n                // Heal: input is flowing again — clear a prior backpressure stamp.\r\n                if self.backpressure.swap(false, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(false);\r\n                }\r\n                true\r\n            }\r\n            Err(TrySendError::Full(_)) => {\r\n                // DROP + surface, but stamp only on the rising edge.\r\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(true);\r\n                }\r\n                false\r\n            }\r\n            // The writer thread is gone (session teardown) — nothing to do.\r\n            Err(TrySendError::Disconnected(_)) => false,\r\n        }\r\n    }\r\n\r\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\r\n    /// broker is the single writer). Best-effort: a session served without a\r\n    /// perch (raw broker tests) has nowhere to record.\r\n    fn stamp_backpressure(&self, dropping: bool) {\r\n        if self.endpoint.is_empty() {\r\n            return;\r\n        }\r\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\r\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\r\n    }\r\n}\r\n\r\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\r\n/// records from `rx` and apply each via the BLOCKING\r\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\r\n/// wedged harness parks this thread (and only this thread); a write error is\r\n/// swallowed (the same best-effort the inline callers had). The thread exits\r\n/// when the last `tx` clone drops at session teardown.\r\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\r\n    while let Ok(bytes) = rx.recv() {\r\n        let _ = session.write_input(&bytes);\r\n    }\r\n}\r\n\r\n/// Drain the inject floor's buffered controller input to the PTY **after** an\r\n/// injected sequence, then release the floor. Each step takes the buffered bytes\r\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\r\n/// hold a lock across a PTY write — now never block on it either); input arriving\r\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\r\n/// drains empty and the floor frees.\r\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\r\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\r\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\r\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\r\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\r\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\r\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\r\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\r\n/// pid is alive (the F-e mis-diagnosed \"translation-binary liveness decay\").\r\n/// Recovering the guard (the effect-journal poison-tolerance idiom, bug #16 /\r\n/// REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) keeps the floor usable so ONE panic cannot\r\n/// cascade the whole session's delivery dead.\r\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\r\nfn lock_floor(floor: &Mutex<InjectFloor>) -> std::sync::MutexGuard<'_, InjectFloor> {\r\n    floor.lock().unwrap_or_else(|p| p.into_inner())\r\n}\r\n\r\n/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\r\n/// (B-1, REQ-HAZARD-BROKER-FLOOR-LOCK-POISON). A brain-only self-update keeps the\r\n/// broker process — and every one of its `Mutex`es — ALIVE (REQ-UPD-3), so a single\r\n/// panic while another thread held one of these locks would otherwise POISON it\r\n/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\r\n/// dies, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read\r\n/// deadline elapsed\", brain.rs) while non-locked ops keep working. The `into_inner`\r\n/// idiom (identical to [`lock_floor`] and the effect journal, bug #16) keeps the\r\n/// broker serving. Safe for the SHORT map ops it guards (`sessions` / `pair_holds`\r\n/// inserts+removes stay coherent on recovery — the same argument as the floor). The\r\n/// per-session `OutputLog` RING needs the extra coherence clamp of [`recover_log`].\r\n/// CLASS INVARIANT (KNOWN-HAZARDS): NO bare `.lock().unwrap()` on a broker-resident\r\n/// lock reachable from serve/dispatch — use `recover`/`recover_log` or a documented\r\n/// fail-fast justification.\r\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\r\nfn recover<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {\r\n    m.lock().unwrap_or_else(|p| p.into_inner())\r\n}\r\n\r\n/// [`recover`] for the per-session [`OutputLog`] RING, with a COHERENCE CLAMP on the\r\n/// poison-recovery path (B-1, doyle ruling). A panic mid-[`OutputLog::append`] can\r\n/// leave the ring torn (a bumped `next_seq` past the ring, an over-cap ring from an\r\n/// interrupted prune, a non-monotonic front/back), and replaying those bytes to a\r\n/// subscriber risks garbage. So ONLY on the recovered (poisoned) branch, run\r\n/// [`OutputLog::clamp_or_reset`]: cheap-check the ring invariants and RESET it empty\r\n/// (+ loud log) on any violation. Lost scrollback self-heals on the next PTY output +\r\n/// repaint — strictly better than a permanent wedge (fail-fast reintroduces the very\r\n/// wedge B-1 kills) or a torn serve (blind recover). The healthy `Ok` path is a pure\r\n/// lock — no clamp, no cost on the hot append path.\r\n// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\r\nfn recover_log(m: &Mutex<OutputLog>) -> std::sync::MutexGuard<'_, OutputLog> {\r\n    match m.lock() {\r\n        Ok(g) => g,\r\n        Err(p) => {\r\n            let mut g = p.into_inner();\r\n            let _ = g.clamp_or_reset(); // bool is for the unit test; the effect is the reset\r\n            g\r\n        }\r\n    }\r\n}\r\n\r\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\r\n    loop {\r\n        let chunk = lock_floor(floor).take_or_release();\r\n        match chunk {\r\n            Some(bytes) => {\r\n                input.enqueue(bytes);\r\n            }\r\n            None => break,\r\n        }\r\n    }\r\n}\r\n\r\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\r\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\r\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\r\n///\r\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\r\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\r\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\r\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\r\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\r\n/// deadline-miss or binary death: flush+release anyway (operator input is never\r\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\r\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\r\n/// (session teardown), reaping the child.\r\n///\r\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\r\n/// sequence and the operator's live controller share one PTY without clobbering\r\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\r\n/// the \"never park forever\" bound applied to injection.\r\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\r\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\r\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\r\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\r\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\r\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\r\n/// (reused; only the translation child is created). Extracted verbatim from\r\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\r\n/// rebuild a session's translation child from the NEW binary without disturbing\r\n/// the PTY / output log / session (brain-parity).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\nfn build_translation(\r\n    argv: &[String],\r\n    endpoint_id: &str,\r\n    input: &Arc<InputWriter>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: &Arc<Mutex<OutputLog>>,\r\n) -> Option<Arc<Translation>> {\r\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\r\n    match TranslationChild::spawn(argv, cmd_tx) {\r\n        Ok(child) => {\r\n            let child = Arc::new(child);\r\n            // First line: identity (content-free of any PTY bytes).\r\n            let _ = child.send(&ToBinary::Init {\r\n                endpoint_id: endpoint_id.to_string(),\r\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\r\n            });\r\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\r\n            let faulted = Arc::new(AtomicBool::new(false));\r\n            let (event_tx, event_rx) = channel::<String>();\r\n            let w_input = Arc::clone(input);\r\n            let w_child = Arc::clone(&child);\r\n            let w_floor = Arc::clone(&floor);\r\n            let w_faulted = Arc::clone(&faulted);\r\n            let w_respawns = Arc::clone(&respawns);\r\n            let w_log = Arc::clone(log);\r\n            let w_endpoint = endpoint_id.to_string();\r\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\r\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\r\n            // recovery missed, anything) must NOT silently kill the thread and orphan\r\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\r\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\r\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\r\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\r\n            let sup_child = Arc::clone(&child);\r\n            let sup_faulted = Arc::clone(&faulted);\r\n            let sup_endpoint = endpoint_id.to_string();\r\n            thread::spawn(move || {\r\n                run_inject_worker_supervised(\r\n                    sup_child,\r\n                    sup_faulted,\r\n                    sup_endpoint,\r\n                    std::panic::AssertUnwindSafe(move || {\r\n                        run_inject_worker(\r\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\r\n                            w_log, w_endpoint,\r\n                        );\r\n                    }),\r\n                );\r\n            });\r\n            Some(Arc::new(Translation {\r\n                floor,\r\n                event_tx,\r\n                child,\r\n                faulted,\r\n            }))\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\r\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\r\n            );\r\n            None\r\n        }\r\n    }\r\n}\r\n\r\n/// The resident translation binary path declared by the manifest at\r\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\r\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\r\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\r\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r","startLine":1439,"lineNumbers":[1439,null,1448,null,1491,null,1514,null,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,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,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,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,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,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":5906,"totalBytes":19956,"outputLines":370,"outputBytes":19956,"shownRange":{"start":1519,"end":1888},"nextOffset":1889}}},"isError":false,"timestamp":1783730526052}}
{"type":"message","id":"a7b9afc7","parentId":"1a9d52b5","timestamp":"2026-07-11T00:42:23.920Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing commit-tag relationships**\n\n<!-- -->\n\n**Mapping version tag changes**\n\n<!-- -->\n\n**Searching release commit logs**\n\n<!-- -->"},{"type":"toolCall","id":"call_iYCLxTwfBbzjT6h8CiCE4eQx|fc_08496348794f7e09016a5191706204819883aff67deda996a5","name":"grep","arguments":{"i":"Find release commit subjects","pattern":"release: v0\\.30\\.[2-6]","path":"spt-core/.git/logs","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_z4dysK5aa7F10gKejh4zl52A|fc_08496348794f7e09016a5191706218819890307f9a22f07901","name":"read","arguments":{"i":"Read brain process loop","path":"spt-core/crates/spt-daemon/src/brainproc.rs","selector":"175-235,235-285"}},{"type":"toolCall","id":"call_i3UHMpWTXizRyQoRv4TnOyqI|fc_08496348794f7e09016a51917062288198b5d079d71e5ac148","name":"read","arguments":{"i":"Read old broker writer exact","path":"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs","selector":"60-90,1035-1120,1650-1770,4210-4235"}},{"type":"toolCall","id":"call_vJmUpuWHHv27sXndRm4uIg5e|fc_08496348794f7e09016a519170624c8198b771f08b046bd7ee","name":"grep","arguments":{"i":"Find inject commit bound","pattern":"INJECT_COMMIT_DEADLINE|inject_commit_deadline","path":"spt-core/crates/spt-daemon/src/broker.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_BFAnJQrqEXJLB3ysxSSdz9PM|fc_08496348794f7e09016a51917062548198b4676902d41e88fc","name":"read","arguments":{"i":"Read conn bound implementation","path":"spt-core/crates/spt-daemon/src/conn.rs","selector":"300-510"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":15124,"output":780,"cacheRead":137728,"cacheWrite":0,"totalTokens":153632,"reasoningTokens":488,"cost":{"input":0.07562,"output":0.0234,"cacheRead":0.068864,"cacheWrite":0,"total":0.167884}},"stopReason":"toolUse","timestamp":1783730526100,"responseId":"resp_08496348794f7e09016a51915ee1888198a06b11c5cf5cb9fb","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a5191624a488198af1f932d6be120d5","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFwIomPRO04s0JZ4SbGBD0MKYX2HCP4u-qzHVLApo08RADNTZpSitkwPO0lJ6qxAb57dx1AHEaJWG9i6zX_Wf4YeKbC3_EW6XO0Vc6k0-631cf3knW1N7QYtdoN50K3bHcYjVLdokjoa8FA7mKo7RlC5G-8XD_zGLkqKjF0Jj_E75PSEkPpNBmZDuEgd2tgvYe7BmFCm6wOTy7eb3v_P-njhtkx0TRFuiH6l3prZSAPawZiea8SVgxWdUy3KvLXDQ-ehq4LOj5lgmBga5S3WXLG7fsHg2ewYQKqcpZAf8IgI0DfC6xjKGKITdcKuCk1uZjQvcnxACrw8QOWhYUWcO_lKbcK5UhRolWQ_skW-6pJUF9hqrieV8lpcMC9Q8vBnDg0kZV2geBiqgV54ZEf0MubFE4srNaaWS_Be3gHiPjvRbms02B04oQL4gTMDl6RvQBGcqD34USyEY0K2ZaTrfzk6WTsTc8XUI9QT58mA80WgeYpvR3YXeLgj4BKn4sBarrdsC8RRPtRSklCSUX2joRpniwdubEWQVLV2jFzfWJsBsYIr_TE-Aeq1TQAeAqJbMajuJcC5pymfe4ZkB4PBuBpjyqjAxIrPVOJPsxDoUFIsqAL1-TgrFIIP61r0XZrs1kGyghMulV0ai7-rTwZDXCKJOpB7f310RLgXIIbrV7CaBdj-cfpsW__MdkzPLZATpMNP-y9prVqsAiz84YGp27PeMG1VpO4X25PZQ4YvviDUNhjmC0tmByfm4-jGhyyeFPw3almcl0zaxKbtYeRDjSxgBHjC8w24KEe2UtzQ4iwiE_jg6-7rLbU2WQYkjyuwtcL39JYEju3waben37VsCs6v0i46uZQQhIu1TJ9U-wUsBA5HapXwL2AiHAU3_RTfuYkRfj-LBTsh6Z7PI9ZLvUudpROtkSttd8psUcMZzOH6hpgTpUK16QufNwXcHPLnNCxoTFHyRccxSLJtZHfPFKeF3fTBqU1fFCRJJrxxQDQEsxcRiO6vwF_23_RzClqvstg3Os-oZ-UIeZl7jmmKbuGimT14KK5Bo_s7kqfbdYVf7RMCwVXiWew3zwEJP5IMK9h03mFJXcUtuPCUoEsloySiPhwFnq6dpSo8u3Nir9L1wTuu1WYKCqWeOPI8HPZFwZ8xjDnfPz-iV6zIKoM18_fjDzHSBSwoPbS8Oo_gkUVdBnTnO90sMkCbGZ1HCqV33ifMCcA9ZEwYruN_W_ZCFtl28lwfUPN4hxPqASqxsncxk6-djt4S59RtaDG96KZv_9eQ39ImcwJZjYbkz2vZ4fsMg-yXh1VZZOg9uNGmnugjYQ1CxFbZtyWq7Vz3laKiBGDTt0Zy6T5UEcsceCURrPmp5cKHq0QfnYeyfbA9qgd9vX-w-9xMChojQgKzgTrBSCYxs_9iqNb5mN7Wwc7dCoJeTw9-5OWOAFmdjbNlSTGrZnax8f9quSoHXuK-aMtgARqpp2x5DZGZXCxZ9F1X2SHlE9WOq_MGSmik0MoTQHiWx4cyVSYTPRc4MQFLnvhuZutAj8sEMIrYe9E3DXMTLCQGMsCjQQ8r8uXFDrkD_VjPaxW3_XNJQWx6X2NX47DFeZ089iwV6SP5vU9IBUbC0M07OvyN1KJZR3X2HvJ6OBqb99qQsZlktUB57Eu22HmvoxJrV0xBQXEmJUIzdx_P2CvmhsFAovbNfkQHQF23MnoozVZMjl0903cIAWlTBlI9RRBjbW6IBjv_YDPtu6N96jsJA5uzAw7gFbpCyMHmg7AnWJNYW9KwJUIdclLe_PdCHes8pmvIhOHkKeCezdWPt3RkFAOhqaPFllYxA9EFA7LKjNRaNBMaJKBzoeIRsldxiL2QIARJryt2LmN4yu8ZUoCtNkzvRGiE-6q58dJ1CdUonaTYadjNhMcSxy0YdNcNTBvvWEYS50e-z1PaypDTr6rL-3yhEPnpUobIsGes0iU0WO1rRZwTIZQPh2wW4oe7UFixuwjhB0vdVzGRp6FhaWm3bpUmTW2XoX7kMaMX6ZOWYXGrdIVR6KuPrQiASw641EaFvqBILxqwXHnIS2IYzb5OHM9U_qTR7cBEV_yOq6xVn5iEes0H5sV4kXdTaSMYjiWx1EJyFpuwwT6bI33mCvwJ7JSbY7oSpogM-HhRfHi8Z3UgYYOs08gwT_WTEy0R464j6h8f8Ryyp2aunqi-0-n2oJXF5SfvO3u5l0EgEVRuj7uJ0F7yAc0Hl82qJn2oTYk1l-eoP-m8nQw_klL5oNLAzcLu91qc2bxz5COZqPry_aXv1fxJX0GKzuABjptF_w8AGfaluvsijhKZv3SHHUhzKX-SM5j2uJ_vrqHCh2MKh6XPZqEHxmtIeAIeRhxZ7vn9Z6x8r4d9ihYuqgTMjP7ziWTOmI9LQt3yixGo3p4JL6xuNdqCcItxgA_SkoWA0Q_9kE36c1QRzFmHHGyK_mQcBUF0rJjYPNK09IHgOtQyLE2aQy_ZF-Nzs6a2jspWy6lQG5eQsbAG__vDkuSskO_LZVwiMsI0uqnfd6nYYo-QYL3lR5PJBozh-q2YWdxrF1ogOqCUnXdKQswFwE9cK4BZnqQ825TXHOm73UuBJnsAEeca_UWakAAtoSSZH71aKVzXTFiXZ9a40WvdiPgu4nx49vUp2wcodgFQQfcwqmRToeEBlWHuayB8GW380hlJPf3TKbPT7uVYdxzdFD76rVbxqDMQo1OeBOl4J9rjfZV5WsHtbzubZN7hAoScl29ZCjLj50cjnNX3dlAj5-gAKY-EgC_3SUha04FIx8oOySTXZfiHYiCIzfxdQswKK0IFVmZCi5DIMigUnu6lyi8FFeW4WDnHIXROUHM_WkHLR1_YMw2zF5m5Wh1d7pqzZfFcKV6NETwlUDT6ya231mBtkA3nuS9GaxJiUhFKxIs8MCRQE7cD5GZ4er5NbGop_7XGH_3Aatkxk4FwanBi8vGJKeeNKqdHe5zNtBWPmh_t1WPD1-SxNyDv4Vb_LJgAGJ68VZd5zeEbcIA6WgKmGbKzjamKaKyc1alKVwlp7xSDXumArUgTHfEpNOSjxhEFOQ8lhp5b1plvKTtam8BICidnIo7667eMFygAN9tMQTgKj93_VDlfZPCM7SfM9pFFj-hGIEB2vf4xdAWW_Qd-DwI2pFKHCBCnI4FLQ1N0YnXKOSPvpfOYAfS1oTwBn3ZRaa7qgQpG_s2Q9NCMahX1N7rlTmhjXoJ-qY_U6wOmgP08HsEdv2ySj3M8WFctE7ZbD2A8AnpmknYkaEwTsuALnCWmj4-QuEXhbmwWt2Jbsnh0zmGH829_tUCZR0S5cTMxjAV_Iu6v3XGh6f5I-AfUwwFg5J5v1krchpugYVXmkZFGBZBLS772kNrNpVzbL_fZaVLLdYInkJjR4fMFumEfuC8ZXLd_xX6vXgBHmkWJqlTUOJ2k4XxzmVwhtSpjXEPopLvZbi5pSZp3SVtF3ZtCHRpmHt7mneKlldOtfxmbhjYXjT3JiWF74QtOcGm2fuzYWLUtaOMBhYy6a-oPGE-LfDwQZjYwg==","summary":[{"type":"summary_text","text":"**Assessing commit-tag relationships**\n\n<!-- -->"},{"type":"summary_text","text":"**Mapping version tag changes**\n\n<!-- -->"},{"type":"summary_text","text":"**Searching release commit logs**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a5191706204819883aff67deda996a5","type":"function_call","status":"completed","arguments":"{\"i\":\"Find release commit subjects\",\"pattern\":\"release: v0\\\\.30\\\\.[2-6]\",\"path\":\"spt-core/.git/logs\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_iYCLxTwfBbzjT6h8CiCE4eQx","name":"grep"},{"id":"fc_08496348794f7e09016a5191706218819890307f9a22f07901","type":"function_call","status":"completed","arguments":"{\"i\":\"Read brain process loop\",\"path\":\"spt-core/crates/spt-daemon/src/brainproc.rs\",\"selector\":\"175-235,235-285\"}","call_id":"call_z4dysK5aa7F10gKejh4zl52A","name":"read"},{"id":"fc_08496348794f7e09016a51917062288198b5d079d71e5ac148","type":"function_call","status":"completed","arguments":"{\"i\":\"Read old broker writer exact\",\"path\":\"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs\",\"selector\":\"60-90,1035-1120,1650-1770,4210-4235\"}","call_id":"call_i3UHMpWTXizRyQoRv4TnOyqI","name":"read"},{"id":"fc_08496348794f7e09016a519170624c8198b771f08b046bd7ee","type":"function_call","status":"completed","arguments":"{\"i\":\"Find inject commit bound\",\"pattern\":\"INJECT_COMMIT_DEADLINE|inject_commit_deadline\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_vJmUpuWHHv27sXndRm4uIg5e","name":"grep"},{"id":"fc_08496348794f7e09016a51917062548198b4676902d41e88fc","type":"function_call","status":"completed","arguments":"{\"i\":\"Read conn bound implementation\",\"path\":\"spt-core/crates/spt-daemon/src/conn.rs\",\"selector\":\"300-510\"}","call_id":"call_BFAnJQrqEXJLB3ysxSSdz9PM","name":"read"}]},"duration":17819.193599999882,"ttft":3557.5484000002034,"contextSnapshot":{"promptTokens":152852,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_iYCLxTwfBbzjT6h8CiCE4eQx|fc_08496348794f7e09016a5191706204819883aff67deda996a5","toolName":"grep","startedAt":"2026-07-11T00:42:23.920Z","args":{"path":"spt-core/.git/logs"},"intent":"Find release commit subjects"},"id":"58fc7a37","parentId":"a7b9afc7","timestamp":"2026-07-11T00:42:23.920Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_z4dysK5aa7F10gKejh4zl52A|fc_08496348794f7e09016a5191706218819890307f9a22f07901","toolName":"read","startedAt":"2026-07-11T00:42:23.920Z","args":{"path":"spt-core/crates/spt-daemon/src/brainproc.rs"},"intent":"Read brain process loop"},"id":"550fd9b5","parentId":"58fc7a37","timestamp":"2026-07-11T00:42:23.920Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_i3UHMpWTXizRyQoRv4TnOyqI|fc_08496348794f7e09016a51917062288198b5d079d71e5ac148","toolName":"read","startedAt":"2026-07-11T00:42:23.920Z","args":{"path":"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs"},"intent":"Read old broker writer exact"},"id":"755e9afc","parentId":"550fd9b5","timestamp":"2026-07-11T00:42:23.920Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_vJmUpuWHHv27sXndRm4uIg5e|fc_08496348794f7e09016a519170624c8198b771f08b046bd7ee","toolName":"grep","startedAt":"2026-07-11T00:42:23.920Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs"},"intent":"Find inject commit bound"},"id":"1cfa21fe","parentId":"755e9afc","timestamp":"2026-07-11T00:42:23.920Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BFAnJQrqEXJLB3ysxSSdz9PM|fc_08496348794f7e09016a51917062548198b4676902d41e88fc","toolName":"read","startedAt":"2026-07-11T00:42:23.921Z","args":{"path":"spt-core/crates/spt-daemon/src/conn.rs"},"intent":"Read conn bound implementation"},"id":"472c306c","parentId":"1cfa21fe","timestamp":"2026-07-11T00:42:23.921Z"}
{"type":"message","id":"4bf0080f","parentId":"472c306c","timestamp":"2026-07-11T00:42:23.922Z","message":{"role":"toolResult","toolCallId":"call_vJmUpuWHHv27sXndRm4uIg5e|fc_08496348794f7e09016a519170624c8198b771f08b046bd7ee","toolName":"grep","content":[{"type":"text","text":" 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 179|/// ONLY — never relaxes an assertion.\n 180|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 181|fn brain_write_deadline() -> Duration {\n...\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*290|/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n 291|/// so the structural outcome (floor releases → buffered input flushes → binary\n 292|/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n 293|/// the deadline's PRESENCE is the invariant, not its exact length.\n*294|fn inject_commit_deadline() -> Duration {\n*295|    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 296|        Ok(ms) => ms\n 297|            .parse::<u64>()\n 298|            .map(Duration::from_millis)\n*299|            .unwrap_or(INJECT_COMMIT_DEADLINE),\n*300|        Err(_) => INJECT_COMMIT_DEADLINE,\n 301|    }\n 302|}\n 303|\n...\n 1534|/// while the worker drives an event), and the FAULT flag the worker raises when a\n*1535|/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\n 1536|/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\n 1537|struct Translation {\n 1538|    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\n...\n 1794|/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n*1795|/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n 1796|/// buffered controller bytes AFTER the injected sequence and release the floor. On\n 1797|/// deadline-miss or binary death: flush+release anyway (operator input is never\n 1798|/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\n...\n 2076|    }\n*2077|    let deadline = Instant::now() + inject_commit_deadline();\n 2078|    let mut committed = false;\n 2079|    let mut disconnected = false;\n 2080|    let mut sent_text: Vec<u8> = Vec::new();\n...\n 2334|                &child,\n*2335|                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\n 2336|            );\n 2337|            return;\n 2338|        }"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":11,"fileCount":1,"files":["spt-core/crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":11}],"truncated":false,"displayContent":"  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  179│/// ONLY — never relaxes an assertion.\n  180│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  181│fn brain_write_deadline() -> Duration {\n     │...\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 *290│/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n  291│/// so the structural outcome (floor releases → buffered input flushes → binary\n  292│/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n  293│/// the deadline's PRESENCE is the invariant, not its exact length.\n *294│fn inject_commit_deadline() -> Duration {\n *295│    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n  296│        Ok(ms) => ms\n  297│            .parse::<u64>()\n  298│            .map(Duration::from_millis)\n *299│            .unwrap_or(INJECT_COMMIT_DEADLINE),\n *300│        Err(_) => INJECT_COMMIT_DEADLINE,\n  301│    }\n  302│}\n  303│\n     │...\n 1534│/// while the worker drives an event), and the FAULT flag the worker raises when a\n*1535│/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\n 1536│/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\n 1537│struct Translation {\n 1538│    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\n     │...\n 1794│/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n*1795│/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n 1796│/// buffered controller bytes AFTER the injected sequence and release the floor. On\n 1797│/// deadline-miss or binary death: flush+release anyway (operator input is never\n 1798│/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\n     │...\n 2076│    }\n*2077│    let deadline = Instant::now() + inject_commit_deadline();\n 2078│    let mut committed = false;\n 2079│    let mut disconnected = false;\n 2080│    let mut sent_text: Vec<u8> = Vec::new();\n     │...\n 2334│                &child,\n*2335│                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\n 2336│            );\n 2337│            return;\n 2338│        }"},"isError":false,"timestamp":1783730543922}}
{"type":"message","id":"fefdeaa3","parentId":"4bf0080f","timestamp":"2026-07-11T00:42:23.969Z","message":{"role":"toolResult","toolCallId":"call_i3UHMpWTXizRyQoRv4TnOyqI|fc_08496348794f7e09016a51917062288198b5d079d71e5ac148","toolName":"read","content":[{"type":"text","text":"use crate::msg::{\n…\n    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_SUBSCRIBE,\n    KIND_NET_SENT,\n    KIND_ADAPTER_APPLY, KIND_APPLIED,\n    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\n    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n    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    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n};\nuse crate::nethost::{NetHost, NET_EFFECT_SESSION};\nuse crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\nuse crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n\n/// The shared, serialized send side of one brain connection. Output frames,\n/// replay frames, command acks, and exit/error events all write through this.\n/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\n/// handle under the same lock discipline.\npub(crate) type SharedSend = Arc<Mutex<SendHalf>>;\n\n/// Observation-only RCA gate for PTY/control write causality. The value is\n/// sampled once per broker process so the hot paths pay only a cheap static read.\nstatic RCA_PTY_TRACE_ENABLED: LazyLock<bool> = LazyLock::new(|| {\n    matches!(\n        std::env::var(\"SPT_RCA_PTY_TRACE\"),\n        Ok(value) if value == \"1\"\n    )\n});\n\n/// Process-local monotonic origin and correlation-id sources for RCA markers.\nstatic RCA_PTY_TRACE_START: LazyLock<Instant> = LazyLock::new(Instant::now);\nstatic NEXT_RCA_CONN_ID: AtomicU64 = AtomicU64::new(1);\n…\n    ///\n    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller\n    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written\n    /// inline under the log lock. The writer thread advances the authoritative\n    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,\n    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]\n    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`\n    /// additionally makes a stale deadline-evict a no-op AND stops a superseded\n    /// writer mid-INITIAL-BATCH (P1c — a writer still flushing its owned `initial`\n    /// batch ignores the dropped `tx`, so the epoch gate is what guarantees a\n    /// single live writer per connection and a monotonic on-wire seq stream,\n    /// [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]). Stamps `driven_by` to the new\n    /// controller's identity.\n    fn become_controller(\n        &mut self,\n        sub: SharedSend,\n        by: Option<String>,\n        from_seq: u64,\n        conn_id: u64,\n        install: ControllerInstall,\n    ) {\n        // Drop the prior controller sink first (its writer's live loop ends when\n        // tx drops), and bump the generation so (a) an in-flight deadline-evict\n        // for the old controller can't unseat this one and (b) a prior writer\n        // still flushing its initial batch returns before its next frame.\n        self.controller = None;\n        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;\n        // #6 (ADR-0031): a COLD attach's initial batch is a single synthesized clean\n        // repaint of the CURRENT SCREEN (at pseudo-seq `next_seq - 1`) instead of the\n        // raw ring — replaying an alt-screen TUI's raw ring into a fresh terminal is\n        // exactly the corruption #6/#7/#8 report. A resume-from-floor (`from_seq > 0`)\n        // keeps the raw-ring exactly-once re-fetch untouched (B2). Either way the\n        // batch is owned by the writer thread (never pushed through the live channel —\n        // it must not block the drain); live frames stream raw + in-order afterwards.\n        let initial: Vec<(u64, Vec<u8>)> = match self.repaint_initial(from_seq) {\n            Some(repaint) => vec![repaint],\n            None => self\n                .ring\n                .iter()\n                .filter(|(s, _)| *s >= from_seq)\n                .cloned()\n                .collect(),\n        };\n        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n        let writer_send = Arc::clone(&sub);\n        let dt = Arc::clone(&self.delivered_through);\n        let epoch_gate = Arc::clone(&self.controller_epoch);\n        let sid = self.session_id;\n        // W2 decouple: the writer publishes its in-flight-write window here so a\n        // stall-evict can distinguish a wedged writer from an idle one.\n        let write_blocked_since = Arc::new(Mutex::new(None));\n        let writer_blocked = Arc::clone(&write_blocked_since);\n        let writer = thread::spawn(move || {\n            controller_writer(\n                conn_id,\n                sid,\n                writer_send,\n                initial,\n                rx,\n                dt,\n                epoch,\n                epoch_gate,\n                writer_blocked,\n            )\n        });\n        self.controller = Some(ControllerSink {\n            conn_id,\n            tx,\n            send: sub,\n            by,\n            last_ok: Instant::now(),\n            write_blocked_since,\n            _writer: writer,\n        });\n        if rca_pty_trace_enabled() {\n            eprintln!(\n                \"RCA_CONTROLLER_INSTALL: conn_id={conn_id} sid={} epoch={epoch} \\\n                 source={} intent={} by={} elapsed_us={}\",\n                self.session_id,\n                install.source(),\n                install.intent(),\n                if self.controller_by().is_some() {\n                    \"remote\"\n                } else {\n                    \"local\"\n                },\n…\n            );\n        }\n…\n    }\n…\nfn controller_writer(\n…\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate is read UNDER `send.lock()`, atomically with the\n        // write: both writers serialize on this one socket mutex, and\n        // `become_controller` bumps the epoch BEFORE spawning the new writer, so\n        // whichever superseded writer next holds the lock observes the bump and\n        // bails before committing — no check-then-block-then-write window. (A\n        // frame the stale writer committed BEFORE the bump is irreducible at the\n        // broker; fix #1 removes the contradictory double-take that produced it.)\n        let mut trace = CtrlWriteTrace::new(conn_id, sid, epoch, Some(*seq), &frame);\n        mark_blocked(true);\n        if let Some(trace) = trace.as_mut() {\n            trace.begin_wait();\n        }\n        let ok = match send.lock() {\n            Ok(mut s) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.locked();\n                }\n                if controller_epoch.load(Ordering::Acquire) != epoch {\n                    mark_blocked(false);\n                    if let Some(trace) = trace.as_ref() {\n                        trace.done(\"superseded\");\n                    }\n                    return;\n                }\n                if let Some(trace) = trace.as_mut() {\n                    trace.begin_write();\n                }\n                let ok = write_frame(&mut *s, &frame).is_ok();\n                if let Some(trace) = trace.as_mut() {\n                    trace.finish_write();\n                }\n                ok\n            }\n            Err(_) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.lock_failed();\n                }\n                false\n            }\n        };\n        mark_blocked(false);\n        if let Some(trace) = trace.as_ref() {\n            trace.done(if ok { \"ok\" } else { \"error\" });\n        }\n        if !ok {\n            return;\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        let mut trace = CtrlWriteTrace::new(conn_id, sid, epoch, seq, frame);\n        mark_blocked(true);\n        if let Some(trace) = trace.as_mut() {\n            trace.begin_wait();\n        }\n        let ok = match send.lock() {\n            Ok(mut s) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.locked();\n                    trace.begin_write();\n                }\n                let ok = write_frame(&mut *s, frame).is_ok();\n                if let Some(trace) = trace.as_mut() {\n                    trace.finish_write();\n                }\n                ok\n            }\n            Err(_) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.lock_failed();\n                }\n                false\n            }\n        };\n        mark_blocked(false);\n        if let Some(trace) = trace.as_ref() {\n            trace.done(if ok { \"ok\" } else { \"error\" });\n        }\n        if !ok {\n            return;\n        }\n        if let Some(seq) = seq {\n            // (a) CONTIGUOUS cursor (b4 / B2 gapless-resume — doyle's gate): with the\n            // drop-on-full handoff in `append`, the controller's live channel can SKIP\n            // a seq (dropped while Full). The frame still went to the socket (the live\n            // operator sees it), but the durable resume cursor must NOT jump past the\n            // gap — see [`contiguous_advance`].\n            contiguous_advance(&delivered_through, seq);\n        }\n    }\n}\n…\n        let host = self\n…\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        match host.pair_meet(&req.subnet, req.verbose) {\n            Ok(addr) => {\n                // The canonical lowercase-hex name (== node_id_hex everywhere),\n                // derived from the met address's proven id.\n                let member_hex = spt_proto::identity::PublicKey::from_bytes(addr.id.as_bytes())\n                    .map(|p| p.to_hex())\n                    .unwrap_or_default();\n                let session_id = self.next_id.fetch_add(1, Ordering::Relaxed);\n                {\n                    let mut holds = recover(&self.pair_holds);\n                    let now = Instant::now();\n                    holds.retain(|_, h| h.deadline > now); // sweep abandoned holds\n                    holds.insert(\n                        session_id,\n                        PairHold {\n                            subnet: req.subnet.clone(),\n                            addr,\n                            deadline: now + PAIR_CODE_WAIT,\n                        },\n                    );\n                }\n                let frame = Envelope::new(\n                    KIND_MET_MEMBER,\n                    serde_json::to_value(MetMember {\n                        session_id,\n…\n                    })\n                    .expect(\"MetMember serializes\"),\n                );\n…\n            }\n…\n        }"}],"details":{"displayContent":{"text":"use crate::msg::{\n…\n    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_SUBSCRIBE,\n    KIND_NET_SENT,\n    KIND_ADAPTER_APPLY, KIND_APPLIED,\n    KIND_BROKER_IMAGE, KIND_BROKER_IMAGE_REPLY, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY,\n    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n    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    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n};\nuse crate::nethost::{NetHost, NET_EFFECT_SESSION};\nuse crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\nuse crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n\n/// The shared, serialized send side of one brain connection. Output frames,\n/// replay frames, command acks, and exit/error events all write through this.\n/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\n/// handle under the same lock discipline.\npub(crate) type SharedSend = Arc<Mutex<SendHalf>>;\n\n/// Observation-only RCA gate for PTY/control write causality. The value is\n/// sampled once per broker process so the hot paths pay only a cheap static read.\nstatic RCA_PTY_TRACE_ENABLED: LazyLock<bool> = LazyLock::new(|| {\n    matches!(\n        std::env::var(\"SPT_RCA_PTY_TRACE\"),\n        Ok(value) if value == \"1\"\n    )\n});\n\n/// Process-local monotonic origin and correlation-id sources for RCA markers.\nstatic RCA_PTY_TRACE_START: LazyLock<Instant> = LazyLock::new(Instant::now);\nstatic NEXT_RCA_CONN_ID: AtomicU64 = AtomicU64::new(1);\n…\n    ///\n    /// W1: the ring replay is now the INITIAL BATCH of a dedicated controller\n    /// writer thread (symmetric with [`add_viewer`]) — it is no longer written\n    /// inline under the log lock. The writer thread advances the authoritative\n    /// `delivered_through` cursor as it writes (D4-1: only on a successful write,\n    /// monotonic, never the unsent tail). Dropping any prior [`ControllerSink`]\n    /// ends its writer's LIVE loop when `tx` drops; bumping `controller_epoch`\n    /// additionally makes a stale deadline-evict a no-op AND stops a superseded\n    /// writer mid-INITIAL-BATCH (P1c — a writer still flushing its owned `initial`\n    /// batch ignores the dropped `tx`, so the epoch gate is what guarantees a\n    /// single live writer per connection and a monotonic on-wire seq stream,\n    /// [`REQ-HAZARD-CONTROLLER-WRITER-REORDER`]). Stamps `driven_by` to the new\n    /// controller's identity.\n    fn become_controller(\n        &mut self,\n        sub: SharedSend,\n        by: Option<String>,\n        from_seq: u64,\n        conn_id: u64,\n        install: ControllerInstall,\n    ) {\n        // Drop the prior controller sink first (its writer's live loop ends when\n        // tx drops), and bump the generation so (a) an in-flight deadline-evict\n        // for the old controller can't unseat this one and (b) a prior writer\n        // still flushing its initial batch returns before its next frame.\n        self.controller = None;\n        let epoch = self.controller_epoch.fetch_add(1, Ordering::AcqRel) + 1;\n        // #6 (ADR-0031): a COLD attach's initial batch is a single synthesized clean\n        // repaint of the CURRENT SCREEN (at pseudo-seq `next_seq - 1`) instead of the\n        // raw ring — replaying an alt-screen TUI's raw ring into a fresh terminal is\n        // exactly the corruption #6/#7/#8 report. A resume-from-floor (`from_seq > 0`)\n        // keeps the raw-ring exactly-once re-fetch untouched (B2). Either way the\n        // batch is owned by the writer thread (never pushed through the live channel —\n        // it must not block the drain); live frames stream raw + in-order afterwards.\n        let initial: Vec<(u64, Vec<u8>)> = match self.repaint_initial(from_seq) {\n            Some(repaint) => vec![repaint],\n            None => self\n                .ring\n                .iter()\n                .filter(|(s, _)| *s >= from_seq)\n                .cloned()\n                .collect(),\n        };\n        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n        let writer_send = Arc::clone(&sub);\n        let dt = Arc::clone(&self.delivered_through);\n        let epoch_gate = Arc::clone(&self.controller_epoch);\n        let sid = self.session_id;\n        // W2 decouple: the writer publishes its in-flight-write window here so a\n        // stall-evict can distinguish a wedged writer from an idle one.\n        let write_blocked_since = Arc::new(Mutex::new(None));\n        let writer_blocked = Arc::clone(&write_blocked_since);\n        let writer = thread::spawn(move || {\n            controller_writer(\n                conn_id,\n                sid,\n                writer_send,\n                initial,\n                rx,\n                dt,\n                epoch,\n                epoch_gate,\n                writer_blocked,\n            )\n        });\n        self.controller = Some(ControllerSink {\n            conn_id,\n            tx,\n            send: sub,\n            by,\n            last_ok: Instant::now(),\n            write_blocked_since,\n            _writer: writer,\n        });\n        if rca_pty_trace_enabled() {\n            eprintln!(\n                \"RCA_CONTROLLER_INSTALL: conn_id={conn_id} sid={} epoch={epoch} \\\n                 source={} intent={} by={} elapsed_us={}\",\n                self.session_id,\n                install.source(),\n                install.intent(),\n                if self.controller_by().is_some() {\n                    \"remote\"\n                } else {\n                    \"local\"\n                },\n…\n            );\n        }\n…\n    }\n…\nfn controller_writer(\n…\n    epoch: u64,\n    controller_epoch: Arc<AtomicU64>,\n    write_blocked_since: Arc<Mutex<Option<Instant>>>,\n) {\n    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n    // move the wedge onto the mutex).\n    let mark_blocked = |on: bool| {\n        if let Ok(mut g) = write_blocked_since.lock() {\n            *g = if on { Some(Instant::now()) } else { None };\n        }\n    };\n    for (seq, bytes) in &initial {\n        let frame = output_envelope(sid, *seq, bytes);\n        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared\n        // epoch past ours) must write NOTHING further — otherwise its owned\n        // initial batch races the new writer on the same socket and reorders the\n        // seq stream. The gate is read UNDER `send.lock()`, atomically with the\n        // write: both writers serialize on this one socket mutex, and\n        // `become_controller` bumps the epoch BEFORE spawning the new writer, so\n        // whichever superseded writer next holds the lock observes the bump and\n        // bails before committing — no check-then-block-then-write window. (A\n        // frame the stale writer committed BEFORE the bump is irreducible at the\n        // broker; fix #1 removes the contradictory double-take that produced it.)\n        let mut trace = CtrlWriteTrace::new(conn_id, sid, epoch, Some(*seq), &frame);\n        mark_blocked(true);\n        if let Some(trace) = trace.as_mut() {\n            trace.begin_wait();\n        }\n        let ok = match send.lock() {\n            Ok(mut s) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.locked();\n                }\n                if controller_epoch.load(Ordering::Acquire) != epoch {\n                    mark_blocked(false);\n                    if let Some(trace) = trace.as_ref() {\n                        trace.done(\"superseded\");\n                    }\n                    return;\n                }\n                if let Some(trace) = trace.as_mut() {\n                    trace.begin_write();\n                }\n                let ok = write_frame(&mut *s, &frame).is_ok();\n                if let Some(trace) = trace.as_mut() {\n                    trace.finish_write();\n                }\n                ok\n            }\n            Err(_) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.lock_failed();\n                }\n                false\n            }\n        };\n        mark_blocked(false);\n        if let Some(trace) = trace.as_ref() {\n            trace.done(if ok { \"ok\" } else { \"error\" });\n        }\n        if !ok {\n            return;\n        }\n        advance_delivered(&delivered_through, *seq);\n    }\n    while let Ok(msg) = rx.recv() {\n        let (frame, seq) = match &msg {\n            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n            CtrlMsg::Control(frame) => (frame, None),\n        };\n        // NO epoch gate on the live path (P1c): new output only ever flows to the\n        // CURRENT controller's channel (the drain clones `self.controller.tx`), so\n        // a superseded writer's channel holds only its pre-supersede output\n        // backlog (seq the consumer dedups via snap-above) plus its own TERMINAL\n        // control frames — notably the `Displaced` kick the displaced controller\n        // MUST still receive. The loop ends naturally when `tx` drops. Gating\n        // output here would suppress that `Displaced` (the cv-matrix loud-take\n        // hang). The reorder hazard is the INITIAL-BATCH replay, gated above.\n        let mut trace = CtrlWriteTrace::new(conn_id, sid, epoch, seq, frame);\n        mark_blocked(true);\n        if let Some(trace) = trace.as_mut() {\n            trace.begin_wait();\n        }\n        let ok = match send.lock() {\n            Ok(mut s) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.locked();\n                    trace.begin_write();\n                }\n                let ok = write_frame(&mut *s, frame).is_ok();\n                if let Some(trace) = trace.as_mut() {\n                    trace.finish_write();\n                }\n                ok\n            }\n            Err(_) => {\n                if let Some(trace) = trace.as_mut() {\n                    trace.lock_failed();\n                }\n                false\n            }\n        };\n        mark_blocked(false);\n        if let Some(trace) = trace.as_ref() {\n            trace.done(if ok { \"ok\" } else { \"error\" });\n        }\n        if !ok {\n            return;\n        }\n        if let Some(seq) = seq {\n            // (a) CONTIGUOUS cursor (b4 / B2 gapless-resume — doyle's gate): with the\n            // drop-on-full handoff in `append`, the controller's live channel can SKIP\n            // a seq (dropped while Full). The frame still went to the socket (the live\n            // operator sees it), but the durable resume cursor must NOT jump past the\n            // gap — see [`contiguous_advance`].\n            contiguous_advance(&delivered_through, seq);\n        }\n    }\n}\n…\n        let host = self\n…\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        match host.pair_meet(&req.subnet, req.verbose) {\n            Ok(addr) => {\n                // The canonical lowercase-hex name (== node_id_hex everywhere),\n                // derived from the met address's proven id.\n                let member_hex = spt_proto::identity::PublicKey::from_bytes(addr.id.as_bytes())\n                    .map(|p| p.to_hex())\n                    .unwrap_or_default();\n                let session_id = self.next_id.fetch_add(1, Ordering::Relaxed);\n                {\n                    let mut holds = recover(&self.pair_holds);\n                    let now = Instant::now();\n                    holds.retain(|_, h| h.deadline > now); // sweep abandoned holds\n                    holds.insert(\n                        session_id,\n                        PairHold {\n                            subnet: req.subnet.clone(),\n                            addr,\n                            deadline: now + PAIR_CODE_WAIT,\n                        },\n                    );\n                }\n                let frame = Envelope::new(\n                    KIND_MET_MEMBER,\n                    serde_json::to_value(MetMember {\n                        session_id,\n…\n                    })\n                    .expect(\"MetMember serializes\"),\n                );\n…\n            }\n…\n        }","startLine":52,"lineNumbers":[52,null,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,null,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,null,1122,1123,null,1125,null,1643,null,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,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,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,null,4207,null,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,null,4237,4238,4239,null,4241,null,4246]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\rca-capture\\crates\\spt-daemon\\src\\broker.rs"}}},"isError":false,"timestamp":1783730543969}}
{"type":"message","id":"6061edf4","parentId":"fefdeaa3","timestamp":"2026-07-11T00:42:23.970Z","message":{"role":"toolResult","toolCallId":"call_iYCLxTwfBbzjT6h8CiCE4eQx|fc_08496348794f7e09016a5191706204819883aff67deda996a5","toolName":"grep","content":[{"type":"text","text":"# spt-core/.git/logs/refs/heads/release/\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)"}],"details":{"scopePath":"spt-core/.git/logs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":1,"fileCount":1,"files":["spt-core/.git/logs/refs/heads/release/v0.30.4"],"fileMatches":[{"path":"spt-core/.git/logs/refs/heads/release/v0.30.4","count":1}],"truncated":false,"displayContent":"# spt-core/.git/logs/refs/heads/release/\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)"},"isError":false,"timestamp":1783730543970}}
{"type":"message","id":"0f4b3748","parentId":"6061edf4","timestamp":"2026-07-11T00:42:23.979Z","message":{"role":"toolResult","toolCallId":"call_BFAnJQrqEXJLB3ysxSSdz9PM|fc_08496348794f7e09016a51917062548198b4676902d41e88fc","toolName":"read","content":[{"type":"text","text":"impl Inner {\n…\n    fn watchdog(&self) {\n…\n        loop {\n…\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            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    /// 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();\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        // ── 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();\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        {\n            let mut d = recover(&self.inner.dog);\n            d.shutdown = true;\n            self.inner.dog_cv.notify_all();\n        }\n        if let Some(dog) = self.dog.take() {\n            let _ = dog.join();\n        }\n        // Halves (gate or graveyard) drop AFTER the watchdog is gone — no raw\n        // handle use can race the CloseHandle/limbo path.\n    }\n}\n\nfn poisoned_err() -> io::Error {\n    io::Error::new(\n        io::ErrorKind::BrokenPipe,\n        \"broker conn poisoned (bounded write retired the physical connection)\",\n    )\n}\n\nfn timeout_err() -> io::Error {\n    io::Error::new(\n        io::ErrorKind::TimedOut,\n        \"broker conn write exceeded its bound (conn poisoned + aborted)\",\n    )\n}\n"}],"details":{"displayContent":{"text":"impl Inner {\n…\n    fn watchdog(&self) {\n…\n        loop {\n…\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            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    /// 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();\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        // ── 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();\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        {\r\n            let mut d = recover(&self.inner.dog);\r\n            d.shutdown = true;\r\n            self.inner.dog_cv.notify_all();\r\n        }\r\n        if let Some(dog) = self.dog.take() {\r\n            let _ = dog.join();\r\n        }\r\n        // Halves (gate or graveyard) drop AFTER the watchdog is gone — no raw\r\n        // handle use can race the CloseHandle/limbo path.\r\n    }\r\n}\r\n\r\nfn poisoned_err() -> io::Error {\r\n    io::Error::new(\r\n        io::ErrorKind::BrokenPipe,\r\n        \"broker conn poisoned (bounded write retired the physical connection)\",\r\n    )\r\n}\r\n\r\nfn timeout_err() -> io::Error {\r\n    io::Error::new(\r\n        io::ErrorKind::TimedOut,\r\n        \"broker conn write exceeded its bound (conn poisoned + aborted)\",\r\n    )\r\n}\r\n","startLine":170,"lineNumbers":[170,null,235,null,237,null,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"}}},"isError":false,"timestamp":1783730543979}}
{"type":"message","id":"df55cc24","parentId":"0f4b3748","timestamp":"2026-07-11T00:42:23.991Z","message":{"role":"toolResult","toolCallId":"call_z4dysK5aa7F10gKejh4zl52A|fc_08496348794f7e09016a5191706218819890307f9a22f07901","toolName":"read","content":[{"type":"text","text":"    // *counter* is rehydrated, never the wall-clock stamp.\n    eprintln!(\n        \"BRAIN_UP: connected to broker {name} (pid {}, generation {generation}, start-reason {})\",\n        std::process::id(),\n        reason.as_arg(),\n    );\n\n    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\n    // continuity from the broker — re-attach EVERY hosted session in resume mode\n    // from its delivered cursor, the production replacement for the retired\n    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\n    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\n    // so this is a no-op now and forward-correct when daemon-hosted sessions land\n    // (the live-agent adapter). A failure here is non-fatal — the brain still\n    // serves; the next heartbeat probes broker liveness. NOTE: this is the PTY\n    // message-DELIVERY axis; the orthogonal live-agent **Psyche** lifecycle (the\n    // run_pulse_loop production caller) is hosted by `livehost::spawn_live_host`\n    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\n    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\n    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\n    // this conn (the heartbeat below reads it only in net_status()/sessions()\n    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\n    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\n    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\n    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\n    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\n    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\n    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    match brain.resume_session_cursors() {\n        Ok(ids) if !ids.is_empty() => {\n            eprintln!(\n                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\n                ids.len()\n            )\n        }\n        Ok(_) => {}\n        Err(e) => eprintln!(\"BRAIN_RESUME_NONFATAL: {e} — no sessions resumed, continuing\"),\n    }\n\n    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\n    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\n    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\n    // promotes the trial binary on this stamp; promoting on a bare post-connect\n    // breadcrumb would accept a brain that connected but never rebuilt\n    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\n    // this is forward-correct: the gate is defined on the real readiness\n    // semantic the live-agent adapter inherits.\n    write_ready(generation);\n\n    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n    // online/offline exclusivity — a reconcile loop that runs each offline\n    // instance's `wake_command` as a supervised watcher child. Relocated from\n    // the broker process to the brain child: shellwake is brain-owned logic\n    // (ADR-0018 Q5 exception), a pure disk-reconciler that only reaches the\n    // broker over the same socket IPC for the remote-wake dial. Its boot sweep\n    // doubles as orphan cleanup, so a brain respawn re-reconciles the watcher\n    // children from disk — exactly the Q5 \"re-reconciled on brain start\"\n    // posture. The stop flag is held for symmetry, never raised (the watchers\n    // die with the brain process and the next brain re-sweeps them).\n    // [impl->REQ-SHELL-2]\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    let _ = crate::shellwake::spawn_wake_host(Arc::new(AtomicBool::new(false)));\n\n    // Live-agent Psyche host (M11-W0.1, REQ-DAEMON-1): the production caller\n    // `run_pulse_loop` lacked. One thread sweeps `livehost::reconcile_once` at\n    // boot (the rehydrate the no-op above can't do) and every tick — re-hosting\n    // every ONLINE live endpoint from disk (the harness-endpoint mirror of the\n    // shell waker). Online-only is staged behind the interim `api listen`\n    // (status-absent perches are skipped — no double-host) until W0.2 makes the\n    // brain the first-host. The stop flag is held for symmetry; the threads die\n    // with the brain and the next brain rehydrates from disk (CONTEXT:194).\n    // [impl->REQ-DAEMON-1]\n    let _ = crate::livehost::spawn_live_host(Arc::new(AtomicBool::new(false)), reason);\n\n    let mut consumers_started = false;\n    loop {\n        thread::sleep(BRAIN_HEARTBEAT);\n        // `net-status` is both the net-capability probe and the broker-liveness\n        // probe: an `Err` is the broker gone (or a future planned\n        // snapshot+self-exit, Q3) — exit for a supervised respawn.\n        let status = match brain.net_status() {\n            Ok(s) => s,\n            Err(e) => {\n                eprintln!(\"BRAIN_BROKER_LOST: {e} — exiting for supervised respawn\");\n                return Err(e);\n            }\n        };\n        if consumer_gate(status.enabled, consumers_started) {\n            let hex = status.node_id_hex.clone().unwrap_or_default();\n            spawn_net_consumers(&hex);\n            consumers_started = true;\n            eprintln!(\n                \"BRAIN_NET_CONSUMERS_UP: net enabled (node {hex}) — dispatcher + peer pump started\"\n            );\n        }\n        // DRIVE the broker's controller-liveness reap every heartbeat (UPDATE-WEDGE\n        // fix, counter-54). A `KIND_SESSIONS` poll is what runs the broker's\n        // `reap_dead_controller` sweep (broker.rs), which stall-evicts a black-holed\n        // LOCAL controller conn a HARD-KILLED prior brain generation left behind (its\n        // hosted PTYs keep writing to a dead pipe → the broker-side writer BLOCKS, never\n        // EOFs). Nothing else polls `KIND_SESSIONS` during an isolated brain-trial\n        // window, so without this the wedge never drains: the promotion DRAINED gate\n        // (`old_gen_drained()` = `!any_local_controller_wedged()`, a PURE READ that never\n        // drives the evict) stays false for the full trial → \"alive but never ready\"\n        // rollback (the 2026-07-09 field freeze), AND the same wedge black-holes recovery\n        // (the late `BRAIN_SUBSCRIBER_STALL_EVICT`). Driven on the FIRST heartbeat and on\n        // BOTH the trial candidate AND the rollback/recovery brain (one loop covers both);\n        // best-effort — a transient sessions error must never fell the heartbeat (the\n        // net_status probe above already owns broker-liveness). Self-applies: it drives\n        // the CURRENT (older) broker's already-shipped reap verb, so a new brain un-strands\n        // its own trial against a live older broker — no coordinated broker restart.\n        // [impl->REQ-UPDATE-TRIAL-DRAIN-DRIVE]\n        let _ = brain.sessions();\n        write_ready(generation);\n    }\n\n[Showing lines 174-288 of 2106. Use :289 to continue]"}],"details":{"truncation":{"content":"    // *counter* is rehydrated, never the wall-clock stamp.\r\n    eprintln!(\r\n        \"BRAIN_UP: connected to broker {name} (pid {}, generation {generation}, start-reason {})\",\r\n        std::process::id(),\r\n        reason.as_arg(),\r\n    );\r\n\r\n    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\r\n    // continuity from the broker — re-attach EVERY hosted session in resume mode\r\n    // from its delivered cursor, the production replacement for the retired\r\n    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\r\n    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\r\n    // so this is a no-op now and forward-correct when daemon-hosted sessions land\r\n    // (the live-agent adapter). A failure here is non-fatal — the brain still\r\n    // serves; the next heartbeat probes broker liveness. NOTE: this is the PTY\r\n    // message-DELIVERY axis; the orthogonal live-agent **Psyche** lifecycle (the\r\n    // run_pulse_loop production caller) is hosted by `livehost::spawn_live_host`\r\n    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\r\n    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\r\n    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\r\n    // this conn (the heartbeat below reads it only in net_status()/sessions()\r\n    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\r\n    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\r\n    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\r\n    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\r\n    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\r\n    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\r\n    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\r\n    match brain.resume_session_cursors() {\r\n        Ok(ids) if !ids.is_empty() => {\r\n            eprintln!(\r\n                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\r\n                ids.len()\r\n            )\r\n        }\r\n        Ok(_) => {}\r\n        Err(e) => eprintln!(\"BRAIN_RESUME_NONFATAL: {e} — no sessions resumed, continuing\"),\r\n    }\r\n\r\n    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\r\n    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\r\n    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\r\n    // promotes the trial binary on this stamp; promoting on a bare post-connect\r\n    // breadcrumb would accept a brain that connected but never rebuilt\r\n    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\r\n    // this is forward-correct: the gate is defined on the real readiness\r\n    // semantic the live-agent adapter inherits.\r\n    write_ready(generation);\r\n\r\n    // Shell wake-watchers (restoration D2-2): the offline half of the shell\r\n    // online/offline exclusivity — a reconcile loop that runs each offline\r\n    // instance's `wake_command` as a supervised watcher child. Relocated from\r\n    // the broker process to the brain child: shellwake is brain-owned logic\r\n    // (ADR-0018 Q5 exception), a pure disk-reconciler that only reaches the\r\n    // broker over the same socket IPC for the remote-wake dial. Its boot sweep\r\n    // doubles as orphan cleanup, so a brain respawn re-reconciles the watcher\r\n    // children from disk — exactly the Q5 \"re-reconciled on brain start\"\r\n    // posture. The stop flag is held for symmetry, never raised (the watchers\r\n    // die with the brain process and the next brain re-sweeps them).\r\n    // [impl->REQ-SHELL-2]\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    let _ = crate::shellwake::spawn_wake_host(Arc::new(AtomicBool::new(false)));\r\n\r\n    // Live-agent Psyche host (M11-W0.1, REQ-DAEMON-1): the production caller\r\n    // `run_pulse_loop` lacked. One thread sweeps `livehost::reconcile_once` at\r\n    // boot (the rehydrate the no-op above can't do) and every tick — re-hosting\r\n    // every ONLINE live endpoint from disk (the harness-endpoint mirror of the\r\n    // shell waker). Online-only is staged behind the interim `api listen`\r\n    // (status-absent perches are skipped — no double-host) until W0.2 makes the\r\n    // brain the first-host. The stop flag is held for symmetry; the threads die\r\n    // with the brain and the next brain rehydrates from disk (CONTEXT:194).\r\n    // [impl->REQ-DAEMON-1]\r\n    let _ = crate::livehost::spawn_live_host(Arc::new(AtomicBool::new(false)), reason);\r\n\r\n    let mut consumers_started = false;\r\n    loop {\r\n        thread::sleep(BRAIN_HEARTBEAT);\r\n        // `net-status` is both the net-capability probe and the broker-liveness\r\n        // probe: an `Err` is the broker gone (or a future planned\r\n        // snapshot+self-exit, Q3) — exit for a supervised respawn.\r\n        let status = match brain.net_status() {\r\n            Ok(s) => s,\r\n            Err(e) => {\r\n                eprintln!(\"BRAIN_BROKER_LOST: {e} — exiting for supervised respawn\");\r\n                return Err(e);\r\n            }\r\n        };\r\n        if consumer_gate(status.enabled, consumers_started) {\r\n            let hex = status.node_id_hex.clone().unwrap_or_default();\r\n            spawn_net_consumers(&hex);\r\n            consumers_started = true;\r\n            eprintln!(\r\n                \"BRAIN_NET_CONSUMERS_UP: net enabled (node {hex}) — dispatcher + peer pump started\"\r\n            );\r\n        }\r\n        // DRIVE the broker's controller-liveness reap every heartbeat (UPDATE-WEDGE\r\n        // fix, counter-54). A `KIND_SESSIONS` poll is what runs the broker's\r\n        // `reap_dead_controller` sweep (broker.rs), which stall-evicts a black-holed\r\n        // LOCAL controller conn a HARD-KILLED prior brain generation left behind (its\r\n        // hosted PTYs keep writing to a dead pipe → the broker-side writer BLOCKS, never\r\n        // EOFs). Nothing else polls `KIND_SESSIONS` during an isolated brain-trial\r\n        // window, so without this the wedge never drains: the promotion DRAINED gate\r\n        // (`old_gen_drained()` = `!any_local_controller_wedged()`, a PURE READ that never\r\n        // drives the evict) stays false for the full trial → \"alive but never ready\"\r\n        // rollback (the 2026-07-09 field freeze), AND the same wedge black-holes recovery\r\n        // (the late `BRAIN_SUBSCRIBER_STALL_EVICT`). Driven on the FIRST heartbeat and on\r\n        // BOTH the trial candidate AND the rollback/recovery brain (one loop covers both);\r\n        // best-effort — a transient sessions error must never fell the heartbeat (the\r\n        // net_status probe above already owns broker-liveness). Self-applies: it drives\r\n        // the CURRENT (older) broker's already-shipped reap verb, so a new brain un-strands\r\n        // its own trial against a live older broker — no coordinated broker restart.\r\n        // [impl->REQ-UPDATE-TRIAL-DRAIN-DRIVE]\r\n        let _ = brain.sessions();\r\n        write_ready(generation);\r\n    }\r","truncated":true,"truncatedBy":"lines","totalLines":1933,"totalBytes":7198,"outputLines":115,"outputBytes":7198,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    // *counter* is rehydrated, never the wall-clock stamp.\r\n    eprintln!(\r\n        \"BRAIN_UP: connected to broker {name} (pid {}, generation {generation}, start-reason {})\",\r\n        std::process::id(),\r\n        reason.as_arg(),\r\n    );\r\n\r\n    // Multi-session cold-start resume (restoration D4-2, ADR-0018 Q6): rebuild\r\n    // continuity from the broker — re-attach EVERY hosted session in resume mode\r\n    // from its delivered cursor, the production replacement for the retired\r\n    // brain→brain `BrainState` handoff. Today the supervised daemon brain hosts no\r\n    // PTY sessions (net-consumers + shellwake re-derive from disk / `net-status`),\r\n    // so this is a no-op now and forward-correct when daemon-hosted sessions land\r\n    // (the live-agent adapter). A failure here is non-fatal — the brain still\r\n    // serves; the next heartbeat probes broker liveness. NOTE: this is the PTY\r\n    // message-DELIVERY axis; the orthogonal live-agent **Psyche** lifecycle (the\r\n    // run_pulse_loop production caller) is hosted by `livehost::spawn_live_host`\r\n    // below, keyed on the live ENDPOINT not the broker session (CONTEXT:168/177).\r\n    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\r\n    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\r\n    // this conn (the heartbeat below reads it only in net_status()/sessions()\r\n    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\r\n    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\r\n    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\r\n    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\r\n    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\r\n    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\r\n    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\r\n    match brain.resume_session_cursors() {\r\n        Ok(ids) if !ids.is_empty() => {\r\n            eprintln!(\r\n                \"BRAIN_RESUMED: re-established {} session cursor(s): {ids:?}\",\r\n                ids.len()\r\n            )\r\n        }\r\n        Ok(_) => {}\r\n        Err(e) => eprintln!(\"BRAIN_RESUME_NONFATAL: {e} — no sessions resumed, continuing\"),\r\n    }\r\n\r\n    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\r\n    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\r\n    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\r\n    // promotes the trial binary on this stamp; promoting on a bare post-connect\r\n    // breadcrumb would accept a brain that connected but never rebuilt\r\n    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\r\n    // this is forward-correct: the gate is defined on the real readiness\r\n    // semantic the live-agent adapter inherits.\r\n    write_ready(generation);\r\n\r\n    // Shell wake-watchers (restoration D2-2): the offline half of the shell\r\n    // online/offline exclusivity — a reconcile loop that runs each offline\r\n    // instance's `wake_command` as a supervised watcher child. Relocated from\r\n    // the broker process to the brain child: shellwake is brain-owned logic\r\n    // (ADR-0018 Q5 exception), a pure disk-reconciler that only reaches the\r\n    // broker over the same socket IPC for the remote-wake dial. Its boot sweep\r\n    // doubles as orphan cleanup, so a brain respawn re-reconciles the watcher\r\n    // children from disk — exactly the Q5 \"re-reconciled on brain start\"\r\n    // posture. The stop flag is held for symmetry, never raised (the watchers\r\n    // die with the brain process and the next brain re-sweeps them).\r\n    // [impl->REQ-SHELL-2]\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    let _ = crate::shellwake::spawn_wake_host(Arc::new(AtomicBool::new(false)));\r\n\r\n    // Live-agent Psyche host (M11-W0.1, REQ-DAEMON-1): the production caller\r\n    // `run_pulse_loop` lacked. One thread sweeps `livehost::reconcile_once` at\r\n    // boot (the rehydrate the no-op above can't do) and every tick — re-hosting\r\n    // every ONLINE live endpoint from disk (the harness-endpoint mirror of the\r\n    // shell waker). Online-only is staged behind the interim `api listen`\r\n    // (status-absent perches are skipped — no double-host) until W0.2 makes the\r\n    // brain the first-host. The stop flag is held for symmetry; the threads die\r\n    // with the brain and the next brain rehydrates from disk (CONTEXT:194).\r\n    // [impl->REQ-DAEMON-1]\r\n    let _ = crate::livehost::spawn_live_host(Arc::new(AtomicBool::new(false)), reason);\r\n\r\n    let mut consumers_started = false;\r\n    loop {\r\n        thread::sleep(BRAIN_HEARTBEAT);\r\n        // `net-status` is both the net-capability probe and the broker-liveness\r\n        // probe: an `Err` is the broker gone (or a future planned\r\n        // snapshot+self-exit, Q3) — exit for a supervised respawn.\r\n        let status = match brain.net_status() {\r\n            Ok(s) => s,\r\n            Err(e) => {\r\n                eprintln!(\"BRAIN_BROKER_LOST: {e} — exiting for supervised respawn\");\r\n                return Err(e);\r\n            }\r\n        };\r\n        if consumer_gate(status.enabled, consumers_started) {\r\n            let hex = status.node_id_hex.clone().unwrap_or_default();\r\n            spawn_net_consumers(&hex);\r\n            consumers_started = true;\r\n            eprintln!(\r\n                \"BRAIN_NET_CONSUMERS_UP: net enabled (node {hex}) — dispatcher + peer pump started\"\r\n            );\r\n        }\r\n        // DRIVE the broker's controller-liveness reap every heartbeat (UPDATE-WEDGE\r\n        // fix, counter-54). A `KIND_SESSIONS` poll is what runs the broker's\r\n        // `reap_dead_controller` sweep (broker.rs), which stall-evicts a black-holed\r\n        // LOCAL controller conn a HARD-KILLED prior brain generation left behind (its\r\n        // hosted PTYs keep writing to a dead pipe → the broker-side writer BLOCKS, never\r\n        // EOFs). Nothing else polls `KIND_SESSIONS` during an isolated brain-trial\r\n        // window, so without this the wedge never drains: the promotion DRAINED gate\r\n        // (`old_gen_drained()` = `!any_local_controller_wedged()`, a PURE READ that never\r\n        // drives the evict) stays false for the full trial → \"alive but never ready\"\r\n        // rollback (the 2026-07-09 field freeze), AND the same wedge black-holes recovery\r\n        // (the late `BRAIN_SUBSCRIBER_STALL_EVICT`). Driven on the FIRST heartbeat and on\r\n        // BOTH the trial candidate AND the rollback/recovery brain (one loop covers both);\r\n        // best-effort — a transient sessions error must never fell the heartbeat (the\r\n        // net_status probe above already owns broker-liveness). Self-applies: it drives\r\n        // the CURRENT (older) broker's already-shipped reap verb, so a new brain un-strands\r\n        // its own trial against a live older broker — no coordinated broker restart.\r\n        // [impl->REQ-UPDATE-TRIAL-DRAIN-DRIVE]\r\n        let _ = brain.sessions();\r\n        write_ready(generation);\r\n    }\r","startLine":174,"lineNumbers":[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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brainproc.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2106,"totalBytes":7198,"outputLines":115,"outputBytes":7198,"shownRange":{"start":174,"end":288},"nextOffset":289}}},"isError":false,"timestamp":1783730543991}}
{"type":"message","id":"300d192f","parentId":"df55cc24","timestamp":"2026-07-11T00:42:31.903Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Preparing concise summary for Main**\n\n<!-- -->"},{"type":"toolCall","id":"call_t2gte5ReakgFitrwVpLGsdb5|fc_08496348794f7e09016a519173672c81988a01864522a5c7c9","name":"irc","arguments":{"i":"Send pinned root","op":"send","to":"Main","message":"Pinned broker-side root: pre-v0.30.6 SharedSend=Arc<Mutex<SendHalf>> held across blocking write_frame; Windows WriteFileEx/SleepEx(INFINITE) could park forever. Logical stall eviction only dropped role, not in-flight writer/physical conn. Repro capture: .worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md; old exact code .worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760; codec.rs:21-35. v0.30.3 ce053dc added heartbeat sessions() roundtrip and v0.30.4 fb499db changed resume to Viewer but kept undrained subscription, enabling SharedSend self-deadlock; v0.30.5 03c7109 cursor-only removed that specific trigger; v0.30.6 d634b67 introduces bounded/cancelable BrokerConn writes. Echo verify is confounder/load only: separate per-session worker, sleeps off locks, InputWriter try_send; enabled env can add up to settle/echo/redrive delay but cannot globally freeze broker.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8949,"output":323,"cacheRead":152064,"cacheWrite":0,"totalTokens":161336,"reasoningTokens":21,"cost":{"input":0.04474500000000001,"output":0.00969,"cacheRead":0.076032,"cacheWrite":0,"total":0.130467}},"stopReason":"toolUse","timestamp":1783730544035,"responseId":"resp_08496348794f7e09016a519170c8a48198ad57c65f8dcb3a21","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a5191727f9c8198ad8145863fd2e8eb","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZFz6OqJVLOMs74r0WL1BFZfew-hFKsv1kGZ6YkRsRdbdXvQEEaZi71uwmlB5XqmdKGvNV_-jiHWoE4pzy-WP86DbKv3qVzkfuE1bL9jOemswHF0cNRwtuToiPiRhEH2yaQtiabcoBPOcYsn4bEjaqLPuouARzJgr6dWC4vs7v93pB-uywnHOD_dCHMsabz_bhjcFQZ1pcLWH4quuhth3SCuGcLOfjs6DFql6tPqrGsEo7ZmzfVRbgdr3CuvWK82XU0HdTRfCObQpgfV8-O5i2rHm0pcaEoBWC39cZfpqIxbuVs1Ac2AuvVhmVvRCurStd0AvIzu2SxeNdyYGNBUHtgDJYRFZ3E4TzwxatmAoJMQ2l_WTcnNmMOrXCcakk_8mdhP73EP2UVHqH1kmimF60DKVNjiGROPuyZk_tbIrKP16-fY1oJDxfJOUS349xtvzGLBswK93gvBLFktvGSsxTbWjLmij43HiFxNLNReKq__8rBhbQ_HvOYTxwBmlFOEYugtLG9tAViuS0voyGDYbszNkOXHlMpXuC4XgtUwuoHUEXAUeXGgOXoN4sy4z31HxQKkLm7hJT424BGTpAzYWs-dxY0kTgKg33qV33iApx-HFs8m28GJT-z3tbRgrZ-AaazUR890FNReSxKbxaE05sJF9zzfOkBRxdO5z1Qb2spPx3IifoRpZa0tF6qbexY-6lYTFWqUZC6HCEebzqbl8MOPTqgCEafESYucF2EpBx7gzUoMTLcJyDWNbvg2MgH04ywy7tZnJU6dcI_VQCn27YnqBXfVqhKdxyGhhLa6WUFqMeZH4Y6rkPrOKQCbjqW6b62POy3VhFUImLg5IF9l-aWvcq37_2VR9QeRws7Gv3ecbm1ys1toM_72joowQBFQjCNpYJezX6A-RF9UKpSsvsKZTQvg8cDIbbtAEwK0A45up0qJgjSobzI6lwxG3VUZV_yvW7w4QIAHLoCttYsBf2Ci6y5AuzDnI2PZfWdtW3YoWdsTMicT622LAPFgY91zUCruWTeqww9Ppuvli7KFhLUv6A==","summary":[{"type":"summary_text","text":"**Preparing concise summary for Main**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a519173672c81988a01864522a5c7c9","type":"function_call","status":"completed","arguments":"{\"i\":\"Send pinned root\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Pinned broker-side root: pre-v0.30.6 SharedSend=Arc<Mutex<SendHalf>> held across blocking write_frame; Windows WriteFileEx/SleepEx(INFINITE) could park forever. Logical stall eviction only dropped role, not in-flight writer/physical conn. Repro capture: .worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md; old exact code .worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760; codec.rs:21-35. v0.30.3 ce053dc added heartbeat sessions() roundtrip and v0.30.4 fb499db changed resume to Viewer but kept undrained subscription, enabling SharedSend self-deadlock; v0.30.5 03c7109 cursor-only removed that specific trigger; v0.30.6 d634b67 introduces bounded/cancelable BrokerConn writes. Echo verify is confounder/load only: separate per-session worker, sleeps off locks, InputWriter try_send; enabled env can add up to settle/echo/redrive delay but cannot globally freeze broker.\" ,\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_t2gte5ReakgFitrwVpLGsdb5","name":"irc"}]},"duration":7867.361900000367,"ttft":1829.722500000149,"contextSnapshot":{"promptTokens":161013,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_t2gte5ReakgFitrwVpLGsdb5|fc_08496348794f7e09016a519173672c81988a01864522a5c7c9","toolName":"irc","startedAt":"2026-07-11T00:42:31.904Z","intent":"Send pinned root"},"id":"33ac6598","parentId":"300d192f","timestamp":"2026-07-11T00:42:31.904Z"}
{"type":"message","id":"039e4eb0","parentId":"33ac6598","timestamp":"2026-07-11T00:42:31.904Z","message":{"role":"toolResult","toolCallId":"call_t2gte5ReakgFitrwVpLGsdb5|fc_08496348794f7e09016a519173672c81988a01864522a5c7c9","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: woken"}],"details":{"op":"send","from":"FreezeCodePath","to":"Main","receipts":[{"to":"Main","outcome":"woken"}]},"isError":false,"timestamp":1783730551904}}
{"type":"message","id":"16f1c570","parentId":"039e4eb0","timestamp":"2026-07-11T00:43:38.404Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning comprehensive schema with versions and evidence**\n\n<!-- -->"},{"type":"toolCall","id":"call_aOCvU1aF4kRSO1ajO9BYsW7M|fc_08496348794f7e09016a51917bb45481989b2acd305ffdb2f5","name":"yield","arguments":{"type":"result","result":{"data":{"summary":"Strongest pinned root candidate: a pre-v0.30.6 broker connection write could block indefinitely after a controller/brain consumer stopped reading. The physical mechanism is pinned, reproduced, and source-localized; only the historical identity/reason of the field consumer that stopped draining is not recoverable from code alone. Before round 4, `SharedSend` was `Arc<Mutex<SendHalf>>`; controller/viewer/reply writers acquired that mutex and retained it across synchronous length-prefixed `write_frame` (`write_all` prefix, body, then `flush`). On Windows/interprocess 2.4.2 the underlying named-pipe write waits through `WriteFileEx` + `SleepEx(INFINITE)` and exposes no usable write timeout. Logical `stall_evict_controller` removed the controller role but did not cancel the in-flight write, close the physical connection, release the writer's stack-owned mutex guard, or join the writer. A real black-hole reproduction observed four writers acquire immediately (`wait_us=0`) and then remain inside `write_frame` for ~127.953 s, returning only when a later restart closed their connections; this kills the competing mutex-convoy and scheduler-starvation explanations. Exact evidence: `.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md:1-97`; old implementation `.worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760`; framing `crates/spt-daemon/src/codec.rs:21-35`; current regression narrative/test `crates/spt-daemon/tests/brain_decouple.rs:384-474`.\n\nVersion/commit chain: v0.30.2 tag ref `b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa`; v0.30.3 `0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047`; v0.30.4 `ade41be9d865b43f92a9c9017507734d8a9edd55`; v0.30.5 `8e5ff889e0a4fa9a907801dc861904608cbc8e65`; v0.30.6 `d73e19e12f7192a08d6ee502d21d03809a57a921` (`.git/refs/tags/v0.30.2` through `v0.30.6`). The v0.30.3 window includes `ce053dc930104afda280504b7e489f27462d47bb`, “brain self-drives the controller reap”; it added a second serialized request/reply round trip, `brain.sessions()`, every 500 ms (`brainproc.rs:253-287`). It did not itself block on peers, but made heartbeat progress depend on the already-contended brain connection. v0.30.4 fix `fb499dbfc29521daab72cb49e1768aeb5f3c74bc` changed cold resume from local Controller to Viewer, preventing control steal but retaining an output subscription on the same non-draining request/reply connection. v0.30.5 fix `03c71093ca70e76a86d42087e753e3cca0793095` made the supervised daemon resume cursor-only, eliminating that specific undrained-subscription self-deadlock (`brainproc.rs:181-208`; `brain.rs:1029-1085`). v0.30.6 round-4 fix `d634b67b2962ab919d0a03a280d63695e48214b3` replaced every raw physical broker-connection send with bounded, cancelable, poison-on-failure `BrokerConn` writes; branch log explicitly names the change, and `conn.rs:1-59,304-465` shows the absolute deadline covering gate wait plus OS completion, out-of-band CancelIoEx/shutdown, connection poison/sever, and no reuse.\n\nCode-path operation map for v0.30.3-v0.30.6: (1) PTY output drain itself appends/rings and performs only `try_send` fan-out under `Mutex<OutputLog>` in the modern/pre-round-4 architecture (`broker.rs:715-858`); this is non-blocking and therefore not the terminal freeze root. (2) Per-controller and per-viewer dedicated writer threads block on `rx.recv()` while idle and blocked synchronously on physical socket writes while active (`broker.rs:1330-1357,1438-1500`); before v0.30.6 the latter held the connection mutex indefinitely. (3) All dispatch replies on the same connection serialized behind the same send mutex; thus an undrained resumed viewer could hold the mutex while output-writing and starve `KIND_NET_STATUS_REPLY`/`KIND_SESSIONS_REPLY`, preventing the brain from reading, which perpetuated the writer block—a closed causal cycle documented at `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:31-85` and tested at `tests/brain_resume_conn_deadlock.rs:4-24`. (4) Resume in v0.30.4 called `resume_sessions`: query sessions, seed cursor, subscribe each as Viewer (`brain.rs:1029-1058`). The supervised brain does not continuously drain PTY output, so this multiplexed unsolicited output and request/reply onto one connection. v0.30.5 uses `resume_session_cursors` and sends no subscribe (`brain.rs:1061-1085`). (5) Inject delivery is serialized per translation-bearing session: one `event_rx` loop, up to two attempts, a 400 ms settle probe cadence, 1.2 s echo-verify window when declared, and a 5 s translation commit deadline (`broker.rs:353-445,1960-2145,2160-2285`). `SPT_INJECT_VERIFY_ECHO` is non-default and enabled on the live daemon, so it can increase PTY traffic and worker occupancy and may help satisfy the load trigger, but it sleeps on the dedicated inject worker with no output-log/floor lock held; output-log reads are short snapshots, PTY writes enqueue through bounded non-blocking `InputWriter::enqueue` (`broker.rs:1600-1715`), and the sole blocking PTY write is isolated on its per-session input-writer thread (`broker.rs:1717-1730`). It is therefore a confounder/load amplifier, not a code-supported global broker-freeze mechanism.\n\nFalsifiable hypotheses:\n1. PHYSICAL NON-DRAINING-CONSUMER WRITE (strongest/pinned, supported): real `seq>0` output plus a connected consumer that stops reading causes a controller/viewer writer to enter `write_frame`; pre-v0.30.6 it remains alive after logical eviction. Falsifier: reproduce with an unread controller while showing the writer/handler physically retires within the configured deadline without client drop on pre-v0.30.6. Existing capture shows the opposite pre-fix, and `brain_decouple.rs:471+` is the post-fix gate.\n2. v0.30.4 UNDRAINED RESUME SELF-DEADLOCK (supported as a concrete trigger, closed in v0.30.5): resume-as-Viewer replays active sessions onto the daemon brain's request/reply connection; a viewer writer blocks holding SharedSend; heartbeat reply needs SharedSend; heartbeat cannot return/drain. Falsifier: run `resume_sessions` on N flooding sessions while heartbeat round trips continue at the healthy floor, or prove zero viewer subscriptions after daemon resume. `brain_resume_conn_deadlock.rs:264-340` now asserts cursor-only leaves zero viewers and its seam-sensitivity arm proves `resume_sessions` does create them.\n3. v0.30.3 REAP-DRIVE ITSELF BLOCKS ON DEAD PEER (killed): `sessions()` snapshots only session locks; `net_status()` is a local status read; `OutputLog::append` uses non-blocking `try_send`; no dead-peer operation is on this tick. `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:5-30` pins the negative code trace. Falsifier for the kill would be an exact call from either heartbeat request into a peer dial/wait or a held lock shared with it; none exists.\n4. BROKER NET-RUNTIME STARVATION BY DEAD-PEER DIALS (killed): a 12-deep proof-hang/unreachable dial rig kept the net-runtime liveness canary at 32-33 ms versus the 15 s stall threshold and left 12/24 permits free; async awaits yield. Evidence `docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md:1-54`. Falsifier for the kill: canary age approaching the stall deadline under the same dial shapes.\n5. INJECT SETTLE/ECHO RE-DRIVE GLOBALLY SERIALIZES PTY DATA (killed as root, retained as confounder): declared echo verification may cause settle probes, sleeps, a second typed attempt, and additional output, but operations are confined to a per-session worker; sleeps happen outside locks and PTY writes are non-blocking queue handoffs. Falsifier: thread evidence showing the inject worker sleeps while holding `OutputLog`, the inject floor, global broker dispatch, or a physical connection gate. Current code shows none. It can still increase output pressure and should be disabled in comparative field trials because the daemon has non-default `SPT_INJECT_VERIFY_ECHO` enabled.\n\nConclusion: code plus the saved physical reproduction pins the durable broker-side root to unbounded/cancel-incapable physical connection writes, not merely to the v0.30.3/v0.30.4 resume policy. v0.30.3 and v0.30.4 supplied/widened reproducible triggers; v0.30.5 removed the daemon-resume trigger; only v0.30.6 closes the general failure class for any stuck controller/viewer/reply consumer. Code alone cannot name why the original field client stopped reading; the root report explicitly records that historical process identity as unproven.","files":[{"path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","description":"Authoritative physical RCA and Windows reproduction: four immediate-lock writes parked inside frame I/O ~127.953 s; logical eviction did not cancel/retire them; exact old-code anchors and rejected causes."},{"path":"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs","description":"Instrumented pre-v0.30.6 broker. Lines 73-77 define raw SharedSend; 1048-1108 install controller writer; 1664-1760 retain SendHalf mutex across write_frame on initial/live paths."},{"path":"spt-core/crates/spt-daemon/src/codec.rs","description":"Lines 21-35 implement synchronous length-prefixed frame output using write_all(prefix), write_all(body), flush."},{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","description":"Exact v0.30.4 self-deadlock causal chain and code proof that v0.30.3 reap-drive/dead-peer tick is not itself blocking; explains Viewer fix did not remove subscription."},{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","description":"Empirical kill of net-runtime starvation: 12 in-flight dead-peer dials, 32-33 ms canary age, permits available."},{"path":"spt-core/crates/spt-daemon/src/brainproc.rs","description":"Lines 181-208 current v0.30.5 cursor-only resume; lines 253-287 v0.30.3 heartbeat sessions reap-drive serialized after net_status."},{"path":"spt-core/crates/spt-daemon/src/brain.rs","description":"Lines 1029-1058 draining-only subscribe-as-Viewer resume and deadlock warning; 1061-1085 cursor-only safe resume; 1585-1610 explains local Control resume steals/freezes."},{"path":"spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","description":"Dead-peer-independent round-3 regression proof and deterministic zero-viewer/seam-sensitivity guard."},{"path":"spt-core/crates/spt-daemon/src/conn.rs","description":"v0.30.6 bounded connection primitive. Lines 1-59 state failure class; 304-465 implement absolute bounded gate+write, watchdog cancellation, poisoning, severing, and retirement."},{"path":"spt-core/crates/spt-daemon/tests/brain_decouple.rs","description":"Lines 384-474 document exact pre-fix Windows physical-retirement failure and post-fix non-draining-controller gate."},{"path":"spt-core/crates/spt-daemon/src/broker.rs","description":"Current active paths: output non-blocking fan-out 715-858; writers 1330-1500; InputWriter isolation 1600-1730; settle/echo/re-drive 1960-2285."},{"path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive","description":"Commit evidence for v0.30.3-era ce053dc reap-drive change."},{"path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","description":"Commit evidence for fb499db resume-as-Viewer and 03c7109 cursor-only round-3 fix, plus diagnostic verdict commits."},{"path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","description":"Commit evidence for d634b67 v0.30.6 bounded+cancelable+poison-on-failure writes and its RED-first physical gate."},{"path":"spt-core/.git/refs/tags/v0.30.2","description":"Tag ref b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa."},{"path":"spt-core/.git/refs/tags/v0.30.3","description":"Tag ref 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047."},{"path":"spt-core/.git/refs/tags/v0.30.4","description":"Tag ref ade41be9d865b43f92a9c9017507734d8a9edd55."},{"path":"spt-core/.git/refs/tags/v0.30.5","description":"Tag ref 8e5ff889e0a4fa9a907801dc861904608cbc8e65."},{"path":"spt-core/.git/refs/tags/v0.30.6","description":"Tag ref d73e19e12f7192a08d6ee502d21d03809a57a921."}],"architecture":"Active terminal output flows PTY child → one per-session drain thread → short `Mutex<OutputLog>` append/ring update → non-blocking bounded queue handoffs to isolated controller/viewer writers → serialized physical broker connection → attach server/net stream → `spt rc`. Input flows controller/inject → per-session `InjectFloor` ordering → bounded non-blocking `InputWriter` queue → sole blocking PTY input writer. A brain swap preserves broker/PTYs; the replacement brain queries broker-held session cursors. In v0.30.4 it subscribed those sessions as Viewers on the same connection used for heartbeat request/reply, although that brain did not continuously drain output. The resulting unsolicited-output writer and dispatch reply writer shared one `Arc<Mutex<SendHalf>>`; if output filled the pipe, the former held the mutex inside OS I/O and permanently prevented the reply that would let the brain drain—self-deadlock. v0.30.5 separates cursor reconstruction from actual subscription for the non-draining supervised brain. v0.30.6 changes the lower connection layer itself: every writer acquires a deadline-bounded gate, writes outside locks with a watchdog, and on timeout poisons/aborts/severs the whole physical connection so existing EOF cleanup removes roles. This is the general fix because it also covers a stuck human controller unrelated to resume."}}}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1102,"output":3593,"cacheRead":160256,"cacheWrite":0,"totalTokens":164951,"reasoningTokens":58,"cost":{"input":0.00551,"output":0.10779,"cacheRead":0.08012799999999999,"cacheWrite":0,"total":0.193428}},"stopReason":"toolUse","timestamp":1783730551957,"responseId":"resp_08496348794f7e09016a519178c6308198a4342e7ccbc68648","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_08496348794f7e09016a51917a13188198a7370bc5cd7c6e6d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZF7-074FvknCkTZrCIiVfORT25daiGbVcq1yf5TT1UKwnaW4yYf6Jdbl8XdBcs5VammZU9CDAsyyvikz5X87DG7U6ybozQ4e5KDmDbeYL2PcjaaLCp54z2BY6Q-ZV_m4WoqroAuYy_PswkyNTzhW_L7r8xouD5u3IufDZBV_JJITHOFWvZvyHwFYhciBtZg2pFSVF7q7phrVYfv28HVXlZR2WrgRN5ZTYEkfT9MWODDufkf9MDph3d6GMkP0gjkL3Gmyjy_W4cmukdkAGbvqhIGQ5qF2x2m4p1BbzQScyiIJs-LxB-FJeXap-Yli3njRIZ2w1Mz7bfsEM1Dv2AgZ_CrtCVQzmCTk0CISEBk9p-YbEP3wKiXEz09wT-MmJt6top89fMa_Llrjm0kdA9COpNNRdc9Mmd8FbFSyWyhw8g9lCJreJ44xzsD8m6Zoq0odi5K_S50mYJ-zUmjN6RJ271bTDkJkABfm6DSIRK2QGbptFNLyEqtiRSWzMfnv8qcz2X2zT4tqtqvoE8cU3jfVWePIRcWUXX2SiZ52C_mOUrEs3oyR3Sj5u-LmO34L51V6Knyl5-eyu_V_J18qShSVW8ZDCoeYh8bgmpzVRZRRyJevp97KCMj5x0anZY0PcDqh0CVKypB5EldQ48AQupE48w_LfxkG1dGIwVL_zImcnHmIkvzY2uJH1f6jpqKU5IOudaCx4AWe2wgopfAzoWgRD6k1FiW_2H-oZkwYagp_wY7ytXlmD8QLVHDU8CwpYh4sT4t10-wNrUyoktP11irpYvtBXlGtUNFZBqpwZjvqh6t-OMgx5hdBH7aVTaT5mwuB97xDU2Kn8m8_JPLy_BCGoo6ts2QgBrioFWKJNDCXBCUmqfo3jEtKlzndtijP7B8zBmvfwmS6pU8gIpGxfdfX4IS5bHaEVfseioBiKj6KUnSSLb7AMdiyQeMRCWuMBzWlc3RqbMsWhAOxuDh0RmZsBn5cO2JPf3CYB30pvWxogWIr8VqEYFy3vtpkljbvu1TKMM08-aWQvFe64SCzlhSIf8Lx40wEuWBna3BGSzLIu2WLm1yA889IdirxzCQ1hZX7EOZfV0s5u54ylUye8139aZr4R8EBq6fR13kbxvZbi0ldXYOqPNaD1_SCDs45rG0y_gcGGYH3_t9epQwkAmFgA3_eBHaq9xvnluw8Nnj5K0NjIjlfFkRv6w-dJRsGENSFoMzE16iK5E8lAfmraex1UAYkEtiNrXnJOnFvppEQnI2-XaZGU4PZ0u1P4PwALh4_OKJ","summary":[{"type":"summary_text","text":"**Planning comprehensive schema with versions and evidence**\n\n<!-- -->"}]},{"id":"fc_08496348794f7e09016a51917bb45481989b2acd305ffdb2f5","type":"function_call","status":"completed","arguments":"{\"type\":\"result\",\"result\":{\"data\":{\"summary\":\"Strongest pinned root candidate: a pre-v0.30.6 broker connection write could block indefinitely after a controller/brain consumer stopped reading. The physical mechanism is pinned, reproduced, and source-localized; only the historical identity/reason of the field consumer that stopped draining is not recoverable from code alone. Before round 4, `SharedSend` was `Arc<Mutex<SendHalf>>`; controller/viewer/reply writers acquired that mutex and retained it across synchronous length-prefixed `write_frame` (`write_all` prefix, body, then `flush`). On Windows/interprocess 2.4.2 the underlying named-pipe write waits through `WriteFileEx` + `SleepEx(INFINITE)` and exposes no usable write timeout. Logical `stall_evict_controller` removed the controller role but did not cancel the in-flight write, close the physical connection, release the writer's stack-owned mutex guard, or join the writer. A real black-hole reproduction observed four writers acquire immediately (`wait_us=0`) and then remain inside `write_frame` for ~127.953 s, returning only when a later restart closed their connections; this kills the competing mutex-convoy and scheduler-starvation explanations. Exact evidence: `.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md:1-97`; old implementation `.worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760`; framing `crates/spt-daemon/src/codec.rs:21-35`; current regression narrative/test `crates/spt-daemon/tests/brain_decouple.rs:384-474`.\\n\\nVersion/commit chain: v0.30.2 tag ref `b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa`; v0.30.3 `0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047`; v0.30.4 `ade41be9d865b43f92a9c9017507734d8a9edd55`; v0.30.5 `8e5ff889e0a4fa9a907801dc861904608cbc8e65`; v0.30.6 `d73e19e12f7192a08d6ee502d21d03809a57a921` (`.git/refs/tags/v0.30.2` through `v0.30.6`). The v0.30.3 window includes `ce053dc930104afda280504b7e489f27462d47bb`, “brain self-drives the controller reap”; it added a second serialized request/reply round trip, `brain.sessions()`, every 500 ms (`brainproc.rs:253-287`). It did not itself block on peers, but made heartbeat progress depend on the already-contended brain connection. v0.30.4 fix `fb499dbfc29521daab72cb49e1768aeb5f3c74bc` changed cold resume from local Controller to Viewer, preventing control steal but retaining an output subscription on the same non-draining request/reply connection. v0.30.5 fix `03c71093ca70e76a86d42087e753e3cca0793095` made the supervised daemon resume cursor-only, eliminating that specific undrained-subscription self-deadlock (`brainproc.rs:181-208`; `brain.rs:1029-1085`). v0.30.6 round-4 fix `d634b67b2962ab919d0a03a280d63695e48214b3` replaced every raw physical broker-connection send with bounded, cancelable, poison-on-failure `BrokerConn` writes; branch log explicitly names the change, and `conn.rs:1-59,304-465` shows the absolute deadline covering gate wait plus OS completion, out-of-band CancelIoEx/shutdown, connection poison/sever, and no reuse.\\n\\nCode-path operation map for v0.30.3-v0.30.6: (1) PTY output drain itself appends/rings and performs only `try_send` fan-out under `Mutex<OutputLog>` in the modern/pre-round-4 architecture (`broker.rs:715-858`); this is non-blocking and therefore not the terminal freeze root. (2) Per-controller and per-viewer dedicated writer threads block on `rx.recv()` while idle and blocked synchronously on physical socket writes while active (`broker.rs:1330-1357,1438-1500`); before v0.30.6 the latter held the connection mutex indefinitely. (3) All dispatch replies on the same connection serialized behind the same send mutex; thus an undrained resumed viewer could hold the mutex while output-writing and starve `KIND_NET_STATUS_REPLY`/`KIND_SESSIONS_REPLY`, preventing the brain from reading, which perpetuated the writer block—a closed causal cycle documented at `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:31-85` and tested at `tests/brain_resume_conn_deadlock.rs:4-24`. (4) Resume in v0.30.4 called `resume_sessions`: query sessions, seed cursor, subscribe each as Viewer (`brain.rs:1029-1058`). The supervised brain does not continuously drain PTY output, so this multiplexed unsolicited output and request/reply onto one connection. v0.30.5 uses `resume_session_cursors` and sends no subscribe (`brain.rs:1061-1085`). (5) Inject delivery is serialized per translation-bearing session: one `event_rx` loop, up to two attempts, a 400 ms settle probe cadence, 1.2 s echo-verify window when declared, and a 5 s translation commit deadline (`broker.rs:353-445,1960-2145,2160-2285`). `SPT_INJECT_VERIFY_ECHO` is non-default and enabled on the live daemon, so it can increase PTY traffic and worker occupancy and may help satisfy the load trigger, but it sleeps on the dedicated inject worker with no output-log/floor lock held; output-log reads are short snapshots, PTY writes enqueue through bounded non-blocking `InputWriter::enqueue` (`broker.rs:1600-1715`), and the sole blocking PTY write is isolated on its per-session input-writer thread (`broker.rs:1717-1730`). It is therefore a confounder/load amplifier, not a code-supported global broker-freeze mechanism.\\n\\nFalsifiable hypotheses:\\n1. PHYSICAL NON-DRAINING-CONSUMER WRITE (strongest/pinned, supported): real `seq>0` output plus a connected consumer that stops reading causes a controller/viewer writer to enter `write_frame`; pre-v0.30.6 it remains alive after logical eviction. Falsifier: reproduce with an unread controller while showing the writer/handler physically retires within the configured deadline without client drop on pre-v0.30.6. Existing capture shows the opposite pre-fix, and `brain_decouple.rs:471+` is the post-fix gate.\\n2. v0.30.4 UNDRAINED RESUME SELF-DEADLOCK (supported as a concrete trigger, closed in v0.30.5): resume-as-Viewer replays active sessions onto the daemon brain's request/reply connection; a viewer writer blocks holding SharedSend; heartbeat reply needs SharedSend; heartbeat cannot return/drain. Falsifier: run `resume_sessions` on N flooding sessions while heartbeat round trips continue at the healthy floor, or prove zero viewer subscriptions after daemon resume. `brain_resume_conn_deadlock.rs:264-340` now asserts cursor-only leaves zero viewers and its seam-sensitivity arm proves `resume_sessions` does create them.\\n3. v0.30.3 REAP-DRIVE ITSELF BLOCKS ON DEAD PEER (killed): `sessions()` snapshots only session locks; `net_status()` is a local status read; `OutputLog::append` uses non-blocking `try_send`; no dead-peer operation is on this tick. `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:5-30` pins the negative code trace. Falsifier for the kill would be an exact call from either heartbeat request into a peer dial/wait or a held lock shared with it; none exists.\\n4. BROKER NET-RUNTIME STARVATION BY DEAD-PEER DIALS (killed): a 12-deep proof-hang/unreachable dial rig kept the net-runtime liveness canary at 32-33 ms versus the 15 s stall threshold and left 12/24 permits free; async awaits yield. Evidence `docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md:1-54`. Falsifier for the kill: canary age approaching the stall deadline under the same dial shapes.\\n5. INJECT SETTLE/ECHO RE-DRIVE GLOBALLY SERIALIZES PTY DATA (killed as root, retained as confounder): declared echo verification may cause settle probes, sleeps, a second typed attempt, and additional output, but operations are confined to a per-session worker; sleeps happen outside locks and PTY writes are non-blocking queue handoffs. Falsifier: thread evidence showing the inject worker sleeps while holding `OutputLog`, the inject floor, global broker dispatch, or a physical connection gate. Current code shows none. It can still increase output pressure and should be disabled in comparative field trials because the daemon has non-default `SPT_INJECT_VERIFY_ECHO` enabled.\\n\\nConclusion: code plus the saved physical reproduction pins the durable broker-side root to unbounded/cancel-incapable physical connection writes, not merely to the v0.30.3/v0.30.4 resume policy. v0.30.3 and v0.30.4 supplied/widened reproducible triggers; v0.30.5 removed the daemon-resume trigger; only v0.30.6 closes the general failure class for any stuck controller/viewer/reply consumer. Code alone cannot name why the original field client stopped reading; the root report explicitly records that historical process identity as unproven.\",\"files\":[{\"path\":\"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md\",\"description\":\"Authoritative physical RCA and Windows reproduction: four immediate-lock writes parked inside frame I/O ~127.953 s; logical eviction did not cancel/retire them; exact old-code anchors and rejected causes.\"},{\"path\":\"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs\",\"description\":\"Instrumented pre-v0.30.6 broker. Lines 73-77 define raw SharedSend; 1048-1108 install controller writer; 1664-1760 retain SendHalf mutex across write_frame on initial/live paths.\"},{\"path\":\"spt-core/crates/spt-daemon/src/codec.rs\",\"description\":\"Lines 21-35 implement synchronous length-prefixed frame output using write_all(prefix), write_all(body), flush.\"},{\"path\":\"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md\",\"description\":\"Exact v0.30.4 self-deadlock causal chain and code proof that v0.30.3 reap-drive/dead-peer tick is not itself blocking; explains Viewer fix did not remove subscription.\"},{\"path\":\"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\",\"description\":\"Empirical kill of net-runtime starvation: 12 in-flight dead-peer dials, 32-33 ms canary age, permits available.\"},{\"path\":\"spt-core/crates/spt-daemon/src/brainproc.rs\",\"description\":\"Lines 181-208 current v0.30.5 cursor-only resume; lines 253-287 v0.30.3 heartbeat sessions reap-drive serialized after net_status.\"},{\"path\":\"spt-core/crates/spt-daemon/src/brain.rs\",\"description\":\"Lines 1029-1058 draining-only subscribe-as-Viewer resume and deadlock warning; 1061-1085 cursor-only safe resume; 1585-1610 explains local Control resume steals/freezes.\"},{\"path\":\"spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs\",\"description\":\"Dead-peer-independent round-3 regression proof and deterministic zero-viewer/seam-sensitivity guard.\"},{\"path\":\"spt-core/crates/spt-daemon/src/conn.rs\",\"description\":\"v0.30.6 bounded connection primitive. Lines 1-59 state failure class; 304-465 implement absolute bounded gate+write, watchdog cancellation, poisoning, severing, and retirement.\"},{\"path\":\"spt-core/crates/spt-daemon/tests/brain_decouple.rs\",\"description\":\"Lines 384-474 document exact pre-fix Windows physical-retirement failure and post-fix non-draining-controller gate.\"},{\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"description\":\"Current active paths: output non-blocking fan-out 715-858; writers 1330-1500; InputWriter isolation 1600-1730; settle/echo/re-drive 1960-2285.\"},{\"path\":\"spt-core/.git/logs/refs/heads/update-wedge-drain-drive\",\"description\":\"Commit evidence for v0.30.3-era ce053dc reap-drive change.\"},{\"path\":\"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer\",\"description\":\"Commit evidence for fb499db resume-as-Viewer and 03c7109 cursor-only round-3 fix, plus diagnostic verdict commits.\"},{\"path\":\"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write\",\"description\":\"Commit evidence for d634b67 v0.30.6 bounded+cancelable+poison-on-failure writes and its RED-first physical gate.\"},{\"path\":\"spt-core/.git/refs/tags/v0.30.2\",\"description\":\"Tag ref b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa.\"},{\"path\":\"spt-core/.git/refs/tags/v0.30.3\",\"description\":\"Tag ref 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047.\"},{\"path\":\"spt-core/.git/refs/tags/v0.30.4\",\"description\":\"Tag ref ade41be9d865b43f92a9c9017507734d8a9edd55.\"},{\"path\":\"spt-core/.git/refs/tags/v0.30.5\",\"description\":\"Tag ref 8e5ff889e0a4fa9a907801dc861904608cbc8e65.\"},{\"path\":\"spt-core/.git/refs/tags/v0.30.6\",\"description\":\"Tag ref d73e19e12f7192a08d6ee502d21d03809a57a921.\"}],\"architecture\":\"Active terminal output flows PTY child → one per-session drain thread → short `Mutex<OutputLog>` append/ring update → non-blocking bounded queue handoffs to isolated controller/viewer writers → serialized physical broker connection → attach server/net stream → `spt rc`. Input flows controller/inject → per-session `InjectFloor` ordering → bounded non-blocking `InputWriter` queue → sole blocking PTY input writer. A brain swap preserves broker/PTYs; the replacement brain queries broker-held session cursors. In v0.30.4 it subscribed those sessions as Viewers on the same connection used for heartbeat request/reply, although that brain did not continuously drain output. The resulting unsolicited-output writer and dispatch reply writer shared one `Arc<Mutex<SendHalf>>`; if output filled the pipe, the former held the mutex inside OS I/O and permanently prevented the reply that would let the brain drain—self-deadlock. v0.30.5 separates cursor reconstruction from actual subscription for the non-draining supervised brain. v0.30.6 changes the lower connection layer itself: every writer acquires a deadline-bounded gate, writes outside locks with a watchdog, and on timeout poisons/aborts/severs the whole physical connection so existing EOF cleanup removes roles. This is the general fix because it also covers a stuck human controller unrelated to resume.\"}}}","call_id":"call_aOCvU1aF4kRSO1ajO9BYsW7M","name":"yield"}]},"duration":66445.29550000001,"ttft":1517.5815999996848,"contextSnapshot":{"promptTokens":161358,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_aOCvU1aF4kRSO1ajO9BYsW7M|fc_08496348794f7e09016a51917bb45481989b2acd305ffdb2f5","toolName":"yield","startedAt":"2026-07-11T00:43:38.405Z"},"id":"7fee893e","parentId":"16f1c570","timestamp":"2026-07-11T00:43:38.405Z"}
{"type":"message","id":"7a6f1d29","parentId":"7fee893e","timestamp":"2026-07-11T00:43:38.405Z","message":{"role":"toolResult","toolCallId":"call_aOCvU1aF4kRSO1ajO9BYsW7M|fc_08496348794f7e09016a51917bb45481989b2acd305ffdb2f5","toolName":"yield","content":[{"type":"text","text":"Result submitted."}],"details":{"data":{"summary":"Strongest pinned root candidate: a pre-v0.30.6 broker connection write could block indefinitely after a controller/brain consumer stopped reading. The physical mechanism is pinned, reproduced, and source-localized; only the historical identity/reason of the field consumer that stopped draining is not recoverable from code alone. Before round 4, `SharedSend` was `Arc<Mutex<SendHalf>>`; controller/viewer/reply writers acquired that mutex and retained it across synchronous length-prefixed `write_frame` (`write_all` prefix, body, then `flush`). On Windows/interprocess 2.4.2 the underlying named-pipe write waits through `WriteFileEx` + `SleepEx(INFINITE)` and exposes no usable write timeout. Logical `stall_evict_controller` removed the controller role but did not cancel the in-flight write, close the physical connection, release the writer's stack-owned mutex guard, or join the writer. A real black-hole reproduction observed four writers acquire immediately (`wait_us=0`) and then remain inside `write_frame` for ~127.953 s, returning only when a later restart closed their connections; this kills the competing mutex-convoy and scheduler-starvation explanations. Exact evidence: `.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md:1-97`; old implementation `.worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760`; framing `crates/spt-daemon/src/codec.rs:21-35`; current regression narrative/test `crates/spt-daemon/tests/brain_decouple.rs:384-474`.\n\nVersion/commit chain: v0.30.2 tag ref `b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa`; v0.30.3 `0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047`; v0.30.4 `ade41be9d865b43f92a9c9017507734d8a9edd55`; v0.30.5 `8e5ff889e0a4fa9a907801dc861904608cbc8e65`; v0.30.6 `d73e19e12f7192a08d6ee502d21d03809a57a921` (`.git/refs/tags/v0.30.2` through `v0.30.6`). The v0.30.3 window includes `ce053dc930104afda280504b7e489f27462d47bb`, “brain self-drives the controller reap”; it added a second serialized request/reply round trip, `brain.sessions()`, every 500 ms (`brainproc.rs:253-287`). It did not itself block on peers, but made heartbeat progress depend on the already-contended brain connection. v0.30.4 fix `fb499dbfc29521daab72cb49e1768aeb5f3c74bc` changed cold resume from local Controller to Viewer, preventing control steal but retaining an output subscription on the same non-draining request/reply connection. v0.30.5 fix `03c71093ca70e76a86d42087e753e3cca0793095` made the supervised daemon resume cursor-only, eliminating that specific undrained-subscription self-deadlock (`brainproc.rs:181-208`; `brain.rs:1029-1085`). v0.30.6 round-4 fix `d634b67b2962ab919d0a03a280d63695e48214b3` replaced every raw physical broker-connection send with bounded, cancelable, poison-on-failure `BrokerConn` writes; branch log explicitly names the change, and `conn.rs:1-59,304-465` shows the absolute deadline covering gate wait plus OS completion, out-of-band CancelIoEx/shutdown, connection poison/sever, and no reuse.\n\nCode-path operation map for v0.30.3-v0.30.6: (1) PTY output drain itself appends/rings and performs only `try_send` fan-out under `Mutex<OutputLog>` in the modern/pre-round-4 architecture (`broker.rs:715-858`); this is non-blocking and therefore not the terminal freeze root. (2) Per-controller and per-viewer dedicated writer threads block on `rx.recv()` while idle and blocked synchronously on physical socket writes while active (`broker.rs:1330-1357,1438-1500`); before v0.30.6 the latter held the connection mutex indefinitely. (3) All dispatch replies on the same connection serialized behind the same send mutex; thus an undrained resumed viewer could hold the mutex while output-writing and starve `KIND_NET_STATUS_REPLY`/`KIND_SESSIONS_REPLY`, preventing the brain from reading, which perpetuated the writer block—a closed causal cycle documented at `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:31-85` and tested at `tests/brain_resume_conn_deadlock.rs:4-24`. (4) Resume in v0.30.4 called `resume_sessions`: query sessions, seed cursor, subscribe each as Viewer (`brain.rs:1029-1058`). The supervised brain does not continuously drain PTY output, so this multiplexed unsolicited output and request/reply onto one connection. v0.30.5 uses `resume_session_cursors` and sends no subscribe (`brain.rs:1061-1085`). (5) Inject delivery is serialized per translation-bearing session: one `event_rx` loop, up to two attempts, a 400 ms settle probe cadence, 1.2 s echo-verify window when declared, and a 5 s translation commit deadline (`broker.rs:353-445,1960-2145,2160-2285`). `SPT_INJECT_VERIFY_ECHO` is non-default and enabled on the live daemon, so it can increase PTY traffic and worker occupancy and may help satisfy the load trigger, but it sleeps on the dedicated inject worker with no output-log/floor lock held; output-log reads are short snapshots, PTY writes enqueue through bounded non-blocking `InputWriter::enqueue` (`broker.rs:1600-1715`), and the sole blocking PTY write is isolated on its per-session input-writer thread (`broker.rs:1717-1730`). It is therefore a confounder/load amplifier, not a code-supported global broker-freeze mechanism.\n\nFalsifiable hypotheses:\n1. PHYSICAL NON-DRAINING-CONSUMER WRITE (strongest/pinned, supported): real `seq>0` output plus a connected consumer that stops reading causes a controller/viewer writer to enter `write_frame`; pre-v0.30.6 it remains alive after logical eviction. Falsifier: reproduce with an unread controller while showing the writer/handler physically retires within the configured deadline without client drop on pre-v0.30.6. Existing capture shows the opposite pre-fix, and `brain_decouple.rs:471+` is the post-fix gate.\n2. v0.30.4 UNDRAINED RESUME SELF-DEADLOCK (supported as a concrete trigger, closed in v0.30.5): resume-as-Viewer replays active sessions onto the daemon brain's request/reply connection; a viewer writer blocks holding SharedSend; heartbeat reply needs SharedSend; heartbeat cannot return/drain. Falsifier: run `resume_sessions` on N flooding sessions while heartbeat round trips continue at the healthy floor, or prove zero viewer subscriptions after daemon resume. `brain_resume_conn_deadlock.rs:264-340` now asserts cursor-only leaves zero viewers and its seam-sensitivity arm proves `resume_sessions` does create them.\n3. v0.30.3 REAP-DRIVE ITSELF BLOCKS ON DEAD PEER (killed): `sessions()` snapshots only session locks; `net_status()` is a local status read; `OutputLog::append` uses non-blocking `try_send`; no dead-peer operation is on this tick. `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:5-30` pins the negative code trace. Falsifier for the kill would be an exact call from either heartbeat request into a peer dial/wait or a held lock shared with it; none exists.\n4. BROKER NET-RUNTIME STARVATION BY DEAD-PEER DIALS (killed): a 12-deep proof-hang/unreachable dial rig kept the net-runtime liveness canary at 32-33 ms versus the 15 s stall threshold and left 12/24 permits free; async awaits yield. Evidence `docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md:1-54`. Falsifier for the kill: canary age approaching the stall deadline under the same dial shapes.\n5. INJECT SETTLE/ECHO RE-DRIVE GLOBALLY SERIALIZES PTY DATA (killed as root, retained as confounder): declared echo verification may cause settle probes, sleeps, a second typed attempt, and additional output, but operations are confined to a per-session worker; sleeps happen outside locks and PTY writes are non-blocking queue handoffs. Falsifier: thread evidence showing the inject worker sleeps while holding `OutputLog`, the inject floor, global broker dispatch, or a physical connection gate. Current code shows none. It can still increase output pressure and should be disabled in comparative field trials because the daemon has non-default `SPT_INJECT_VERIFY_ECHO` enabled.\n\nConclusion: code plus the saved physical reproduction pins the durable broker-side root to unbounded/cancel-incapable physical connection writes, not merely to the v0.30.3/v0.30.4 resume policy. v0.30.3 and v0.30.4 supplied/widened reproducible triggers; v0.30.5 removed the daemon-resume trigger; only v0.30.6 closes the general failure class for any stuck controller/viewer/reply consumer. Code alone cannot name why the original field client stopped reading; the root report explicitly records that historical process identity as unproven.","files":[{"path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","description":"Authoritative physical RCA and Windows reproduction: four immediate-lock writes parked inside frame I/O ~127.953 s; logical eviction did not cancel/retire them; exact old-code anchors and rejected causes."},{"path":"spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs","description":"Instrumented pre-v0.30.6 broker. Lines 73-77 define raw SharedSend; 1048-1108 install controller writer; 1664-1760 retain SendHalf mutex across write_frame on initial/live paths."},{"path":"spt-core/crates/spt-daemon/src/codec.rs","description":"Lines 21-35 implement synchronous length-prefixed frame output using write_all(prefix), write_all(body), flush."},{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","description":"Exact v0.30.4 self-deadlock causal chain and code proof that v0.30.3 reap-drive/dead-peer tick is not itself blocking; explains Viewer fix did not remove subscription."},{"path":"spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","description":"Empirical kill of net-runtime starvation: 12 in-flight dead-peer dials, 32-33 ms canary age, permits available."},{"path":"spt-core/crates/spt-daemon/src/brainproc.rs","description":"Lines 181-208 current v0.30.5 cursor-only resume; lines 253-287 v0.30.3 heartbeat sessions reap-drive serialized after net_status."},{"path":"spt-core/crates/spt-daemon/src/brain.rs","description":"Lines 1029-1058 draining-only subscribe-as-Viewer resume and deadlock warning; 1061-1085 cursor-only safe resume; 1585-1610 explains local Control resume steals/freezes."},{"path":"spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","description":"Dead-peer-independent round-3 regression proof and deterministic zero-viewer/seam-sensitivity guard."},{"path":"spt-core/crates/spt-daemon/src/conn.rs","description":"v0.30.6 bounded connection primitive. Lines 1-59 state failure class; 304-465 implement absolute bounded gate+write, watchdog cancellation, poisoning, severing, and retirement."},{"path":"spt-core/crates/spt-daemon/tests/brain_decouple.rs","description":"Lines 384-474 document exact pre-fix Windows physical-retirement failure and post-fix non-draining-controller gate."},{"path":"spt-core/crates/spt-daemon/src/broker.rs","description":"Current active paths: output non-blocking fan-out 715-858; writers 1330-1500; InputWriter isolation 1600-1730; settle/echo/re-drive 1960-2285."},{"path":"spt-core/.git/logs/refs/heads/update-wedge-drain-drive","description":"Commit evidence for v0.30.3-era ce053dc reap-drive change."},{"path":"spt-core/.git/logs/refs/heads/uwedge2-resume-viewer","description":"Commit evidence for fb499db resume-as-Viewer and 03c7109 cursor-only round-3 fix, plus diagnostic verdict commits."},{"path":"spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write","description":"Commit evidence for d634b67 v0.30.6 bounded+cancelable+poison-on-failure writes and its RED-first physical gate."},{"path":"spt-core/.git/refs/tags/v0.30.2","description":"Tag ref b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa."},{"path":"spt-core/.git/refs/tags/v0.30.3","description":"Tag ref 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047."},{"path":"spt-core/.git/refs/tags/v0.30.4","description":"Tag ref ade41be9d865b43f92a9c9017507734d8a9edd55."},{"path":"spt-core/.git/refs/tags/v0.30.5","description":"Tag ref 8e5ff889e0a4fa9a907801dc861904608cbc8e65."},{"path":"spt-core/.git/refs/tags/v0.30.6","description":"Tag ref d73e19e12f7192a08d6ee502d21d03809a57a921."}],"architecture":"Active terminal output flows PTY child → one per-session drain thread → short `Mutex<OutputLog>` append/ring update → non-blocking bounded queue handoffs to isolated controller/viewer writers → serialized physical broker connection → attach server/net stream → `spt rc`. Input flows controller/inject → per-session `InjectFloor` ordering → bounded non-blocking `InputWriter` queue → sole blocking PTY input writer. A brain swap preserves broker/PTYs; the replacement brain queries broker-held session cursors. In v0.30.4 it subscribed those sessions as Viewers on the same connection used for heartbeat request/reply, although that brain did not continuously drain output. The resulting unsolicited-output writer and dispatch reply writer shared one `Arc<Mutex<SendHalf>>`; if output filled the pipe, the former held the mutex inside OS I/O and permanently prevented the reply that would let the brain drain—self-deadlock. v0.30.5 separates cursor reconstruction from actual subscription for the non-draining supervised brain. v0.30.6 changes the lower connection layer itself: every writer acquires a deadline-bounded gate, writes outside locks with a watchdog, and on timeout poisons/aborts/severs the whole physical connection so existing EOF cleanup removes roles. This is the general fix because it also covers a stuck human controller unrelated to resume."},"status":"success","type":"result"},"isError":false,"timestamp":1783730618405}}
{"type":"message","id":"2f3bb45d","parentId":"7a6f1d29","timestamp":"2026-07-11T00:43:38.464Z","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":1783730618461}}
{"type":"message","id":"e7ec0a33","parentId":"2f3bb45d","timestamp":"2026-07-11T00:43:38.467Z","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\nYour pinned root explains pre-v0.30.6 and should be fixed by d634b67. For the residual v0.30.6 operator report, can any current code path still cause multi-minute per-terminal freezes? Explicitly separate proven historical root from residual root; cite exact current lines or conclude unpinned pending timestamp/repro.","attribution":"agent","timestamp":1783730566366,"steering":true}}
{"type":"message","id":"f0badab5","parentId":"e7ec0a33","timestamp":"2026-07-11T00:43:52.638Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Investigating residual PTY path delays**\n\n<!-- -->\n\n**Analyzing sequential write timeouts**\n\n<!-- -->"},{"type":"toolCall","id":"call_2EXs4p0yPvENjfCVu4GpMTcn|fc_0a129b8eba4c900a016a5191c92f94819abd256750fc76510b","name":"read","arguments":{"i":"Inspect controller resume loop","path":"spt-core/crates/spt-daemon/src/attach.rs","selector":"95-215,430-535"}},{"type":"toolCall","id":"call_s0Jhd9EK5EtdG0Z0SXj8dDlj|fc_0a129b8eba4c900a016a5191c92fa8819a8815d3073cea86a1","name":"grep","arguments":{"i":"Find long current deadlines","pattern":"Duration::from_(secs|millis)\\((120|180|240|300|600|[6-9][0-9])|RECONNECT|deadline|recv_timeout|sleep","path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_a491UBocRpLBMuLsVaiAW1O6|fc_0a129b8eba4c900a016a5191c92fb0819a833aed11304f7990","name":"read","arguments":{"i":"Inspect rc reconnect state","path":"spt-core/crates/spt/src/rc.rs","selector":"1360-1640,1800-1960"}},{"type":"toolCall","id":"call_mxUGixexfApp1TQoaRYGPuwR|fc_0a129b8eba4c900a016a5191c92fb4819abb1f4abea9e3fa41","name":"grep","arguments":{"i":"Find unbounded writes current","pattern":"write_frame\\(|write_all\\(|write_input\\(|\\.send\\(|\\.recv\\(|\\.lock\\(","path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4864,"output":612,"cacheRead":160256,"cacheWrite":0,"totalTokens":165732,"reasoningTokens":234,"cost":{"input":0.02432,"output":0.01836,"cacheRead":0.08012799999999999,"cacheWrite":0,"total":0.122808}},"stopReason":"toolUse","timestamp":1783730619559,"responseId":"resp_0a129b8eba4c900a016a5191bc68c0819a91bc7d2351ac1f87","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a129b8eba4c900a016a5191bdf370819ab65afa8b200b3dd1","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZHJE1a9n8FbL50LtvRYrvrQZ9tzwu6DRAjTw6KpWzu8UxwtNDqddqwx_51lMNI_rlltNx_kxZNz5WEDPfvO9eNr6PH5n9ClucB4ZUmDMf7nC5DgMY53PgJhPPivUUX2LxswM3UJTBwj4bh3ArMYOiiyOhP2CTSKGN6Iz7gDYDjqNLskQiu1BQrkNCQcPF_P1p7Y5W2aqgCTRoXu8BbJ3qiSnS0ufx7noq5qGHfqseoYwXUs4USFDt8skQrYoCCvO_JtohVUqDFnU4yw4BRD35B5Ark7O1kFTRBoww_C0W7ERYIjkCl9frpxvR0d4boGGsSWWcBfRXY36I1FebJ5tpqplJyANM1W34woroQ3CTuzrTBYv1DLsOEKjTOIv2u4om2vK40BJldjlpA_5mK9Btif2fpgtSBM-6lj7afeUEl6ZG_9UWb_e8tatMCKEJnTqN5LNQKLwVA7vWuTGSfRPOC7jwknyyat7_LPcqRiZy6HuA-oCNSupRyIs3YWDwVuVNDiR4KF-9oj_whhV1RpniLlQ2TrdlNnqoQEMKDQrvgRZPtCZLbd10L30t-l2aDJ6rXzQs8WzdieFI29WjIvwHjS2EC4t-2YktZSa8No9gFhfd87EsGXyDyppGB8uSii3IxEDnUffbJm8gcFKj4tNxSArztb15jwF3m6MTT8Ep1OTBxq96bUOPi-yHVe9aB-x_4H1j4RAiIjtlSwccY-_TV50HPMeiC210m53FNMTO7NPp9XdGaLjrxmUmJv0QBYgaEIEBjr_UlHAiFDu9zzuMrQao1Nx873xNJiwQAzMCl1bmDO5yNalBMls_faUs7OrtlKx0ypO0wrPTXyNKebaMTdjM2LKgNiNjxx2I01fha0FOYLxLW19zMngBwyG5zd_N7PtrtxWCHzQ1QRcqbGH5h3Yu4agQgNNsZvaqwOVrTi8mklWa9w6hngultTNMcqOuBXig1sPAm6ZhSTJtmzO45YFgmroxkp0unIyalCaSHVjVAy3_zfggVwBb7crCKHWkLjp2shnonJwilFkvq9FRrH15EsQJNtORK9TpgghMJQd0R2FSOTT_R-A-6f7E10lYflxaPiQ3XQnPuyXLcrpbqE-Je8SPrxBPp4keoKigrYcnl4hMQFoahur0DKB_FDHBJkiC4z6IeKIa8L23KphiRIVobC37PhooOIfU2HJjZ97K-frgRtEuoXIA2iC3UrKM9cPYVpJXgPNwd334mFWrb3LlMrdWgayXdixYSn5wyPHPn2osMsEElBUqetFOGbkC127-0ueD1VSjSU533jwMy9ZCwU1IO0Db3ZMcbQb9TuvLiSWKTLJZA4ig-LmStggpy5p93wrOh6WZXDZZtEtg1EpaeCel5SM6j1SLzIRi3S1rufPShRgDcRearqULZxJFaYMnhg3jMVpxLD_4sE5YSl8eBoHtTqeiOPB06m1My-RqBN3xP5FxhbZgUVNfK5Yx_L_fp58ihyHlzjDi6oEWBbx9dFYqxumMw9LwGPV_sstwNL4qnLJ2nqzhyzH-zupL5U9GCjTiz0PKIv1G_rQxpEjk-fu8DrdOjhq4pjCnzIfAMFHt8pcaFO92OkapkwMbmHxYJK3JIZec5M6kW9L2NZGeIbkInXJn6nzEmLzOo2rBBEpxaVYca1wwtq06D7smNDrGb-tCQceANG7kRJcVvrNqGt6ZEvCndv4pj7v8mV2VzyQxtwcokU036bvBhJ8R-vveXqyiUEHvOTMQTzbRVe7HcyUhXqAfBEaDyskn5wIKeSNHzcp90yyQD6ZEosVx1tm6fiXVnNS3-n_IGMD02iG4iOR6bReK7x2rlTVzjf3JqGHbdi34_bIuBRSw6VcEBVtQLgbjQN5gsXEBrPFNZd1P-V7a832rkSRH-mFNmj4CXfsKvy59P9ttGe8f-0FCN5j0tvbvIgb3y6XvTIIDU2JBUnijrrdrnrPRUzImPrYz4ZEkoOQzWs0vTzPaeCWpm05Zz3GPugc_yunWGJOxDhywAq2woKZwz32NNx9q2XcV2_gsZB-u73j5Ht4xP93N044yQ07XjuPMp7b6PGBV_sK-pMYVWVBj46jrkKWHTn47FZKBu2cDrT6N3zmId22oGAdoFKZ506a8FhTcb21RrJlzZFtT4cCImI2zM4I5lSaZZoRkhHBTBdccoGJt5dVA_I0JLynTsRZWN8CjTS6I42zg_r9kh0kwjy_zq5JLIBg9KZ6DWTSvzE3fihR8Jr53P5OUDAASAoNqzhyerVAX_h0QECkZ6Zg4rIsK1K2sEHQNPwiSSQ02bSH8bhzeRXrgQ2Q9VMVlUALfTaI0vmp25IqPwQMDJj45bedd4Hwb4A_Gz3UjpnHS-zYG1dmJE-_5quL2AYtsy_UDYlkFPO9-jIK4f0aXg7MlunCjBUpOidFiYeF9YX-277pi0ErRHOKWW_gPurmwkcU7ghOsb9fCiVEXJ21Ov85mG_pNk3kK8qFplP56QBpXdr8dMXMgOkC5tbQz-5G1jOkqPTU0aeqVTU4A==","summary":[{"type":"summary_text","text":"**Investigating residual PTY path delays**\n\n<!-- -->"},{"type":"summary_text","text":"**Analyzing sequential write timeouts**\n\n<!-- -->"}]},{"id":"fc_0a129b8eba4c900a016a5191c92f94819abd256750fc76510b","type":"function_call","status":"completed","arguments":"{\"i\":\"Inspect controller resume loop\",\"path\":\"spt-core/crates/spt-daemon/src/attach.rs\",\"selector\":\"95-215,430-535\"}","call_id":"call_2EXs4p0yPvENjfCVu4GpMTcn","name":"read"},{"id":"fc_0a129b8eba4c900a016a5191c92fa8819a8815d3073cea86a1","type":"function_call","status":"completed","arguments":"{\"i\":\"Find long current deadlines\",\"pattern\":\"Duration::from_(secs|millis)\\\\((120|180|240|300|600|[6-9][0-9])|RECONNECT|deadline|recv_timeout|sleep\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_s0Jhd9EK5EtdG0Z0SXj8dDlj","name":"grep"},{"id":"fc_0a129b8eba4c900a016a5191c92fb0819a833aed11304f7990","type":"function_call","status":"completed","arguments":"{\"i\":\"Inspect rc reconnect state\",\"path\":\"spt-core/crates/spt/src/rc.rs\",\"selector\":\"1360-1640,1800-1960\"}","call_id":"call_a491UBocRpLBMuLsVaiAW1O6","name":"read"},{"id":"fc_0a129b8eba4c900a016a5191c92fb4819abb1f4abea9e3fa41","type":"function_call","status":"completed","arguments":"{\"i\":\"Find unbounded writes current\",\"pattern\":\"write_frame\\\\(|write_all\\\\(|write_input\\\\(|\\\\.send\\\\(|\\\\.recv\\\\(|\\\\.lock\\\\(\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_mxUGixexfApp1TQoaRYGPuwR","name":"grep"}]},"duration":13062.840799999889,"ttft":1917.753200000152,"contextSnapshot":{"promptTokens":165120,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_2EXs4p0yPvENjfCVu4GpMTcn|fc_0a129b8eba4c900a016a5191c92f94819abd256750fc76510b","toolName":"read","startedAt":"2026-07-11T00:43:52.638Z","args":{"path":"spt-core/crates/spt-daemon/src/attach.rs"},"intent":"Inspect controller resume loop"},"id":"b6b5048e","parentId":"f0badab5","timestamp":"2026-07-11T00:43:52.638Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_s0Jhd9EK5EtdG0Z0SXj8dDlj|fc_0a129b8eba4c900a016a5191c92fa8819a8815d3073cea86a1","toolName":"grep","startedAt":"2026-07-11T00:43:52.638Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src"},"intent":"Find long current deadlines"},"id":"6aedf40e","parentId":"b6b5048e","timestamp":"2026-07-11T00:43:52.638Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_a491UBocRpLBMuLsVaiAW1O6|fc_0a129b8eba4c900a016a5191c92fb0819a833aed11304f7990","toolName":"read","startedAt":"2026-07-11T00:43:52.638Z","args":{"path":"spt-core/crates/spt/src/rc.rs"},"intent":"Inspect rc reconnect state"},"id":"143f4e2c","parentId":"6aedf40e","timestamp":"2026-07-11T00:43:52.638Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_mxUGixexfApp1TQoaRYGPuwR|fc_0a129b8eba4c900a016a5191c92fb4819abb1f4abea9e3fa41","toolName":"grep","startedAt":"2026-07-11T00:43:52.638Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src/attach.rs; spt-core/crates/spt-daemon/src/conn.rs; spt-core/crates/spt/src/rc.rs; spt-core/crates/spt-term/src"},"intent":"Find unbounded writes current"},"id":"93c54bac","parentId":"143f4e2c","timestamp":"2026-07-11T00:43:52.638Z"}
{"type":"message","id":"ffc7f3d4","parentId":"93c54bac","timestamp":"2026-07-11T00:43:52.684Z","message":{"role":"toolResult","toolCallId":"call_2EXs4p0yPvENjfCVu4GpMTcn|fc_0a129b8eba4c900a016a5191c92f94819abd256750fc76510b","toolName":"read","content":[{"type":"text","text":"\n/// Minimum spacing between skip-to-live re-subscribes after a viewer eviction\n/// (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). HARD constraint: an evicted viewer that is\n/// hopelessly behind under a sustained flood will be re-evicted immediately after\n/// each re-subscribe; without spacing that is an evict→resubscribe CPU spin. With\n/// it, the operator sees an intermittent LIVE burst each interval (forward\n/// progress), never a busy-loop. ~200ms = several bursts/sec, imperceptible lag.\nconst RESUBSCRIBE_INTERVAL: Duration = Duration::from_millis(200);\n\n/// The CONTROLLER fell behind the live ring further than the ring holds — the\n/// dropped frames are GONE (`tail - delivered_through > cap_chunks`), so an\n/// exactly-once re-fetch is IMPOSSIBLE. Distinguishes the irrecoverable edge from\n/// an ordinary fatal so the serve loop can surface it (a marked truncation),\n/// rather than silent-skipping (a B2 lie) or spinning. Full graceful handling is\n/// DEFERRED to `REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND`.\n// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n#[derive(Debug)]\npub struct ControllerIrrecoverablyBehind {\n    /// The frozen `delivered_through` floor the ring has already rolled past.\n    pub floor: u64,\n}\n\nimpl std::fmt::Display for ControllerIrrecoverablyBehind {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"controller fell irrecoverably behind the live ring at seq {} (dropped frames rolled out of the ring)\",\n            self.floor\n        )\n    }\n}\n\nimpl std::error::Error for ControllerIrrecoverablyBehind {}\n\n/// Is `e` the brain's strict reject-gap fatal (`output gap: got seq N want M`)?\n/// That is the only error the controller gap-resume recovers from; every other\n/// error propagates unchanged.\nfn is_output_gap(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"output gap\")\n}\n\n/// Read one [`BrokerEvent`] for an attached CONTROLLER, RESUMING-FROM-FLOOR on a\n/// b4 drop-don't-block output-gap instead of fataling.\n///\n/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n/// bounded channel is full (a controller that falls behind its own echo under a\n/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n/// controller CANNOT snap-above like a viewer (B2: it is authoritative — it\n/// advances `delivered_through`; skipping rolled frames would be a not-exactly-once\n/// resume). So instead it RE-FETCHES: re-subscribe from\n/// [`Brain::controller_resume_floor`] (`= delivered_through`, frozen at the drop by\n/// `contiguous_advance`) and the broker replays the dropped frames from the ring.\n///\n/// Exactly-once holds IFF the ring still retains the floor (`tail - delivered_through\n/// <= cap_chunks`). If it does not — the controller fell behind further than the\n/// ring holds — the re-subscribe's first frame is STILL a forward gap (floor\n/// unchanged across two resumes). That is [`ControllerIrrecoverablyBehind`]: the\n/// caller surfaces it (marked truncation), never silent-skips (B2 lie) and never\n/// spins. The helper swallows the `Subscribed` ack its own re-subscribe produces so\n/// the repeated-gap detection is not masked. Non-`Output` events (NetStreamData /\n/// Size / Exit / …) pass through unchanged.\n// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\npub fn read_controller_event_resuming(\n    brain: &mut Brain,\n    session_id: u64,\n    by: Option<&str>,\n) -> io::Result<BrokerEvent> {\n    let mut last_resume_floor: Option<u64> = None;\n    let mut swallow_resume_ack = false;\n    loop {\n        match brain.read_event() {\n            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n            // resolved) so it cannot mask a repeated gap as forward progress.\n            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n                swallow_resume_ack = false;\n                continue;\n            }\n            Ok(ev) => return Ok(ev),\n            Err(e) if is_output_gap(&e) => {\n                let floor = brain.controller_resume_floor();\n                if last_resume_floor == Some(floor) {\n                    // No progress across two resumes → the ring rolled past the\n                    // floor → the dropped frames are gone.\n                    return Err(io::Error::other(ControllerIrrecoverablyBehind { floor }));\n                }\n                last_resume_floor = Some(floor);\n                // RE-FETCH from the frozen delivered_through (same operator `by` →\n                // a silent re-take, no Displaced); the broker replays the dropped\n                // frames from the ring.\n                brain.attach_as(session_id, floor, AttachIntent::Control, by)?;\n                swallow_resume_ack = true;\n            }\n            Err(e) => return Err(e),\n        }\n    }\n}\n\n/// Resolve an endpoint label to THIS node's local broker session id (the spawn's\n/// endpoint label in `brain.sessions()`). The server-side half of the cross-node\n/// attach (#4, REQ-RC-CROSS-NODE-ATTACH): the owning node is authoritative for\n/// its own session table, so a remote operator — which cannot know our local\n/// session id — names the endpoint and we map it here, never trusting a wire\n/// `session_id` it could not have. `None` ⇒ no live session under that endpoint\n/// (a stale registry row routed us an attach for something gone → the caller\n/// refuses cleanly, D6).\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\npub(crate) fn resolve_local_session(brain: &mut Brain, endpoint: &str) -> Option<u64> {\n    brain.sessions().ok().and_then(|reply| {\n        reply\n            .sessions\n            .into_iter()\n            .find(|s| s.endpoint == endpoint)\n            .map(|s| s.session_id)\n    })\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…\n            BrokerEvent::Subscribed { outcome, .. } => {\n                use crate::msg::SubscribeOutcome as O;\n                match outcome {\n                    O::Controller | O::TookControl => {\n                        role = Some(ServeRole::Controller);\n                        // A driver attached — the wake edge (controller-only;\n                        // idempotent on an already-active seat).\n                        feed_rest(endpoint, crate::resting::RestEvent::Wake);\n                        // Flush any input that raced ahead of the reply (operator-\n                        // minted ops carry the `rc` tag).\n                        for (op, b) in pending_input.drain(..) {\n                            brain.send_effect(MintedOp::new(Minter::Rc, op), &b)?;\n                        }\n                    }\n                    O::Viewer => {\n                        role = Some(ServeRole::Viewer);\n                        // Wake-neutral: a viewer never wakes the endpoint, never\n                        // keeps it awake (Gap B). Discard any racing input.\n                        pending_input.clear();\n                    }\n                    O::BusyControlled { .. } => {\n                        // A different operator controls it — refuse (no PTY input\n                        // ever reached it; the buffer is discarded). The rc\n                        // renders the busy guidance client-side from driven_by.\n                        wire.net_stream_send(stream_id, &[], None, true)?;\n                        return Ok(AttachServeOutcome::Refused);\n                    }\n                }\n            }\n            // Viewer letterbox feed: the current PTY size (on attach + resize).\n            BrokerEvent::Size { rows, cols, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n                wire.net_stream_send(stream_id, &line, None, false)?;\n            }\n            // This controller was kicked by a different operator's `--take`.\n            BrokerEvent::Displaced { by, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n                wire.net_stream_send(stream_id, &line, None, true)?;\n                // No dormant edge: the endpoint stays driven by the taker (whose\n                // own serve loop already fired the wake).\n                return Ok(AttachServeOutcome::Displaced);\n            }\n\n            // This VIEWER overflowed its broker subscription queue and was evicted\n            // (it forwards slower than the drain fans out under flood). SKIP TO\n            // LIVE — re-subscribe from the current ring floor — instead of tearing\n            // the attach down: DISTINCT from a session-exit EOF. Rate-limited\n            // (RESUBSCRIBE_INTERVAL) so a hopelessly-behind viewer under sustained\n            // flood sees intermittent LIVE bursts, never an evict→resubscribe CPU\n            // spin. The next live Output carries a forward seq jump; the operator's\n            // render cursor snaps to it (tail -f reconnect). VIEWER-only → B2-safe.\n            // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n            BrokerEvent::ViewerEvicted { session_id: sid }\n                if attached && Some(sid) == attached_session =>\n            {\n                if let Some(prev) = last_resubscribe {\n                    let since = prev.elapsed();\n                    if since < RESUBSCRIBE_INTERVAL {\n                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n                    }\n                }\n                brain.attach_skip_to_live(sid, Some(origin_node))?;\n                last_resubscribe = Some(Instant::now());\n            }\n\n            // The operator closed its send side: the viewport is over.\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                wire.net_stream_send(stream_id, &[], None, true)?;\n                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n                // info.json, carrying the current driven_by through), THEN release\n                // the role over IPC so the broker's driven_by=None is the LAST\n                // write — otherwise a concurrent resting RMW restores the stale\n                // marker (the clear-race the broker-single-writer model averts).\n                if role == Some(ServeRole::Controller) {\n                    // The driver detached — the endpoint rests warm (controller-\n                    // only; a viewer leaving is wake-neutral).\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n                if let Some(sid) = attached_session {\n                    let _ = brain.detach_session(sid); // release the role (clears markers)\n                }\n                return Ok(AttachServeOutcome::Detached);\n            }\n\n            // ── Session → wire: output and exit. ────────────────────────────\n            BrokerEvent::Output { seq, bytes, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Output {\n                    seq,\n                    data_b64: encode_bytes(&bytes),\n                });\n                // Unjournaled by design (see module docs): `seq` rides inside\n                // the record, and the operator's render cursor is the dedup.\n                wire.net_stream_send(stream_id, &line, None, false)?;\n            }\n            BrokerEvent::Exit { code, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n                wire.net_stream_send(stream_id, &line, None, true)?;\n                if role == Some(ServeRole::Controller) {\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n                return Ok(AttachServeOutcome::Exited(code));\n            }\n\n            BrokerEvent::Error { message } => {\n                if role == Some(ServeRole::Controller) {\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n…\n            }"}],"details":{"displayContent":{"text":"\n/// Minimum spacing between skip-to-live re-subscribes after a viewer eviction\n/// (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). HARD constraint: an evicted viewer that is\n/// hopelessly behind under a sustained flood will be re-evicted immediately after\n/// each re-subscribe; without spacing that is an evict→resubscribe CPU spin. With\n/// it, the operator sees an intermittent LIVE burst each interval (forward\n/// progress), never a busy-loop. ~200ms = several bursts/sec, imperceptible lag.\nconst RESUBSCRIBE_INTERVAL: Duration = Duration::from_millis(200);\n\n/// The CONTROLLER fell behind the live ring further than the ring holds — the\n/// dropped frames are GONE (`tail - delivered_through > cap_chunks`), so an\n/// exactly-once re-fetch is IMPOSSIBLE. Distinguishes the irrecoverable edge from\n/// an ordinary fatal so the serve loop can surface it (a marked truncation),\n/// rather than silent-skipping (a B2 lie) or spinning. Full graceful handling is\n/// DEFERRED to `REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND`.\n// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n#[derive(Debug)]\npub struct ControllerIrrecoverablyBehind {\n    /// The frozen `delivered_through` floor the ring has already rolled past.\n    pub floor: u64,\n}\n\nimpl std::fmt::Display for ControllerIrrecoverablyBehind {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"controller fell irrecoverably behind the live ring at seq {} (dropped frames rolled out of the ring)\",\n            self.floor\n        )\n    }\n}\n\nimpl std::error::Error for ControllerIrrecoverablyBehind {}\n\n/// Is `e` the brain's strict reject-gap fatal (`output gap: got seq N want M`)?\n/// That is the only error the controller gap-resume recovers from; every other\n/// error propagates unchanged.\nfn is_output_gap(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"output gap\")\n}\n\n/// Read one [`BrokerEvent`] for an attached CONTROLLER, RESUMING-FROM-FLOOR on a\n/// b4 drop-don't-block output-gap instead of fataling.\n///\n/// b4 made the controller a non-blocking `try_send` that DROPS frames when its\n/// bounded channel is full (a controller that falls behind its own echo under a\n/// hard flood). The next frame this reader sees is then a FORWARD gap that the\n/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A\n/// controller CANNOT snap-above like a viewer (B2: it is authoritative — it\n/// advances `delivered_through`; skipping rolled frames would be a not-exactly-once\n/// resume). So instead it RE-FETCHES: re-subscribe from\n/// [`Brain::controller_resume_floor`] (`= delivered_through`, frozen at the drop by\n/// `contiguous_advance`) and the broker replays the dropped frames from the ring.\n///\n/// Exactly-once holds IFF the ring still retains the floor (`tail - delivered_through\n/// <= cap_chunks`). If it does not — the controller fell behind further than the\n/// ring holds — the re-subscribe's first frame is STILL a forward gap (floor\n/// unchanged across two resumes). That is [`ControllerIrrecoverablyBehind`]: the\n/// caller surfaces it (marked truncation), never silent-skips (B2 lie) and never\n/// spins. The helper swallows the `Subscribed` ack its own re-subscribe produces so\n/// the repeated-gap detection is not masked. Non-`Output` events (NetStreamData /\n/// Size / Exit / …) pass through unchanged.\n// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\npub fn read_controller_event_resuming(\n    brain: &mut Brain,\n    session_id: u64,\n    by: Option<&str>,\n) -> io::Result<BrokerEvent> {\n    let mut last_resume_floor: Option<u64> = None;\n    let mut swallow_resume_ack = false;\n    loop {\n        match brain.read_event() {\n            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n            // resolved) so it cannot mask a repeated gap as forward progress.\n            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n                swallow_resume_ack = false;\n                continue;\n            }\n            Ok(ev) => return Ok(ev),\n            Err(e) if is_output_gap(&e) => {\n                let floor = brain.controller_resume_floor();\n                if last_resume_floor == Some(floor) {\n                    // No progress across two resumes → the ring rolled past the\n                    // floor → the dropped frames are gone.\n                    return Err(io::Error::other(ControllerIrrecoverablyBehind { floor }));\n                }\n                last_resume_floor = Some(floor);\n                // RE-FETCH from the frozen delivered_through (same operator `by` →\n                // a silent re-take, no Displaced); the broker replays the dropped\n                // frames from the ring.\n                brain.attach_as(session_id, floor, AttachIntent::Control, by)?;\n                swallow_resume_ack = true;\n            }\n            Err(e) => return Err(e),\n        }\n    }\n}\n\n/// Resolve an endpoint label to THIS node's local broker session id (the spawn's\n/// endpoint label in `brain.sessions()`). The server-side half of the cross-node\n/// attach (#4, REQ-RC-CROSS-NODE-ATTACH): the owning node is authoritative for\n/// its own session table, so a remote operator — which cannot know our local\n/// session id — names the endpoint and we map it here, never trusting a wire\n/// `session_id` it could not have. `None` ⇒ no live session under that endpoint\n/// (a stale registry row routed us an attach for something gone → the caller\n/// refuses cleanly, D6).\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\npub(crate) fn resolve_local_session(brain: &mut Brain, endpoint: &str) -> Option<u64> {\n    brain.sessions().ok().and_then(|reply| {\n        reply\n            .sessions\n            .into_iter()\n            .find(|s| s.endpoint == endpoint)\n            .map(|s| s.session_id)\n    })\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…\n            BrokerEvent::Subscribed { outcome, .. } => {\n                use crate::msg::SubscribeOutcome as O;\n                match outcome {\n                    O::Controller | O::TookControl => {\n                        role = Some(ServeRole::Controller);\n                        // A driver attached — the wake edge (controller-only;\n                        // idempotent on an already-active seat).\n                        feed_rest(endpoint, crate::resting::RestEvent::Wake);\n                        // Flush any input that raced ahead of the reply (operator-\n                        // minted ops carry the `rc` tag).\n                        for (op, b) in pending_input.drain(..) {\n                            brain.send_effect(MintedOp::new(Minter::Rc, op), &b)?;\n                        }\n                    }\n                    O::Viewer => {\n                        role = Some(ServeRole::Viewer);\n                        // Wake-neutral: a viewer never wakes the endpoint, never\n                        // keeps it awake (Gap B). Discard any racing input.\n                        pending_input.clear();\n                    }\n                    O::BusyControlled { .. } => {\n                        // A different operator controls it — refuse (no PTY input\n                        // ever reached it; the buffer is discarded). The rc\n                        // renders the busy guidance client-side from driven_by.\n                        wire.net_stream_send(stream_id, &[], None, true)?;\n                        return Ok(AttachServeOutcome::Refused);\n                    }\n                }\n            }\n            // Viewer letterbox feed: the current PTY size (on attach + resize).\n            BrokerEvent::Size { rows, cols, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n                wire.net_stream_send(stream_id, &line, None, false)?;\n            }\n            // This controller was kicked by a different operator's `--take`.\n            BrokerEvent::Displaced { by, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n                wire.net_stream_send(stream_id, &line, None, true)?;\n                // No dormant edge: the endpoint stays driven by the taker (whose\n                // own serve loop already fired the wake).\n                return Ok(AttachServeOutcome::Displaced);\n            }\n\n            // This VIEWER overflowed its broker subscription queue and was evicted\n            // (it forwards slower than the drain fans out under flood). SKIP TO\n            // LIVE — re-subscribe from the current ring floor — instead of tearing\n            // the attach down: DISTINCT from a session-exit EOF. Rate-limited\n            // (RESUBSCRIBE_INTERVAL) so a hopelessly-behind viewer under sustained\n            // flood sees intermittent LIVE bursts, never an evict→resubscribe CPU\n            // spin. The next live Output carries a forward seq jump; the operator's\n            // render cursor snaps to it (tail -f reconnect). VIEWER-only → B2-safe.\n            // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n            BrokerEvent::ViewerEvicted { session_id: sid }\n                if attached && Some(sid) == attached_session =>\n            {\n                if let Some(prev) = last_resubscribe {\n                    let since = prev.elapsed();\n                    if since < RESUBSCRIBE_INTERVAL {\n                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n                    }\n                }\n                brain.attach_skip_to_live(sid, Some(origin_node))?;\n                last_resubscribe = Some(Instant::now());\n            }\n\n            // The operator closed its send side: the viewport is over.\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                wire.net_stream_send(stream_id, &[], None, true)?;\n                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n                // info.json, carrying the current driven_by through), THEN release\n                // the role over IPC so the broker's driven_by=None is the LAST\n                // write — otherwise a concurrent resting RMW restores the stale\n                // marker (the clear-race the broker-single-writer model averts).\n                if role == Some(ServeRole::Controller) {\n                    // The driver detached — the endpoint rests warm (controller-\n                    // only; a viewer leaving is wake-neutral).\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n                if let Some(sid) = attached_session {\n                    let _ = brain.detach_session(sid); // release the role (clears markers)\n                }\n                return Ok(AttachServeOutcome::Detached);\n            }\n\n            // ── Session → wire: output and exit. ────────────────────────────\n            BrokerEvent::Output { seq, bytes, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Output {\n                    seq,\n                    data_b64: encode_bytes(&bytes),\n                });\n                // Unjournaled by design (see module docs): `seq` rides inside\n                // the record, and the operator's render cursor is the dedup.\n                wire.net_stream_send(stream_id, &line, None, false)?;\n            }\n            BrokerEvent::Exit { code, .. } if attached => {\n                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n                wire.net_stream_send(stream_id, &line, None, true)?;\n                if role == Some(ServeRole::Controller) {\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n                return Ok(AttachServeOutcome::Exited(code));\n            }\n\n            BrokerEvent::Error { message } => {\n                if role == Some(ServeRole::Controller) {\n                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                }\n…\n            }","startLine":95,"lineNumbers":[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,null,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,524,525,526,527,528,529,530,531,532,533,534,535,null,537]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs"}}},"isError":false,"timestamp":1783730632684}}
{"type":"message","id":"ba6c725b","parentId":"ffc7f3d4","timestamp":"2026-07-11T00:43:52.761Z","message":{"role":"toolResult","toolCallId":"call_a491UBocRpLBMuLsVaiAW1O6|fc_0a129b8eba4c900a016a5191c92fb0819a833aed11304f7990","toolName":"read","content":[{"type":"text","text":"enum EstablishFail {\n…\n    /// retry (the operator may bring the daemon back), never a resurrection.\n    /// [impl->REQ-RC-RECONNECT-TRUTH]\n    DaemonDown,\n    /// A setup error (broker connect / dial / attach-open / subscribe).\n    Error(String),\n}\n\n/// Drive the full attach-establish sequence: ensure daemon → broker conn →\n/// session resolve (LOCAL first, cross-node on miss) → dial → attach-open (with\n/// the A-4b tracing retry) → stream subscribe. Factored from `run_attach_inner`\n/// so the B-3 reconnect loop can re-drive it verbatim (REQ-RC-RECONNECT).\n///\n/// Transport for a LOCAL attach: a broker-minted in-process LOOPBACK connection\n/// (M12 W1.5, B1). A QUIC self-dial is refused by iroh (\"Connecting to ourself\n/// is not supported\"), so local attach cannot ride a self-dialed conn; instead\n/// the broker wires an in-process loopback conn whose streams surface as the\n/// SAME net-stream events the cross-node QUIC path produces. From\n/// `request_attach` down this is byte-identical to a cross-node attach — the\n/// one-pump invariant lives at the transport leaf inside the broker, never here.\n/// The loopback peer's origin is this node's own id, so the target's serve gates\n/// same-node.\n///\n/// Transport for a REMOTE attach: the registry-resolved node, seed-dialed over\n/// QUIC (mirrors the wansend resolve→seed_first_addr→net_dial leg). The owning\n/// node's serve is ALREADY transport-agnostic — a QUIC-arriving Attach serves\n/// identically to loopback — so from here down the two legs converge. Honest\n/// failure on every resolve/dial miss: a clean message, never a false attach or\n/// a hang (D2/D6).\n// [impl->REQ-RC-1] [impl->REQ-RC-CROSS-NODE-ATTACH] [impl->REQ-RC-RECONNECT]\nfn establish_attach(endpoint_id: &str, intent: AttachIntent) -> Result<Established, EstablishFail> {\n    // CONNECT-ONLY (REQ-RC-RECONNECT-TRUTH): rc is an attach pump, NOT a daemon\n    // launcher. It must NEVER auto-start the broker — the historical\n    // `ensure_running()` here WMI-resurrected a daemon on every (re)connect,\n    // which fought the operator's `spt daemon stop` (the \"stop 2-4 times\" bug)\n    // and, in the reconnect loop, re-birthed the very daemon the operator was\n    // tearing down. A daemon-down probe returns `DaemonDown` so the caller exits\n    // loud (initial) or keeps its bounded retry without spawning (reconnect). The\n    // broker cold-connect below is already deadline-bounded, so a down daemon\n    // never hangs.\n    // [impl->REQ-RC-RECONNECT-TRUTH]\n    if !spt_daemon::daemon::is_running() {\n        return Err(EstablishFail::DaemonDown);\n    }\n    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n    // pipes), so the single-threaded pump interleaves stdin + broker events\n    // without wedging. The generous io_timeout bounds the setup calls (dial,\n    // sessions, attach-open ack); the pump itself passes its own short slice.\n    let mut brain = Brain::cold_start_pump(\n        &spt_daemon::endpoint::broker_socket_name(),\n        now_ms(),\n        Duration::from_secs(10),\n    )\n    .map_err(|e| EstablishFail::Error(format!(\"broker connect: {e}\")))?;\n\n    let ops = OpMinter::new();\n    // Resolve the endpoint's live session. LOCAL first (the broker's own session\n    // table); on a MISS, the cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH):\n    // resolve the OWNING node from the registry and dial it, letting THAT node\n    // resolve endpoint→its session server-side.\n    let local_session = resolve_session(&mut brain, endpoint_id)\n        .map_err(|e| EstablishFail::Error(format!(\"list sessions: {e}\")))?;\n    let mut remote_node: Option<String> = None;\n    let (conn_id, session_id, req_endpoint): (u64, u64, Option<&str>) = match local_session {\n        Some(sid) => {\n            let dialed = brain\n                .net_dial_loopback()\n                .map_err(|e| EstablishFail::Error(format!(\"loopback dial: {e}\")))?;\n            (dialed.conn_id, sid, None)\n        }\n        None => match crate::wansend::resolve_and_dial_owner(&mut brain, endpoint_id) {\n            crate::wansend::OwnerDial::Dialed { conn_id, node } => {\n                remote_node = Some(node);\n                // session_id is a placeholder the owning node ignores — it\n                // resolves the endpoint to its OWN session id server-side.\n                (conn_id, 0, Some(endpoint_id))\n            }\n            crate::wansend::OwnerDial::NotFound => {\n                return Err(EstablishFail::NoTarget(format!(\n                    \"No live session for '{endpoint_id}' — not running on this node \\\n                     or any visible subnet.\"\n                )))\n            }\n            crate::wansend::OwnerDial::Ambiguous(msg) => {\n                return Err(EstablishFail::NoTarget(msg))\n            }\n            crate::wansend::OwnerDial::Unreachable { node, detail } => {\n                return Err(EstablishFail::NoTarget(format!(\n                    \"'{endpoint_id}' is on {node}, but it is not reachable ({detail}).\"\n                )))\n            }\n        },\n    };\n    // A-4b (REQ-OPID-TRACING-RETRY): the attach stream-open is a tracing-only op.\n    // On the broker-restart no-longer-held reply, re-mint a FRESH `rc` op and open\n    // ONCE more. The first attempt uses the conn already established above (happy\n    // path byte-identical); the one retry RE-DIALS inside `run` — a broker restart\n    // drops the CONN too, not just the stream, so opening against the dead conn\n    // would surface a different error instead of self-healing. A 2nd no-longer-held\n    // surfaces the helper's F-1 public string via the map_err below (never op lingo).\n    let mut established: Option<u64> = Some(conn_id);\n    let stream_id = spt_daemon::with_tracing_retry(\n        // Operator attach open — the rc OpMinter is the counter (ADR-0034 `rc` tag).\n        || Ok(MintedOp::new(Minter::Rc, ops.next())),\n        |op| {\n            let cid = match established.take() {\n                Some(c) => c,\n                None => match local_session {\n                    // Local: the broker's in-process loopback singleton (re-mint/reuse).\n                    Some(_) => brain\n                        .net_dial_loopback()\n                        .map_err(|e| {\n                            std::io::Error::other(format!(\"loopback re-dial: {e}\"))\n                        })?\n                        .conn_id,\n                    // Remote: re-resolve + re-dial the owning node.\n                    None => match crate::wansend::resolve_and_dial_owner(&mut brain, endpoint_id) {\n                        crate::wansend::OwnerDial::Dialed { conn_id, .. } => conn_id,\n                        _ => {\n                            return Err(std::io::Error::other(format!(\n                                \"'{endpoint_id}' is no longer reachable\"\n                            )))\n                        }\n                    },\n                },\n            };\n            request_attach_endpoint(&mut brain, cid, session_id, 0, op, intent, req_endpoint)\n        },\n    )\n    .map_err(|e| EstablishFail::Error(format!(\"attach request: {e}\")))?;\n    // Subscribe our side of the stream so the target's `Output` records surface\n    // as `NetStreamData` events (the open + Request do not auto-subscribe).\n    brain\n        .net_stream_subscribe(stream_id, 0)\n        .map_err(|e| EstablishFail::Error(format!(\"stream subscribe: {e}\")))?;\n    Ok(Established {\n        brain,\n        ops,\n        stream_id,\n        remote_node,\n    })\n}\n\n/// Drive an established viewport to its FINAL end: the B-3 reconnect loop\n/// (REQ-RC-RECONNECT) — each pass is one full viewport (initial resize → pump).\n/// A severed viewport (broker bounce, remote conn drop — the reconnectable\n/// class) paints a centered reconnect banner and RE-DRIVES [`establish_attach`]\n/// inside a bounded window; every other end is FINAL and returns to the\n/// caller's parting copy. The FAULT-MATRIX row-9 promise (\"rc reconnects\") is\n/// made TRUE here, not edited down (triage F-3).\n///\n/// The SEAM (doyle rig ruling #4): everything terminal-owning stays at the\n/// interactive caller — raw-mode guard, stdin-reader spawn, StatusRow\n/// construction/teardown, the parting copy. This fn only needs an established\n/// attach + an `out` sink, so the two-host rig can drive the REAL loop headless\n/// (`out` = a capture buffer) — no behavior change for the interactive path.\n/// `remote_node` is caller-owned so the parting copy names the LAST node the\n/// viewport was attached to (a reconnect can re-resolve it).\n#[allow(clippy::too_many_arguments)]\nfn attach_viewport(\n    endpoint_id: &str,\n    intent: AttachIntent,\n    view: bool,\n    mut est: Established,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n    remote_node: &mut Option<String>,\n    out: &mut impl std::io::Write,\n) -> Result<PumpEnd, String> {\n    loop {\n        // Controller: send the INITIAL terminal size so the PTY matches from the\n        // start (REQ-RCVIEW-1). Best-effort — a failure just leaves the default\n        // size. When the status row is active the harness PTY gets rows-1 (we own\n        // row 1), never 0 (REQ-RC-IDENTITY). Re-sent per establish: the new\n        // viewport's PTY must match our CURRENT terminal, not the original one.\n        if !view {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n                let pty_rows = if status.is_some() {\n                    rows.saturating_sub(1).max(1)\n                } else {\n                    rows\n                };\n                let _ = send_attach_resize(&mut est.brain, est.stream_id, pty_rows, cols);\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…\nfn classify_read_err(kind: std::io::ErrorKind) -> ReadDisposition {\n…\n    match kind {\n        WouldBlock | TimedOut => ReadDisposition::Retry,\n        UnexpectedEof | BrokenPipe | ConnectionReset | ConnectionAborted => {\n            ReadDisposition::BrokerGone\n        }\n        _ => ReadDisposition::Fatal,\n    }\n}\n\n/// The full-duplex pump: drain stdin → `send_attach_input`; render inbound\n/// `AttachRecord::Output` → stdout; end on `Exit`, EOF, or detach.\n#[allow(clippy::too_many_arguments)]\nfn pump(\n    brain: &mut Brain,\n    stream_id: u64,\n    ops: &OpMinter,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    decoder: &mut AttachDecoder,\n    stdout: &mut impl std::io::Write,\n    controller: bool,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n) -> Result<PumpEnd, String> {\n    // Track the harness's mouse-reporting mode from its output DECSET sequences so\n    // the stdin reader knows whether to forward scroll (REQ-RC-MOUSE-FORWARD). The\n    // scanner is pump-local (single producer) and survives a sequence split across\n    // output chunks; it writes the shared `mouse_mode` atomics the reader observes.\n    let mut mouse_scanner = MouseModeScanner::default();\n    // The reserved-row re-assert scanner (REQ-RC-IDENTITY): watches the harness\n    // output for an alt-screen enter / DECSTBM reset that would destroy our\n    // scroll-region margin, so we can re-paint the status row. Pump-local, single\n    // producer, survives a trigger split across output chunks (like mouse_scanner).\n    let mut reassert_scanner = ReassertScanner::default();\n    // Reserve the top row + paint the identity banner up front (REQ-RC-IDENTITY).\n    // Controller-only (the caller passes `Some` only for a control attach).\n    if let Some(status) = status.as_deref_mut() {\n        stdout\n            .write_all(&status.assert_bytes())\n            .map_err(|e| format!(\"status assert: {e}\"))?;\n        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n    }\n    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n    // (the session outlives the viewport). Best-effort: an already-finished or\n    // exited stream send is harmless to attempt.\n    fn detach(brain: &mut Brain, stream_id: u64) -> PumpEnd {\n        let _ = brain.net_stream_send(stream_id, &[], None, true);\n        PumpEnd::Detached\n    }\n    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n    // drop any `seq` already rendered (the operator render cursor, matching the\n    // local `OutputLog` replay discipline).\n    let mut cursor = 0u64;\n    // Controller window-change tracking (REQ-RCVIEW-1): poll the terminal size\n    // each slice and forward a Resize when it changes (cross-platform without\n    // hooking SIGWINCH / the crossterm event loop, which would conflict with the\n    // raw-stdin reader). `None` until the first read.\n    let mut last_size: Option<(u16, u16)> = None;\n    // The PTY size last advertised to us as a VIEWER (the letterbox feed); shown\n    // as a one-line indicator when it differs from our own terminal.\n    let mut pty_size: Option<(u16, u16)> = None;\n    // First-event backstop (REQ-HAZARD-RC-ATTACH-FAILFAST path b): a dead/wedged\n    // attach produces nothing — bail with a clear message past FIRST_EVENT_GRACE\n    // rather than an infinite blank. Any broker event clears it.\n    let started = Instant::now();\n    let mut seen_any = false;\n    // Did the serve ever hand us a rendered Output frame? A serve-closed EOF with\n    // NOTHING rendered means the attach was REFUSED (no live session / gate deny) —\n    // the cross-node D6 stale-row case — not a live viewport the operator detached.\n    // Any live session replays its buffered ring on attach, so a real attach sets\n    // this before the serve would ever finish the stream. (#4, REQ-RC-CROSS-NODE-ATTACH)\n    let mut rendered_any = false;\n    loop {\n        // ── controller window-change → resize ──────────────────────────────\n        if controller {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n                if last_size != Some((rows, cols)) {\n                    last_size = Some((rows, cols));\n                    match status.as_deref_mut() {\n                        // Status active: the harness PTY owns rows-1 (we keep row 1\n                        // for the banner), and we re-assert the margin + repaint on\n                        // the new size (REQ-RC-IDENTITY). Never resize the PTY to 0.\n                        Some(status) => {\n                            status.update_size(rows, cols);\n                            let pty_rows = rows.saturating_sub(1).max(1);\n                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n                                let _ = stdout.flush();\n                            }\n                        }\n                        None => {\n                            let _ = send_attach_resize(brain, stream_id, rows, cols);\n                        }\n                    }\n                }\n            }\n        }\n\n        // ── stdin → session ────────────────────────────────────────────────\n        if let Some(rx) = rx {\n            loop {\n                match rx.try_recv() {\n                    Ok(StdinMsg::Bytes(bytes)) => {\n                        send_attach_input(brain, stream_id, &bytes, ops.next())\n                            .map_err(|e| format!(\"send input: {e}\"))?;\n                    }\n                    Ok(StdinMsg::Detach) => return Ok(detach(brain, stream_id)),\n                    Err(mpsc::TryRecvError::Empty) => break,\n                    Err(mpsc::TryRecvError::Disconnected) => {\n                        return Ok(detach(brain, stream_id))\n                    }\n                }\n            }\n        }\n\n        // ── session → terminal ─────────────────────────────────────────────\n        let ev = match brain.read_event_until(Some(Instant::now() + POLL_SLICE)) {\n            Ok(ev) => ev,\n            Err(e) => match classify_read_err(e.kind()) {\n                // A poll-slice timeout: no event this slice. Before looping, the\n                // first-event backstop — if NOTHING has ever arrived past the\n                // generous grace, the attach is dead/wedged (path b).\n                ReadDisposition::Retry => {\n                    if first_event_stalled(seen_any, started.elapsed()) {\n                        return Ok(PumpEnd::Stalled);\n                    }\n                    continue;\n                }\n                // A severed broker stream (broker bounce / hang-up) — surface it\n                // gracefully, never as a raw RC_FAIL crash (REQ-HAZARD-RC-EOF).\n                ReadDisposition::BrokerGone => return Ok(PumpEnd::BrokerGone),\n                ReadDisposition::Fatal => return Err(format!(\"read event: {e}\")),\n            },\n        };\n        seen_any = true; // any broker event proves the attach stream is live\n        match ev {\n            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Output { seq, data_b64 } => {\n                            if seq < cursor {\n                                continue; // already rendered — re-serve dedup\n                            }\n                            cursor = seq + 1;\n                            rendered_any = true;\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| format!(\"decode output: {e}\"))?;\n                            // Track the harness's mouse-reporting mode from its output\n                            // (REQ-RC-MOUSE-FORWARD) before rendering it verbatim.\n                            mouse_scanner.feed(mouse_mode, &bytes);\n                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n                            // Re-assert the reserved row if the harness output just\n                            // destroyed our scroll region — alt-screen enter or a\n                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n                            // margin is gone (alt-screen has its own full-screen\n                            // margins; a DECSTBM reset clears ours), so re-set the\n                            // MARGIN + repaint (assert_bytes), not just the text —\n                            // else the harness scrolls back over row 1. Scanned on the\n                            // SAME bytes we rendered, after writing them.\n                            if let Some(status) = status.as_deref_mut() {\n                                if reassert_scanner.feed(&bytes) {\n                                    stdout\n                                        .write_all(&status.assert_bytes())\n                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n                                }\n                            }\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}"}],"details":{"displayContent":{"text":"enum EstablishFail {\n…\n    /// retry (the operator may bring the daemon back), never a resurrection.\n    /// [impl->REQ-RC-RECONNECT-TRUTH]\n    DaemonDown,\n    /// A setup error (broker connect / dial / attach-open / subscribe).\n    Error(String),\n}\n\n/// Drive the full attach-establish sequence: ensure daemon → broker conn →\n/// session resolve (LOCAL first, cross-node on miss) → dial → attach-open (with\n/// the A-4b tracing retry) → stream subscribe. Factored from `run_attach_inner`\n/// so the B-3 reconnect loop can re-drive it verbatim (REQ-RC-RECONNECT).\n///\n/// Transport for a LOCAL attach: a broker-minted in-process LOOPBACK connection\n/// (M12 W1.5, B1). A QUIC self-dial is refused by iroh (\"Connecting to ourself\n/// is not supported\"), so local attach cannot ride a self-dialed conn; instead\n/// the broker wires an in-process loopback conn whose streams surface as the\n/// SAME net-stream events the cross-node QUIC path produces. From\n/// `request_attach` down this is byte-identical to a cross-node attach — the\n/// one-pump invariant lives at the transport leaf inside the broker, never here.\n/// The loopback peer's origin is this node's own id, so the target's serve gates\n/// same-node.\n///\n/// Transport for a REMOTE attach: the registry-resolved node, seed-dialed over\n/// QUIC (mirrors the wansend resolve→seed_first_addr→net_dial leg). The owning\n/// node's serve is ALREADY transport-agnostic — a QUIC-arriving Attach serves\n/// identically to loopback — so from here down the two legs converge. Honest\n/// failure on every resolve/dial miss: a clean message, never a false attach or\n/// a hang (D2/D6).\n// [impl->REQ-RC-1] [impl->REQ-RC-CROSS-NODE-ATTACH] [impl->REQ-RC-RECONNECT]\nfn establish_attach(endpoint_id: &str, intent: AttachIntent) -> Result<Established, EstablishFail> {\n    // CONNECT-ONLY (REQ-RC-RECONNECT-TRUTH): rc is an attach pump, NOT a daemon\n    // launcher. It must NEVER auto-start the broker — the historical\n    // `ensure_running()` here WMI-resurrected a daemon on every (re)connect,\n    // which fought the operator's `spt daemon stop` (the \"stop 2-4 times\" bug)\n    // and, in the reconnect loop, re-birthed the very daemon the operator was\n    // tearing down. A daemon-down probe returns `DaemonDown` so the caller exits\n    // loud (initial) or keeps its bounded retry without spawning (reconnect). The\n    // broker cold-connect below is already deadline-bounded, so a down daemon\n    // never hangs.\n    // [impl->REQ-RC-RECONNECT-TRUTH]\n    if !spt_daemon::daemon::is_running() {\n        return Err(EstablishFail::DaemonDown);\n    }\n    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n    // pipes), so the single-threaded pump interleaves stdin + broker events\n    // without wedging. The generous io_timeout bounds the setup calls (dial,\n    // sessions, attach-open ack); the pump itself passes its own short slice.\n    let mut brain = Brain::cold_start_pump(\n        &spt_daemon::endpoint::broker_socket_name(),\n        now_ms(),\n        Duration::from_secs(10),\n    )\n    .map_err(|e| EstablishFail::Error(format!(\"broker connect: {e}\")))?;\n\n    let ops = OpMinter::new();\n    // Resolve the endpoint's live session. LOCAL first (the broker's own session\n    // table); on a MISS, the cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH):\n    // resolve the OWNING node from the registry and dial it, letting THAT node\n    // resolve endpoint→its session server-side.\n    let local_session = resolve_session(&mut brain, endpoint_id)\n        .map_err(|e| EstablishFail::Error(format!(\"list sessions: {e}\")))?;\n    let mut remote_node: Option<String> = None;\n    let (conn_id, session_id, req_endpoint): (u64, u64, Option<&str>) = match local_session {\n        Some(sid) => {\n            let dialed = brain\n                .net_dial_loopback()\n                .map_err(|e| EstablishFail::Error(format!(\"loopback dial: {e}\")))?;\n            (dialed.conn_id, sid, None)\n        }\n        None => match crate::wansend::resolve_and_dial_owner(&mut brain, endpoint_id) {\n            crate::wansend::OwnerDial::Dialed { conn_id, node } => {\n                remote_node = Some(node);\n                // session_id is a placeholder the owning node ignores — it\n                // resolves the endpoint to its OWN session id server-side.\n                (conn_id, 0, Some(endpoint_id))\n            }\n            crate::wansend::OwnerDial::NotFound => {\n                return Err(EstablishFail::NoTarget(format!(\n                    \"No live session for '{endpoint_id}' — not running on this node \\\n                     or any visible subnet.\"\n                )))\n            }\n            crate::wansend::OwnerDial::Ambiguous(msg) => {\n                return Err(EstablishFail::NoTarget(msg))\n            }\n            crate::wansend::OwnerDial::Unreachable { node, detail } => {\n                return Err(EstablishFail::NoTarget(format!(\n                    \"'{endpoint_id}' is on {node}, but it is not reachable ({detail}).\"\n                )))\n            }\n        },\n    };\n    // A-4b (REQ-OPID-TRACING-RETRY): the attach stream-open is a tracing-only op.\n    // On the broker-restart no-longer-held reply, re-mint a FRESH `rc` op and open\n    // ONCE more. The first attempt uses the conn already established above (happy\n    // path byte-identical); the one retry RE-DIALS inside `run` — a broker restart\n    // drops the CONN too, not just the stream, so opening against the dead conn\n    // would surface a different error instead of self-healing. A 2nd no-longer-held\n    // surfaces the helper's F-1 public string via the map_err below (never op lingo).\n    let mut established: Option<u64> = Some(conn_id);\n    let stream_id = spt_daemon::with_tracing_retry(\n        // Operator attach open — the rc OpMinter is the counter (ADR-0034 `rc` tag).\n        || Ok(MintedOp::new(Minter::Rc, ops.next())),\n        |op| {\n            let cid = match established.take() {\n                Some(c) => c,\n                None => match local_session {\n                    // Local: the broker's in-process loopback singleton (re-mint/reuse).\n                    Some(_) => brain\n                        .net_dial_loopback()\n                        .map_err(|e| {\n                            std::io::Error::other(format!(\"loopback re-dial: {e}\"))\n                        })?\n                        .conn_id,\n                    // Remote: re-resolve + re-dial the owning node.\n                    None => match crate::wansend::resolve_and_dial_owner(&mut brain, endpoint_id) {\n                        crate::wansend::OwnerDial::Dialed { conn_id, .. } => conn_id,\n                        _ => {\n                            return Err(std::io::Error::other(format!(\n                                \"'{endpoint_id}' is no longer reachable\"\n                            )))\n                        }\n                    },\n                },\n            };\n            request_attach_endpoint(&mut brain, cid, session_id, 0, op, intent, req_endpoint)\n        },\n    )\n    .map_err(|e| EstablishFail::Error(format!(\"attach request: {e}\")))?;\n    // Subscribe our side of the stream so the target's `Output` records surface\n    // as `NetStreamData` events (the open + Request do not auto-subscribe).\n    brain\n        .net_stream_subscribe(stream_id, 0)\n        .map_err(|e| EstablishFail::Error(format!(\"stream subscribe: {e}\")))?;\n    Ok(Established {\n        brain,\n        ops,\n        stream_id,\n        remote_node,\n    })\n}\n\n/// Drive an established viewport to its FINAL end: the B-3 reconnect loop\n/// (REQ-RC-RECONNECT) — each pass is one full viewport (initial resize → pump).\n/// A severed viewport (broker bounce, remote conn drop — the reconnectable\n/// class) paints a centered reconnect banner and RE-DRIVES [`establish_attach`]\n/// inside a bounded window; every other end is FINAL and returns to the\n/// caller's parting copy. The FAULT-MATRIX row-9 promise (\"rc reconnects\") is\n/// made TRUE here, not edited down (triage F-3).\n///\n/// The SEAM (doyle rig ruling #4): everything terminal-owning stays at the\n/// interactive caller — raw-mode guard, stdin-reader spawn, StatusRow\n/// construction/teardown, the parting copy. This fn only needs an established\n/// attach + an `out` sink, so the two-host rig can drive the REAL loop headless\n/// (`out` = a capture buffer) — no behavior change for the interactive path.\n/// `remote_node` is caller-owned so the parting copy names the LAST node the\n/// viewport was attached to (a reconnect can re-resolve it).\n#[allow(clippy::too_many_arguments)]\nfn attach_viewport(\n    endpoint_id: &str,\n    intent: AttachIntent,\n    view: bool,\n    mut est: Established,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n    remote_node: &mut Option<String>,\n    out: &mut impl std::io::Write,\n) -> Result<PumpEnd, String> {\n    loop {\n        // Controller: send the INITIAL terminal size so the PTY matches from the\n        // start (REQ-RCVIEW-1). Best-effort — a failure just leaves the default\n        // size. When the status row is active the harness PTY gets rows-1 (we own\n        // row 1), never 0 (REQ-RC-IDENTITY). Re-sent per establish: the new\n        // viewport's PTY must match our CURRENT terminal, not the original one.\n        if !view {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n                let pty_rows = if status.is_some() {\n                    rows.saturating_sub(1).max(1)\n                } else {\n                    rows\n                };\n                let _ = send_attach_resize(&mut est.brain, est.stream_id, pty_rows, cols);\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…\nfn classify_read_err(kind: std::io::ErrorKind) -> ReadDisposition {\n…\n    match kind {\n        WouldBlock | TimedOut => ReadDisposition::Retry,\n        UnexpectedEof | BrokenPipe | ConnectionReset | ConnectionAborted => {\n            ReadDisposition::BrokerGone\n        }\n        _ => ReadDisposition::Fatal,\n    }\n}\n\n/// The full-duplex pump: drain stdin → `send_attach_input`; render inbound\n/// `AttachRecord::Output` → stdout; end on `Exit`, EOF, or detach.\n#[allow(clippy::too_many_arguments)]\nfn pump(\n    brain: &mut Brain,\n    stream_id: u64,\n    ops: &OpMinter,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    decoder: &mut AttachDecoder,\n    stdout: &mut impl std::io::Write,\n    controller: bool,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n) -> Result<PumpEnd, String> {\n    // Track the harness's mouse-reporting mode from its output DECSET sequences so\n    // the stdin reader knows whether to forward scroll (REQ-RC-MOUSE-FORWARD). The\n    // scanner is pump-local (single producer) and survives a sequence split across\n    // output chunks; it writes the shared `mouse_mode` atomics the reader observes.\n    let mut mouse_scanner = MouseModeScanner::default();\n    // The reserved-row re-assert scanner (REQ-RC-IDENTITY): watches the harness\n    // output for an alt-screen enter / DECSTBM reset that would destroy our\n    // scroll-region margin, so we can re-paint the status row. Pump-local, single\n    // producer, survives a trigger split across output chunks (like mouse_scanner).\n    let mut reassert_scanner = ReassertScanner::default();\n    // Reserve the top row + paint the identity banner up front (REQ-RC-IDENTITY).\n    // Controller-only (the caller passes `Some` only for a control attach).\n    if let Some(status) = status.as_deref_mut() {\n        stdout\n            .write_all(&status.assert_bytes())\n            .map_err(|e| format!(\"status assert: {e}\"))?;\n        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n    }\n    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n    // (the session outlives the viewport). Best-effort: an already-finished or\n    // exited stream send is harmless to attempt.\n    fn detach(brain: &mut Brain, stream_id: u64) -> PumpEnd {\n        let _ = brain.net_stream_send(stream_id, &[], None, true);\n        PumpEnd::Detached\n    }\n    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n    // drop any `seq` already rendered (the operator render cursor, matching the\n    // local `OutputLog` replay discipline).\n    let mut cursor = 0u64;\n    // Controller window-change tracking (REQ-RCVIEW-1): poll the terminal size\n    // each slice and forward a Resize when it changes (cross-platform without\n    // hooking SIGWINCH / the crossterm event loop, which would conflict with the\n    // raw-stdin reader). `None` until the first read.\n    let mut last_size: Option<(u16, u16)> = None;\n    // The PTY size last advertised to us as a VIEWER (the letterbox feed); shown\n    // as a one-line indicator when it differs from our own terminal.\n    let mut pty_size: Option<(u16, u16)> = None;\n    // First-event backstop (REQ-HAZARD-RC-ATTACH-FAILFAST path b): a dead/wedged\n    // attach produces nothing — bail with a clear message past FIRST_EVENT_GRACE\n    // rather than an infinite blank. Any broker event clears it.\n    let started = Instant::now();\n    let mut seen_any = false;\n    // Did the serve ever hand us a rendered Output frame? A serve-closed EOF with\n    // NOTHING rendered means the attach was REFUSED (no live session / gate deny) —\n    // the cross-node D6 stale-row case — not a live viewport the operator detached.\n    // Any live session replays its buffered ring on attach, so a real attach sets\n    // this before the serve would ever finish the stream. (#4, REQ-RC-CROSS-NODE-ATTACH)\n    let mut rendered_any = false;\n    loop {\n        // ── controller window-change → resize ──────────────────────────────\n        if controller {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n                if last_size != Some((rows, cols)) {\n                    last_size = Some((rows, cols));\n                    match status.as_deref_mut() {\n                        // Status active: the harness PTY owns rows-1 (we keep row 1\n                        // for the banner), and we re-assert the margin + repaint on\n                        // the new size (REQ-RC-IDENTITY). Never resize the PTY to 0.\n                        Some(status) => {\n                            status.update_size(rows, cols);\n                            let pty_rows = rows.saturating_sub(1).max(1);\n                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n                                let _ = stdout.flush();\n                            }\n                        }\n                        None => {\n                            let _ = send_attach_resize(brain, stream_id, rows, cols);\n                        }\n                    }\n                }\n            }\n        }\n\n        // ── stdin → session ────────────────────────────────────────────────\n        if let Some(rx) = rx {\n            loop {\n                match rx.try_recv() {\n                    Ok(StdinMsg::Bytes(bytes)) => {\n                        send_attach_input(brain, stream_id, &bytes, ops.next())\n                            .map_err(|e| format!(\"send input: {e}\"))?;\n                    }\n                    Ok(StdinMsg::Detach) => return Ok(detach(brain, stream_id)),\n                    Err(mpsc::TryRecvError::Empty) => break,\n                    Err(mpsc::TryRecvError::Disconnected) => {\n                        return Ok(detach(brain, stream_id))\n                    }\n                }\n            }\n        }\n\n        // ── session → terminal ─────────────────────────────────────────────\n        let ev = match brain.read_event_until(Some(Instant::now() + POLL_SLICE)) {\n            Ok(ev) => ev,\n            Err(e) => match classify_read_err(e.kind()) {\n                // A poll-slice timeout: no event this slice. Before looping, the\n                // first-event backstop — if NOTHING has ever arrived past the\n                // generous grace, the attach is dead/wedged (path b).\n                ReadDisposition::Retry => {\n                    if first_event_stalled(seen_any, started.elapsed()) {\n                        return Ok(PumpEnd::Stalled);\n                    }\n                    continue;\n                }\n                // A severed broker stream (broker bounce / hang-up) — surface it\n                // gracefully, never as a raw RC_FAIL crash (REQ-HAZARD-RC-EOF).\n                ReadDisposition::BrokerGone => return Ok(PumpEnd::BrokerGone),\n                ReadDisposition::Fatal => return Err(format!(\"read event: {e}\")),\n            },\n        };\n        seen_any = true; // any broker event proves the attach stream is live\n        match ev {\n            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Output { seq, data_b64 } => {\n                            if seq < cursor {\n                                continue; // already rendered — re-serve dedup\n                            }\n                            cursor = seq + 1;\n                            rendered_any = true;\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| format!(\"decode output: {e}\"))?;\n                            // Track the harness's mouse-reporting mode from its output\n                            // (REQ-RC-MOUSE-FORWARD) before rendering it verbatim.\n                            mouse_scanner.feed(mouse_mode, &bytes);\n                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n                            // Re-assert the reserved row if the harness output just\n                            // destroyed our scroll region — alt-screen enter or a\n                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n                            // margin is gone (alt-screen has its own full-screen\n                            // margins; a DECSTBM reset clears ours), so re-set the\n                            // MARGIN + repaint (assert_bytes), not just the text —\n                            // else the harness scrolls back over row 1. Scanned on the\n                            // SAME bytes we rendered, after writing them.\n                            if let Some(status) = status.as_deref_mut() {\n                                if reassert_scanner.feed(&bytes) {\n                                    stdout\n                                        .write_all(&status.assert_bytes())\n                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n                                }\n                            }\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}","startLine":1351,"lineNumbers":[1351,null,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,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,1509,1510,1511,1512,1513,1514,1515,1516,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,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,null,1798,null,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,null,1966,null,1989,1990,1991,null,1997,1998,1999]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs"}}},"isError":false,"timestamp":1783730632761}}
{"type":"message","id":"2067f11c","parentId":"ba6c725b","timestamp":"2026-07-11T00:43:52.786Z","message":{"role":"toolResult","toolCallId":"call_mxUGixexfApp1TQoaRYGPuwR|fc_0a129b8eba4c900a016a5191c92fb4819abb1f4abea9e3fa41","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\n 126|    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n*127|    /// on its `rx.recv()` Err, reads this and — when true — emits a\n 128|    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n 129|    /// the serving brain skips to live instead of tearing the attach down.\n 130|    evicted: Arc<AtomicBool>,\n...\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 520|    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n 521|    /// state; the mutex is taken only around the brief set/clear and the read, never\n...\n 607|/// already-delivered `seq` (an `attach(sub, 0)` rewind, `seq + 1 <= current`)\n*608|/// cannot lower it. This is the exact decision the `let _ = write_frame(...)`\n 609|/// swallow used to drop (doyle D4-1 amendments 1–3).\n 610|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 611|fn advanced_cursor(current: u64, seq: u64, write_ok: bool) -> u64 {\n...\n 813|                    // Mark eviction BEFORE dropping the sink: the writer's\n*814|                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n 815|                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n 816|                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n 817|                    if let Some(sink) = self.viewers.remove(&vid) {\n...\n 951|    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n*952|    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n 953|    /// controller) it is NOT finished, so a live controller is never touched (the\n 954|    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n 955|    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n...\n 1004|    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n*1005|    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n 1006|    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n 1007|    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n 1008|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n...\n 1011|            c.write_blocked_since\n*1012|                .lock()\n 1013|                .ok()\n 1014|                .and_then(|g| *g)\n 1015|                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n...\n 1345|    }\n*1346|    while let Ok(frame) = rx.recv() {\n 1347|        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n 1348|            return;\n 1349|        }\n...\n 1454|    let mark_blocked = |on: bool| {\n*1455|        if let Ok(mut g) = write_blocked_since.lock() {\n 1456|            *g = if on { Some(Instant::now()) } else { None };\n 1457|        }\n 1458|    };\n...\n 1465|        // gate is acquired and BEFORE any byte is written — the same\n*1466|        // serialization point the old epoch-check-under-`send.lock()` had: both\n 1467|        // writers serialize on the one conn gate, and `become_controller` bumps\n 1468|        // the epoch BEFORE spawning the new writer, so whichever superseded\n 1469|        // writer next holds the gate observes the bump and bails before\n...\n 1490|    }\n*1491|    while let Ok(msg) = rx.recv() {\n 1492|        let (frame, seq) = match &msg {\n 1493|            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n 1494|            CtrlMsg::Control(frame) => (frame, None),\n...\n 1706|fn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\n*1707|    while let Ok(bytes) = rx.recv() {\n*1708|        let _ = session.write_input(&bytes);\n 1709|    }\n 1710|}\n 1711|\n...\n 1721|/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\n*1722|/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\n 1723|/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\n 1724|/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\n 1725|/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\n...\n 1731|fn lock_floor(floor: &Mutex<InjectFloor>) -> std::sync::MutexGuard<'_, InjectFloor> {\n*1732|    floor.lock().unwrap_or_else(|p| p.into_inner())\n 1733|}\n 1734|\n 1735|/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\n...\n 1738|/// panic while another thread held one of these locks would otherwise POISON it\n*1739|/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\n 1740|/// dies, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read\n 1741|/// deadline elapsed\", brain.rs) while non-locked ops keep working. The `into_inner`\n 1742|/// idiom (identical to [`lock_floor`] and the effect journal, bug #16) keeps the\n...\n 1745|/// per-session `OutputLog` RING needs the extra coherence clamp of [`recover_log`].\n*1746|/// CLASS INVARIANT (KNOWN-HAZARDS): NO bare `.lock().unwrap()` on a broker-resident\n 1747|/// lock reachable from serve/dispatch — use `recover`/`recover_log` or a documented\n 1748|/// fail-fast justification.\n 1749|// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n 1750|fn recover<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {\n*1751|    m.lock().unwrap_or_else(|p| p.into_inner())\n 1752|}\n 1753|\n 1754|/// [`recover`] for the per-session [`OutputLog`] RING, with a COHERENCE CLAMP on the\n...\n 1765|fn recover_log(m: &Mutex<OutputLog>) -> std::sync::MutexGuard<'_, OutputLog> {\n*1766|    match m.lock() {\n 1767|        Ok(g) => g,\n 1768|        Err(p) => {\n 1769|            let mut g = p.into_inner();\n...\n 1829|            // First line: identity (content-free of any PTY bytes).\n*1830|            let _ = child.send(&ToBinary::Init {\n 1831|                endpoint_id: endpoint_id.to_string(),\n 1832|                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\n 1833|            });\n### conn.rs\n 137|fn recover<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {\n*138|    match m.lock() {\n 139|        Ok(g) => g,\n 140|        Err(p) => p.into_inner(),\n 141|    }\n...\n 350|    /// serialized gate is acquired and BEFORE any byte is written — the same\n*351|    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n 352|    /// superseded controller writer still bails atomically with respect to\n 353|    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n 354|    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n...\n 404|\n*405|        let res = write_frame(&mut half, env);\n 406|\n 407|        // Completion handshake: `write_frame` has returned, so the OS op —\n 408|        // canceled or not — reported completion and the frame buffer is\n\n## spt/src/\n### rc.rs\n 982|    std::thread::spawn(move || {\n*983|        let mut stdin = std::io::stdin().lock();\n 984|        let mut buf = [0u8; 1024];\n 985|        let mut armed = false; // saw the detach prefix, awaiting the next byte\n 986|        loop {\n...\n 996|            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);\n*997|            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {\n 998|                break;\n 999|            }\n 1000|            if parsed.detach {\n*1001|                let _ = tx.send(StdinMsg::Detach);\n 1002|                return;\n 1003|            }\n 1004|        }\n...\n 1029|                        if let Some(bytes) = clipboard_paste(read_clipboard) {\n*1030|                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1031|                                break;\n 1032|                            }\n 1033|                        }\n...\n 1041|                            let bytes = scroll_sgr(up, me.column, me.row);\n*1042|                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1043|                                break;\n 1044|                            }\n 1045|                        }\n...\n 1052|                        KeyAction::Detach => {\n*1053|                            let _ = tx.send(StdinMsg::Detach);\n 1054|                            return;\n 1055|                        }\n 1056|                        KeyAction::Forward(bytes) => {\n*1057|                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1058|                                break;\n 1059|                            }\n 1060|                        }\n...\n 1064|                Event::Paste(s) => {\n*1065|                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {\n 1066|                        break;\n 1067|                    }\n 1068|                }\n...\n 1228|    if status.is_some() {\n*1229|        let _ = stdout.write_all(&StatusRow::restore_bytes());\n 1230|        let _ = stdout.flush();\n 1231|    }\n 1232|    // Out of raw mode (guard drops after) before the parting line.\n...\n 1585|            let remaining = reconnect_remaining_secs(elapsed);\n*1586|            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n 1587|            let _ = out.flush();\n 1588|            // Operator gives up mid-reconnect: honor the detach keys.\n 1589|            if let Some(rx) = rx {\n...\n 1836|        stdout\n*1837|            .write_all(&status.assert_bytes())\n 1838|            .map_err(|e| format!(\"status assert: {e}\"))?;\n 1839|        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840|    }\n...\n 1884|                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885|                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n 1886|                                let _ = stdout.flush();\n 1887|                            }\n 1888|                        }\n...\n 1948|                            mouse_scanner.feed(mouse_mode, &bytes);\n*1949|                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n 1950|                            // Re-assert the reserved row if the harness output just\n 1951|                            // destroyed our scroll region — alt-screen enter or a\n 1952|                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n...\n 1960|                                    stdout\n*1961|                                        .write_all(&status.assert_bytes())\n 1962|                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n 1963|                                }\n 1964|                            }\n...\n 2077|        fn snapshot(&self) -> Vec<u8> {\n*2078|            self.0.lock().unwrap().clone()\n 2079|        }\n 2080|    }\n 2081|    impl std::io::Write for Capture {\n 2082|        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n*2083|            self.0.lock().unwrap().extend_from_slice(buf);\n 2084|            Ok(buf.len())\n 2085|        }\n 2086|        fn flush(&mut self) -> std::io::Result<()> {\n...\n 3173|        // (a) pump START: reserve + paint.\n*3174|        sink.write_all(&status.assert_bytes()).unwrap();\n 3175|        let assert_len = sink.len();\n 3176|        assert!(sink.starts_with(b\"\\x1b[2;24r\"), \"start reserves the row\");\n 3177|        assert!(\n...\n 3184|        if scanner.feed(plain) {\n*3185|            sink.write_all(&status.assert_bytes()).unwrap();\n 3186|        }\n 3187|        assert_eq!(sink.len(), assert_len, \"plain output emits no status re-assert\");\n 3188|\n...\n 3196|            fired = true;\n*3197|            sink.write_all(&status.assert_bytes()).unwrap();\n 3198|        }\n 3199|        assert!(fired, \"the alt-screen enter trips the re-assert scanner\");\n 3200|        assert!(sink.len() > before, \"a re-assert was appended after the trigger\");\n...\n 3206|        // (c) pump EXIT: restore (reset region + clear row 1).\n*3207|        sink.write_all(&StatusRow::restore_bytes()).unwrap();\n 3208|        assert!(\n 3209|            sink.ends_with(b\"\\x1b[r\\x1b[1;1H\\x1b[2K\"),\n 3210|            \"exit restores the full scroll region and clears the reserved row\"\n\n## spt-term/src/\n### pty.rs\n 179|        self.reader\n*180|            .lock()\n 181|            .unwrap()\n 182|            .take()\n 183|            .ok_or(SurfaceError::ReaderTaken)\n...\n 195|        self.child\n*196|            .lock()\n 197|            .unwrap()\n 198|            .try_wait()\n 199|            .map_err(SurfaceError::Io)\n...\n 203|    pub fn wait(&self) -> Result<ExitStatus, SurfaceError> {\n*204|        self.child.lock().unwrap().wait().map_err(SurfaceError::Io)\n 205|    }\n 206|\n 207|    /// Terminate the child. Safe to call from another thread while `wait`\n...\n 210|    pub fn kill(&self) -> Result<(), SurfaceError> {\n*211|        let res = self.killer.lock().unwrap().kill();\n 212|        #[cfg(windows)]\n 213|        {\n 214|            // portable-pty 0.8's Windows `ChildKiller` returns the spurious\n...\n 235|impl SessionSurface for PtySession {\n*236|    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*237|        let mut w = self.writer.lock().unwrap();\n*238|        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239|        w.flush().map_err(SurfaceError::Io)\n 240|    }\n 241|\n...\n 245|        self.master\n*246|            .lock()\n 247|            .unwrap()\n 248|            .resize(size.into())\n 249|            .map_err(|e| SurfaceError::Io(io_other(e)))\n### reader.rs\n 135|                            // CPR never interleaves with caller-injected input.\n*136|                            if let Ok(mut w) = writer.lock() {\n 137|                                for _ in 0..hits {\n*138|                                    let _ = w.write_all(DSR_REPLY);\n 139|                                }\n 140|                                let _ = w.flush();\n 141|                            }\n...\n 223|            fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {\n*224|                self.0.lock().unwrap().extend_from_slice(b);\n 225|                Ok(b.len())\n 226|            }\n 227|            fn flush(&mut self) -> std::io::Result<()> {\n...\n 248|        let drain = Drain::spawn(reader, writer, move |c| {\n*249|            fsink.lock().unwrap().extend_from_slice(c)\n 250|        });\n 251|        drain.join(); // ScriptedReader EOFs, so this returns\n 252|\n 253|        assert_eq!(\n*254|            &*written.lock().unwrap(),\n 255|            DSR_REPLY,\n 256|            \"the pump must answer the DSR query with exactly one CPR reply\"\n 257|        );\n 258|        assert_eq!(\n*259|            &*forwarded.lock().unwrap(),\n 260|            b\"before\\x1b[6nafter\",\n 261|            \"every byte (query included) is forwarded verbatim to the sink\"\n 262|        );\n...\n 283|        assert_eq!(answered, 0, \"no DSR query → no CPR answer\");\n*284|        assert!(written.lock().unwrap().is_empty(), \"nothing written back\");\n 285|    }\n 286|}\n### stream.rs\n 36|    pub fn recv(&self) -> Option<Vec<u8>> {\n*37|        self.rx.recv().ok()\n 38|    }\n 39|\n 40|    /// Non-blocking poll for the next chunk, if one is already buffered.\n...\n 77|            // torn-down consumer never wedges the pump.\n*78|            let _ = tx.send(chunk.to_vec());\n 79|        })?;\n 80|        Ok(OutputStream { rx, drain })\n 81|    }\n### surface.rs\n 104|    /// the concrete surface in A3). Flushed on return.\n*105|    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError>;\n 106|\n 107|    /// Inform the surface (and thus the child) of a new window geometry. On a\n 108|    /// real PTY this updates the kernel winsize and raises `SIGWINCH`.\n...\n 118|    fn send_keys(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*119|        self.write_input(bytes)\n 120|    }\n 121|\n 122|    /// **send-line** — cooked line submission. Appends a carriage return (`\\r`,\n...\n 131|        buf.push(b'\\r');\n*132|        self.write_input(&buf)\n 133|    }\n 134|}"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src/broker.rs, spt-core/crates/spt-daemon/src/attach.rs, spt-core/crates/spt-daemon/src/conn.rs, spt-core/crates/spt/src/rc.rs, spt-core/crates/spt-term/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":63,"fileCount":7,"files":["spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/conn.rs","spt-core/crates/spt/src/rc.rs","spt-core/crates/spt-term/src/pty.rs","spt-core/crates/spt-term/src/reader.rs","spt-core/crates/spt-term/src/stream.rs","spt-core/crates/spt-term/src/surface.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/conn.rs","count":3},{"path":"spt-core/crates/spt/src/rc.rs","count":20},{"path":"spt-core/crates/spt-term/src/pty.rs","count":8},{"path":"spt-core/crates/spt-term/src/reader.rs","count":7},{"path":"spt-core/crates/spt-term/src/stream.rs","count":2},{"path":"spt-core/crates/spt-term/src/surface.rs","count":3}],"truncated":true,"perFileLimitReached":20,"displayContent":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\n  126│    /// `append` stores `true` BEFORE dropping the sink (and thus `tx`); the writer,\n *127│    /// on its `rx.recv()` Err, reads this and — when true — emits a\n  128│    /// [`KIND_VIEWER_EVICTED`] marker (in the writer thread, OFF the log lock) so\n  129│    /// the serving brain skips to live instead of tearing the attach down.\n  130│    evicted: Arc<AtomicBool>,\n     │...\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  520│    /// `last_ok` stale. `Arc<Mutex<…>>` so the reader observes the writer's live\n  521│    /// state; the mutex is taken only around the brief set/clear and the read, never\n     │...\n  607│/// already-delivered `seq` (an `attach(sub, 0)` rewind, `seq + 1 <= current`)\n *608│/// cannot lower it. This is the exact decision the `let _ = write_frame(...)`\n  609│/// swallow used to drop (doyle D4-1 amendments 1–3).\n  610│// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n  611│fn advanced_cursor(current: u64, seq: u64, write_ok: bool) -> u64 {\n     │...\n  813│                    // Mark eviction BEFORE dropping the sink: the writer's\n *814│                    // `rx.recv()` then returns Err (tx dropped) and reads this flag\n  815│                    // to emit a KIND_VIEWER_EVICTED marker (skip-to-live, off the\n  816│                    // log lock) instead of dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT).\n  817│                    if let Some(sink) = self.viewers.remove(&vid) {\n     │...\n  951│    /// writer exits when a socket write fails (a severed / keepalive-killed controller\n *952│    /// conn) or its `tx` drops; while it is parked on `rx.recv()` (a LIVE but idle\n  953│    /// controller) it is NOT finished, so a live controller is never touched (the\n  954│    /// no-false-reap guard). Reaping here makes the broker report honest truth so the\n  955│    /// EXISTING [`converge_perch_stamps`] clears the now-stale info.json\n     │...\n 1004│    /// socket write for at least `deadline` (the brain-subscriber conn suspended /\n*1005│    /// black-holed). An IDLE controller (writer parked on `rx.recv()`, nothing to\n 1006│    /// write) reads NOT stalled: its [`ControllerSink::write_blocked_since`] is `None`.\n 1007│    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n 1008│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n     │...\n 1011│            c.write_blocked_since\n*1012│                .lock()\n 1013│                .ok()\n 1014│                .and_then(|g| *g)\n 1015│                .is_some_and(|since| now.saturating_duration_since(since) >= deadline)\n     │...\n 1345│    }\n*1346│    while let Ok(frame) = rx.recv() {\n 1347│        if !matches!(send.write(&frame), Ok(ConnWrite::Done)) {\n 1348│            return;\n 1349│        }\n     │...\n 1454│    let mark_blocked = |on: bool| {\n*1455│        if let Ok(mut g) = write_blocked_since.lock() {\n 1456│            *g = if on { Some(Instant::now()) } else { None };\n 1457│        }\n 1458│    };\n     │...\n 1465│        // gate is acquired and BEFORE any byte is written — the same\n*1466│        // serialization point the old epoch-check-under-`send.lock()` had: both\n 1467│        // writers serialize on the one conn gate, and `become_controller` bumps\n 1468│        // the epoch BEFORE spawning the new writer, so whichever superseded\n 1469│        // writer next holds the gate observes the bump and bails before\n     │...\n 1490│    }\n*1491│    while let Ok(msg) = rx.recv() {\n 1492│        let (frame, seq) = match &msg {\n 1493│            CtrlMsg::Output(seq, frame) => (frame, Some(*seq)),\n 1494│            CtrlMsg::Control(frame) => (frame, None),\n     │...\n 1706│fn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\n*1707│    while let Ok(bytes) = rx.recv() {\n*1708│        let _ = session.write_input(&bytes);\n 1709│    }\n 1710│}\n 1711│\n     │...\n 1721│/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\n*1722│/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\n 1723│/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\n 1724│/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\n 1725│/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\n     │...\n 1731│fn lock_floor(floor: &Mutex<InjectFloor>) -> std::sync::MutexGuard<'_, InjectFloor> {\n*1732│    floor.lock().unwrap_or_else(|p| p.into_inner())\n 1733│}\n 1734│\n 1735│/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\n     │...\n 1738│/// panic while another thread held one of these locks would otherwise POISON it\n*1739│/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\n 1740│/// dies, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read\n 1741│/// deadline elapsed\", brain.rs) while non-locked ops keep working. The `into_inner`\n 1742│/// idiom (identical to [`lock_floor`] and the effect journal, bug #16) keeps the\n     │...\n 1745│/// per-session `OutputLog` RING needs the extra coherence clamp of [`recover_log`].\n*1746│/// CLASS INVARIANT (KNOWN-HAZARDS): NO bare `.lock().unwrap()` on a broker-resident\n 1747│/// lock reachable from serve/dispatch — use `recover`/`recover_log` or a documented\n 1748│/// fail-fast justification.\n 1749│// [impl->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]\n 1750│fn recover<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {\n*1751│    m.lock().unwrap_or_else(|p| p.into_inner())\n 1752│}\n 1753│\n 1754│/// [`recover`] for the per-session [`OutputLog`] RING, with a COHERENCE CLAMP on the\n     │...\n 1765│fn recover_log(m: &Mutex<OutputLog>) -> std::sync::MutexGuard<'_, OutputLog> {\n*1766│    match m.lock() {\n 1767│        Ok(g) => g,\n 1768│        Err(p) => {\n 1769│            let mut g = p.into_inner();\n     │...\n 1829│            // First line: identity (content-free of any PTY bytes).\n*1830│            let _ = child.send(&ToBinary::Init {\n 1831│                endpoint_id: endpoint_id.to_string(),\n 1832│                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\n 1833│            });\n### conn.rs\n 137│fn recover<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {\n*138│    match m.lock() {\n 139│        Ok(g) => g,\n 140│        Err(p) => p.into_inner(),\n 141│    }\n    │...\n 350│    /// serialized gate is acquired and BEFORE any byte is written — the same\n*351│    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n 352│    /// superseded controller writer still bails atomically with respect to\n 353│    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n 354│    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n    │...\n 404│\n*405│        let res = write_frame(&mut half, env);\n 406│\n 407│        // Completion handshake: `write_frame` has returned, so the OS op —\n 408│        // canceled or not — reported completion and the frame buffer is\n\n## spt/src/\n### rc.rs\n  982│    std::thread::spawn(move || {\n *983│        let mut stdin = std::io::stdin().lock();\n  984│        let mut buf = [0u8; 1024];\n  985│        let mut armed = false; // saw the detach prefix, awaiting the next byte\n  986│        loop {\n     │...\n  996│            let parsed = parse_stdin_chunk(&mut armed, &buf[..n]);\n *997│            if !parsed.forward.is_empty() && tx.send(StdinMsg::Bytes(parsed.forward)).is_err() {\n  998│                break;\n  999│            }\n 1000│            if parsed.detach {\n*1001│                let _ = tx.send(StdinMsg::Detach);\n 1002│                return;\n 1003│            }\n 1004│        }\n     │...\n 1029│                        if let Some(bytes) = clipboard_paste(read_clipboard) {\n*1030│                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1031│                                break;\n 1032│                            }\n 1033│                        }\n     │...\n 1041│                            let bytes = scroll_sgr(up, me.column, me.row);\n*1042│                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1043│                                break;\n 1044│                            }\n 1045│                        }\n     │...\n 1052│                        KeyAction::Detach => {\n*1053│                            let _ = tx.send(StdinMsg::Detach);\n 1054│                            return;\n 1055│                        }\n 1056│                        KeyAction::Forward(bytes) => {\n*1057│                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {\n 1058│                                break;\n 1059│                            }\n 1060│                        }\n     │...\n 1064│                Event::Paste(s) => {\n*1065│                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {\n 1066│                        break;\n 1067│                    }\n 1068│                }\n     │...\n 1228│    if status.is_some() {\n*1229│        let _ = stdout.write_all(&StatusRow::restore_bytes());\n 1230│        let _ = stdout.flush();\n 1231│    }\n 1232│    // Out of raw mode (guard drops after) before the parting line.\n     │...\n 1585│            let remaining = reconnect_remaining_secs(elapsed);\n*1586│            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n 1587│            let _ = out.flush();\n 1588│            // Operator gives up mid-reconnect: honor the detach keys.\n 1589│            if let Some(rx) = rx {\n     │...\n 1836│        stdout\n*1837│            .write_all(&status.assert_bytes())\n 1838│            .map_err(|e| format!(\"status assert: {e}\"))?;\n 1839│        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840│    }\n     │...\n 1884│                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885│                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n 1886│                                let _ = stdout.flush();\n 1887│                            }\n 1888│                        }\n     │...\n 1948│                            mouse_scanner.feed(mouse_mode, &bytes);\n*1949│                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n 1950│                            // Re-assert the reserved row if the harness output just\n 1951│                            // destroyed our scroll region — alt-screen enter or a\n 1952│                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n     │...\n 1960│                                    stdout\n*1961│                                        .write_all(&status.assert_bytes())\n 1962│                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n 1963│                                }\n 1964│                            }\n     │...\n 2077│        fn snapshot(&self) -> Vec<u8> {\n*2078│            self.0.lock().unwrap().clone()\n 2079│        }\n 2080│    }\n 2081│    impl std::io::Write for Capture {\n 2082│        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n*2083│            self.0.lock().unwrap().extend_from_slice(buf);\n 2084│            Ok(buf.len())\n 2085│        }\n 2086│        fn flush(&mut self) -> std::io::Result<()> {\n     │...\n 3173│        // (a) pump START: reserve + paint.\n*3174│        sink.write_all(&status.assert_bytes()).unwrap();\n 3175│        let assert_len = sink.len();\n 3176│        assert!(sink.starts_with(b\"\\x1b[2;24r\"), \"start reserves the row\");\n 3177│        assert!(\n     │...\n 3184│        if scanner.feed(plain) {\n*3185│            sink.write_all(&status.assert_bytes()).unwrap();\n 3186│        }\n 3187│        assert_eq!(sink.len(), assert_len, \"plain output emits no status re-assert\");\n 3188│\n     │...\n 3196│            fired = true;\n*3197│            sink.write_all(&status.assert_bytes()).unwrap();\n 3198│        }\n 3199│        assert!(fired, \"the alt-screen enter trips the re-assert scanner\");\n 3200│        assert!(sink.len() > before, \"a re-assert was appended after the trigger\");\n     │...\n 3206│        // (c) pump EXIT: restore (reset region + clear row 1).\n*3207│        sink.write_all(&StatusRow::restore_bytes()).unwrap();\n 3208│        assert!(\n 3209│            sink.ends_with(b\"\\x1b[r\\x1b[1;1H\\x1b[2K\"),\n 3210│            \"exit restores the full scroll region and clears the reserved row\"\n\n## spt-term/src/\n### pty.rs\n 179│        self.reader\n*180│            .lock()\n 181│            .unwrap()\n 182│            .take()\n 183│            .ok_or(SurfaceError::ReaderTaken)\n    │...\n 195│        self.child\n*196│            .lock()\n 197│            .unwrap()\n 198│            .try_wait()\n 199│            .map_err(SurfaceError::Io)\n    │...\n 203│    pub fn wait(&self) -> Result<ExitStatus, SurfaceError> {\n*204│        self.child.lock().unwrap().wait().map_err(SurfaceError::Io)\n 205│    }\n 206│\n 207│    /// Terminate the child. Safe to call from another thread while `wait`\n    │...\n 210│    pub fn kill(&self) -> Result<(), SurfaceError> {\n*211│        let res = self.killer.lock().unwrap().kill();\n 212│        #[cfg(windows)]\n 213│        {\n 214│            // portable-pty 0.8's Windows `ChildKiller` returns the spurious\n    │...\n 235│impl SessionSurface for PtySession {\n*236│    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*237│        let mut w = self.writer.lock().unwrap();\n*238│        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239│        w.flush().map_err(SurfaceError::Io)\n 240│    }\n 241│\n    │...\n 245│        self.master\n*246│            .lock()\n 247│            .unwrap()\n 248│            .resize(size.into())\n 249│            .map_err(|e| SurfaceError::Io(io_other(e)))\n### reader.rs\n 135│                            // CPR never interleaves with caller-injected input.\n*136│                            if let Ok(mut w) = writer.lock() {\n 137│                                for _ in 0..hits {\n*138│                                    let _ = w.write_all(DSR_REPLY);\n 139│                                }\n 140│                                let _ = w.flush();\n 141│                            }\n    │...\n 223│            fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {\n*224│                self.0.lock().unwrap().extend_from_slice(b);\n 225│                Ok(b.len())\n 226│            }\n 227│            fn flush(&mut self) -> std::io::Result<()> {\n    │...\n 248│        let drain = Drain::spawn(reader, writer, move |c| {\n*249│            fsink.lock().unwrap().extend_from_slice(c)\n 250│        });\n 251│        drain.join(); // ScriptedReader EOFs, so this returns\n 252│\n 253│        assert_eq!(\n*254│            &*written.lock().unwrap(),\n 255│            DSR_REPLY,\n 256│            \"the pump must answer the DSR query with exactly one CPR reply\"\n 257│        );\n 258│        assert_eq!(\n*259│            &*forwarded.lock().unwrap(),\n 260│            b\"before\\x1b[6nafter\",\n 261│            \"every byte (query included) is forwarded verbatim to the sink\"\n 262│        );\n    │...\n 283│        assert_eq!(answered, 0, \"no DSR query → no CPR answer\");\n*284│        assert!(written.lock().unwrap().is_empty(), \"nothing written back\");\n 285│    }\n 286│}\n### stream.rs\n 36│    pub fn recv(&self) -> Option<Vec<u8>> {\n*37│        self.rx.recv().ok()\n 38│    }\n 39│\n 40│    /// Non-blocking poll for the next chunk, if one is already buffered.\n   │...\n 77│            // torn-down consumer never wedges the pump.\n*78│            let _ = tx.send(chunk.to_vec());\n 79│        })?;\n 80│        Ok(OutputStream { rx, drain })\n 81│    }\n### surface.rs\n 104│    /// the concrete surface in A3). Flushed on return.\n*105│    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError>;\n 106│\n 107│    /// Inform the surface (and thus the child) of a new window geometry. On a\n 108│    /// real PTY this updates the kernel winsize and raises `SIGWINCH`.\n    │...\n 118│    fn send_keys(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*119│        self.write_input(bytes)\n 120│    }\n 121│\n 122│    /// **send-line** — cooked line submission. Appends a carriage return (`\\r`,\n    │...\n 131│        buf.push(b'\\r');\n*132│        self.write_input(&buf)\n 133│    }\n 134│}"},"isError":false,"timestamp":1783730632786}}
{"type":"message","id":"0b009ab1","parentId":"2067f11c","timestamp":"2026-07-11T00:43:52.804Z","message":{"role":"toolResult","toolCallId":"call_s0Jhd9EK5EtdG0Z0SXj8dDlj|fc_0a129b8eba4c900a016a5191c92fa8819a8815d3073cea86a1","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\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 145|\n 146|/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n...\n 149|/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n*150|/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n 151|/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n 152|/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n 153|/// untouched.\n...\n 169|/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\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 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...\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...\n 231|// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\n*232|fn wake_gate_decision(live: bool, claimed: bool, past_deadline: bool) -> WakeGate {\n 233|    if live {\n 234|        WakeGate::AlreadyLive\n*235|    } else if claimed && !past_deadline {\n 236|        WakeGate::Racing\n 237|    } else {\n 238|        WakeGate::Claim\n...\n 288|\n*289|/// The effective commit deadline. Production is the const above; the int gate\n 290|/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n 291|/// so the structural outcome (floor releases → buffered input flushes → binary\n 292|/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n*293|/// the deadline's PRESENCE is the invariant, not its exact length.\n*294|fn inject_commit_deadline() -> Duration {\n 295|    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 296|        Ok(ms) => ms\n 297|            .parse::<u64>()\n...\n 303|\n*304|/// How many CONSECUTIVE commit-deadline misses (no intervening successful\n 305|/// `{commit}`) a translation binary may accrue before the inject worker declares\n 306|/// a REAL fault (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). A single missed\n 307|/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n...\n 313|/// in a row; a commit RESETS the counter. Worst-case stall exposure stays bounded\n*314|/// at N × the deadline (the floor still releases every deadline regardless).\n 315|const INJECT_MISS_STRIKE_BUDGET: u32 = 3;\n 316|\n 317|/// The effective strike budget. Production is the const above; the int gate shrinks\n...\n 362|\n*363|/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n 364|/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n 365|/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n 366|/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n...\n 376|\n*377|/// The effective settle-gate deadline; the int gate tunes it via\n 378|/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n*379|fn inject_settle_deadline() -> Duration {\n 380|    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n 381|        Ok(ms) => ms\n 382|            .parse::<u64>()\n...\n 391|/// verify MISS (round-trip + XON-resume latency bound).\n*392|const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n 393|\n 394|/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n*395|fn inject_echo_verify_deadline() -> Duration {\n 396|    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n 397|        Ok(ms) => ms\n 398|            .parse::<u64>()\n...\n 453|\n*454|/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n 455|/// C-1 addendum (3), REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Under miss≠fault a\n 456|/// respooled zero-record envelope (the checkpoint duplicate FIRE) redelivers on a\n 457|/// later drain → misses again → would respool forever (infinite floor-holds +\n...\n 504|    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\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...\n 552|    /// thread (`Arc<AtomicU64>`). Bumped on every [`become_controller`]; the\n*553|    /// drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it\n 554|    /// observed and only detaches if it still matches — so a slow-controller\n 555|    /// timeout can never unseat a NEWER controller that attached meanwhile. The\n 556|    /// writer snapshots its own epoch and returns the moment this value moves past\n### attach.rs\n 486|                    if since < RESUBSCRIBE_INTERVAL {\n*487|                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n 488|                    }\n 489|                }\n 490|                brain.attach_skip_to_live(sid, Some(origin_node))?;\n### conn.rs\n 23|//! 1. acquires the serialized write GATE with a bounded wait (an ABSOLUTE\n*24|//!    deadline stamped at write entry covers BOTH the gate-wait and the OS\n 25|//!    write);\n 26|//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute\n*27|//!    deadline, then performs the blocking [`write_frame`] OUT of every lock;\n 28|//! 3. on success, returns the half to the gate.\n 29|//!\n*30|//! The watchdog fires OUT OF BAND at the deadline — it never relies on the\n 31|//! write returning, nor on opportunistic stall-evict sampling: it poisons the\n 32|//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows — the\n 33|//! completion APC makes the wedged `SleepEx` loop return with\n 34|//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix — the blocked\n*35|//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires\n 36|//! the same abort (the stuck op it waited behind is the wedge).\n 37|//!\n 38|//! ## Poison / retire invariant (both OSes)\n 39|//!\n*40|//! On deadline, write failure, or cancellation: (1) the WHOLE physical\n 41|//! connection is poisoned, idempotently — a length-prefixed frame may have\n 42|//! been PARTIALLY written, so reuse would corrupt framing; (2) read and write\n 43|//! sides are aborted so `handle_conn` reaches its existing EOF cleanup;\n...\n 88|\n*89|/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\n 90|struct Dog {\n*91|    /// `Some((op_id, deadline))` while a write is in flight.\n 92|    inflight: Option<(u64, Instant)>,\n 93|    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\n 94|    shutdown: bool,\n...\n 103|    dog_cv: Condvar,\n*104|    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\n 105|    /// conn refuses every future write and is never re-gated.\n 106|    poisoned: AtomicBool,\n 107|    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\n...\n 199|        }\n*200|        // Wake gate-waiters so they observe the poison instead of sleeping\n*201|        // out their full deadline, and the watchdog so it re-evaluates.\n 202|        self.gate_cv.notify_all();\n 203|        self.dog_cv.notify_all();\n 204|    }\n...\n 229|\n*230|    /// The watchdog body: park until a write is in flight, then sleep to its\n*231|    /// absolute deadline. If the op is still in flight at the deadline,\n 232|    /// poison + cancel, re-canceling periodically until the op reports\n 233|    /// completion (the writer clears `inflight` the instant `write_frame`\n 234|    /// returns — that return IS the buffer-release handshake), then sever.\n...\n 238|            // Park until armed (or shutdown).\n*239|            let (op, deadline) = loop {\n 240|                if d.shutdown {\n 241|                    return;\n 242|                }\n...\n 250|            };\n*251|            // Sleep toward the deadline while THIS op stays in flight.\n 252|            let fired = loop {\n 253|                if d.shutdown {\n 254|                    return;\n...\n 257|                    Some((cur, _)) if cur == op => {}\n*258|                    // Completed (or a newer op armed) before the deadline.\n 259|                    _ => break false,\n 260|                }\n 261|                let now = Instant::now();\n*262|                if now >= deadline {\n 263|                    break true;\n 264|                }\n 265|                let (g, _) = self\n 266|                    .dog_cv\n*267|                    .wait_timeout(d, deadline - now)\n 268|                    .unwrap_or_else(|p| p.into_inner());\n 269|                d = g;\n 270|            };\n...\n 315|    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\n*316|    /// deadline (gate-wait + OS write completion) — the broker passes its\n*317|    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 318|    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 319|        let raw = raw_of(&half);\n 320|        let inner = Arc::new(Inner {\n...\n 360|        let inner = &*self.inner;\n*361|        let deadline = Instant::now() + inner.bound;\n 362|\n\n## spt/src/\n### rc.rs\n 1161|    // subscribe). Factored out so the B-3 reconnect loop can RE-DRIVE the whole\n*1162|    // sequence after a severed viewport (REQ-RC-RECONNECT). The first attempt\n 1163|    // keeps today's clean-exit copy on a no-target miss.\n 1164|    let est = match establish_attach(endpoint_id, intent) {\n 1165|        Ok(est) => est,\n...\n 1169|        }\n*1170|        // CONNECT-ONLY loud exit (REQ-RC-RECONNECT-TRUTH): the daemon is down and\n 1171|        // rc won't birth one. Plain operator language — what's wrong, what to do —\n 1172|        // never the old freeze-forever \"Reconnecting…\" or a silent WMI spawn.\n*1173|        // [impl->REQ-RC-RECONNECT-TRUTH]\n 1174|        Err(EstablishFail::DaemonDown) => {\n 1175|            println!(\n 1176|                \"The spt daemon isn't running — nothing to attach to. \\\n...\n 1251|        Ok(PumpEnd::ReconnectGaveUp { detail, daemon_down }) => {\n*1252|            // B-3 (REQ-RC-RECONNECT): the reconnect window expired without a\n 1253|            // successful re-attach. Plain operator language (Q4 UX rule): what\n 1254|            // happened, what it means, what to do next — never op/read-err lingo.\n 1255|            if daemon_down {\n*1256|                // Loud daemon-down truth (REQ-RC-RECONNECT-TRUTH): the local daemon\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...\n 1263|                     back with `spt daemon start`, then `spt rc {endpoint_id}`]\",\n*1264|                    RECONNECT_WINDOW.as_secs()\n 1265|                );\n 1266|            } else {\n 1267|                let _ = writeln!(\n...\n 1271|                     {endpoint_id}` again once the node/daemon is back]\",\n*1272|                    RECONNECT_WINDOW.as_secs()\n 1273|                );\n 1274|            }\n 1275|            Ok(())\n...\n 1317|/// F-1 (REQ-PUBLIC-ERROR-SURFACES): the residual attach-failure line is the one\n*1318|/// rc surface that still leaked transport lingo (\"brain IPC read deadline\n 1319|/// elapsed\") to an operator. Map the known daemon-didn't-answer class to the\n 1320|/// observable situation + next action; every OTHER error passes through\n 1321|/// verbatim (no over-matching — unknown failures keep their detail). Pure, so\n...\n 1324|fn public_attach_failure(endpoint_id: &str, e: String) -> String {\n*1325|    if e.contains(\"brain IPC read deadline\") {\n 1326|        format!(\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...\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 1342|    brain: Brain,\n 1343|    ops: OpMinter,\n...\n 1357|    /// The daemon isn't running — rc is CONNECT-ONLY and refuses to auto-start it\n*1358|    /// (REQ-RC-RECONNECT-TRUTH). On the initial attach this is a loud \"daemon\n 1359|    /// isn't running — start it\" exit; inside the reconnect loop it's just another\n 1360|    /// retry (the operator may bring the daemon back), never a resurrection.\n*1361|    /// [impl->REQ-RC-RECONNECT-TRUTH]\n 1362|    DaemonDown,\n 1363|    /// A setup error (broker connect / dial / attach-open / subscribe).\n 1364|    Error(String),\n...\n 1369|/// the A-4b tracing retry) → stream subscribe. Factored from `run_attach_inner`\n*1370|/// so the B-3 reconnect loop can re-drive it verbatim (REQ-RC-RECONNECT).\n 1371|///\n 1372|/// Transport for a LOCAL attach: a broker-minted in-process LOOPBACK connection\n 1373|/// (M12 W1.5, B1). A QUIC self-dial is refused by iroh (\"Connecting to ourself\n...\n 1387|/// a hang (D2/D6).\n*1388|// [impl->REQ-RC-1] [impl->REQ-RC-CROSS-NODE-ATTACH] [impl->REQ-RC-RECONNECT]\n 1389|fn establish_attach(endpoint_id: &str, intent: AttachIntent) -> Result<Established, EstablishFail> {\n*1390|    // CONNECT-ONLY (REQ-RC-RECONNECT-TRUTH): rc is an attach pump, NOT a daemon\n 1391|    // launcher. It must NEVER auto-start the broker — the historical\n 1392|    // `ensure_running()` here WMI-resurrected a daemon on every (re)connect,\n 1393|    // which fought the operator's `spt daemon stop` (the \"stop 2-4 times\" bug)\n...\n 1396|    // loud (initial) or keeps its bounded retry without spawning (reconnect). The\n*1397|    // broker cold-connect below is already deadline-bounded, so a down daemon\n 1398|    // never hangs.\n*1399|    // [impl->REQ-RC-RECONNECT-TRUTH]\n 1400|    if !spt_daemon::daemon::is_running() {\n 1401|        return Err(EstablishFail::DaemonDown);\n 1402|    }\n*1403|    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n 1404|    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n 1405|    // pipes), so the single-threaded pump interleaves stdin + broker events\n 1406|    // without wedging. The generous io_timeout bounds the setup calls (dial,\n...\n 1503|/// Drive an established viewport to its FINAL end: the B-3 reconnect loop\n*1504|/// (REQ-RC-RECONNECT) — each pass is one full viewport (initial resize → pump).\n 1505|/// A severed viewport (broker bounce, remote conn drop — the reconnectable\n 1506|/// class) paints a centered reconnect banner and RE-DRIVES [`establish_attach`]\n 1507|/// inside a bounded window; every other end is FINAL and returns to the\n...\n 1572|        // aborts the retry loop honestly. The banner repaints each iteration with a\n*1573|        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\n 1574|        // and working, never the old apparent freeze at a static \"Reconnecting…\".\n 1575|        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\n 1576|        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\n\n## spt-term/src/\n### reader.rs\n 278|            }\n*279|            std::thread::sleep(Duration::from_millis(5));\n 280|        }\n 281|        let answered = drain.dsr_answered();\n 282|        drain.join();\n### stream.rs\n 45|    /// Block up to `timeout` for the next chunk.\n*46|    pub fn recv_timeout(&self, timeout: Duration) -> Option<Vec<u8>> {\n*47|        self.rx.recv_timeout(timeout).ok()\n 48|    }\n 49|\n 50|    /// The underlying drain — live `bytes_forwarded` / `dsr_answered` counters"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src/broker.rs, spt-core/crates/spt-daemon/src/attach.rs, spt-core/crates/spt-daemon/src/conn.rs, spt-core/crates/spt/src/rc.rs, spt-core/crates/spt-term/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":64,"fileCount":6,"files":["spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/attach.rs","spt-core/crates/spt-daemon/src/conn.rs","spt-core/crates/spt/src/rc.rs","spt-core/crates/spt-term/src/reader.rs","spt-core/crates/spt-term/src/stream.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":20},{"path":"spt-core/crates/spt-daemon/src/attach.rs","count":1},{"path":"spt-core/crates/spt-daemon/src/conn.rs","count":20},{"path":"spt-core/crates/spt/src/rc.rs","count":20},{"path":"spt-core/crates/spt-term/src/reader.rs","count":1},{"path":"spt-core/crates/spt-term/src/stream.rs","count":2}],"truncated":true,"perFileLimitReached":20,"displayContent":"# spt-core/crates/\n\n## spt-daemon/src/\n### broker.rs\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 145│\n 146│/// How long a CONTROLLER's channel may stay FULL (no successful handoff since\n    │...\n 149│/// BOUNDS the wedge: a backed-up controller falls behind for at most this long before\n*150│/// eviction — never forever (the v0.12.x root) — and the drain NEVER blocks or sleeps\n 151│/// (a single off-lock `try_send` per chunk; see [`OutputLog::append`]). A live but\n 152│/// merely slow operator lands a frame before this elapses (resetting `last_ok`) and is\n 153│/// untouched.\n    │...\n 169│/// A slow-but-DRAINING brain never trips it — the clock only advances while a write\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 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    │...\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    │...\n 231│// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\n*232│fn wake_gate_decision(live: bool, claimed: bool, past_deadline: bool) -> WakeGate {\n 233│    if live {\n 234│        WakeGate::AlreadyLive\n*235│    } else if claimed && !past_deadline {\n 236│        WakeGate::Racing\n 237│    } else {\n 238│        WakeGate::Claim\n    │...\n 288│\n*289│/// The effective commit deadline. Production is the const above; the int gate\n 290│/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n 291│/// so the structural outcome (floor releases → buffered input flushes → binary\n 292│/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n*293│/// the deadline's PRESENCE is the invariant, not its exact length.\n*294│fn inject_commit_deadline() -> Duration {\n 295│    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 296│        Ok(ms) => ms\n 297│            .parse::<u64>()\n    │...\n 303│\n*304│/// How many CONSECUTIVE commit-deadline misses (no intervening successful\n 305│/// `{commit}`) a translation binary may accrue before the inject worker declares\n 306│/// a REAL fault (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). A single missed\n 307│/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n    │...\n 313│/// in a row; a commit RESETS the counter. Worst-case stall exposure stays bounded\n*314│/// at N × the deadline (the floor still releases every deadline regardless).\n 315│const INJECT_MISS_STRIKE_BUDGET: u32 = 3;\n 316│\n 317│/// The effective strike budget. Production is the const above; the int gate shrinks\n    │...\n 362│\n*363│/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n 364│/// positive readiness signal before typing the first payload byte. Bounded SHORT so a\n 365│/// non-echoing PTY (ConPTY does not surface a programmatic probe's echo) adds only a\n 366│/// small one-time latency — the gate is a worker-local ONE-SHOT (it protects the first\n    │...\n 376│\n*377│/// The effective settle-gate deadline; the int gate tunes it via\n 378│/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n*379│fn inject_settle_deadline() -> Duration {\n 380│    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n 381│        Ok(ms) => ms\n 382│            .parse::<u64>()\n    │...\n 391│/// verify MISS (round-trip + XON-resume latency bound).\n*392│const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n 393│\n 394│/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n*395│fn inject_echo_verify_deadline() -> Duration {\n 396│    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n 397│        Ok(ms) => ms\n 398│            .parse::<u64>()\n    │...\n 453│\n*454│/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n 455│/// C-1 addendum (3), REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). Under miss≠fault a\n 456│/// respooled zero-record envelope (the checkpoint duplicate FIRE) redelivers on a\n 457│/// later drain → misses again → would respool forever (infinite floor-holds +\n    │...\n 504│    /// writer channel (b4, REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE).\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    │...\n 552│    /// thread (`Arc<AtomicU64>`). Bumped on every [`become_controller`]; the\n*553│    /// drain's deadline-evict ([`mark_controller_gone`]) carries the epoch it\n 554│    /// observed and only detaches if it still matches — so a slow-controller\n 555│    /// timeout can never unseat a NEWER controller that attached meanwhile. The\n 556│    /// writer snapshots its own epoch and returns the moment this value moves past\n### attach.rs\n 486│                    if since < RESUBSCRIBE_INTERVAL {\n*487│                        std::thread::sleep(RESUBSCRIBE_INTERVAL - since);\n 488│                    }\n 489│                }\n 490│                brain.attach_skip_to_live(sid, Some(origin_node))?;\n### conn.rs\n  23│//! 1. acquires the serialized write GATE with a bounded wait (an ABSOLUTE\n *24│//!    deadline stamped at write entry covers BOTH the gate-wait and the OS\n  25│//!    write);\n  26│//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute\n *27│//!    deadline, then performs the blocking [`write_frame`] OUT of every lock;\n  28│//! 3. on success, returns the half to the gate.\n  29│//!\n *30│//! The watchdog fires OUT OF BAND at the deadline — it never relies on the\n  31│//! write returning, nor on opportunistic stall-evict sampling: it poisons the\n  32│//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows — the\n  33│//! completion APC makes the wedged `SleepEx` loop return with\n  34│//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix — the blocked\n *35│//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires\n  36│//! the same abort (the stuck op it waited behind is the wedge).\n  37│//!\n  38│//! ## Poison / retire invariant (both OSes)\n  39│//!\n *40│//! On deadline, write failure, or cancellation: (1) the WHOLE physical\n  41│//! connection is poisoned, idempotently — a length-prefixed frame may have\n  42│//! been PARTIALLY written, so reuse would corrupt framing; (2) read and write\n  43│//! sides are aborted so `handle_conn` reaches its existing EOF cleanup;\n    │...\n  88│\n *89│/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\n  90│struct Dog {\n *91│    /// `Some((op_id, deadline))` while a write is in flight.\n  92│    inflight: Option<(u64, Instant)>,\n  93│    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\n  94│    shutdown: bool,\n    │...\n 103│    dog_cv: Condvar,\n*104│    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\n 105│    /// conn refuses every future write and is never re-gated.\n 106│    poisoned: AtomicBool,\n 107│    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\n    │...\n 199│        }\n*200│        // Wake gate-waiters so they observe the poison instead of sleeping\n*201│        // out their full deadline, and the watchdog so it re-evaluates.\n 202│        self.gate_cv.notify_all();\n 203│        self.dog_cv.notify_all();\n 204│    }\n    │...\n 229│\n*230│    /// The watchdog body: park until a write is in flight, then sleep to its\n*231│    /// absolute deadline. If the op is still in flight at the deadline,\n 232│    /// poison + cancel, re-canceling periodically until the op reports\n 233│    /// completion (the writer clears `inflight` the instant `write_frame`\n 234│    /// returns — that return IS the buffer-release handshake), then sever.\n    │...\n 238│            // Park until armed (or shutdown).\n*239│            let (op, deadline) = loop {\n 240│                if d.shutdown {\n 241│                    return;\n 242│                }\n    │...\n 250│            };\n*251│            // Sleep toward the deadline while THIS op stays in flight.\n 252│            let fired = loop {\n 253│                if d.shutdown {\n 254│                    return;\n    │...\n 257│                    Some((cur, _)) if cur == op => {}\n*258│                    // Completed (or a newer op armed) before the deadline.\n 259│                    _ => break false,\n 260│                }\n 261│                let now = Instant::now();\n*262│                if now >= deadline {\n 263│                    break true;\n 264│                }\n 265│                let (g, _) = self\n 266│                    .dog_cv\n*267│                    .wait_timeout(d, deadline - now)\n 268│                    .unwrap_or_else(|p| p.into_inner());\n 269│                d = g;\n 270│            };\n    │...\n 315│    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\n*316│    /// deadline (gate-wait + OS write completion) — the broker passes its\n*317│    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 318│    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 319│        let raw = raw_of(&half);\n 320│        let inner = Arc::new(Inner {\n    │...\n 360│        let inner = &*self.inner;\n*361│        let deadline = Instant::now() + inner.bound;\n 362│\n\n## spt/src/\n### rc.rs\n 1161│    // subscribe). Factored out so the B-3 reconnect loop can RE-DRIVE the whole\n*1162│    // sequence after a severed viewport (REQ-RC-RECONNECT). The first attempt\n 1163│    // keeps today's clean-exit copy on a no-target miss.\n 1164│    let est = match establish_attach(endpoint_id, intent) {\n 1165│        Ok(est) => est,\n     │...\n 1169│        }\n*1170│        // CONNECT-ONLY loud exit (REQ-RC-RECONNECT-TRUTH): the daemon is down and\n 1171│        // rc won't birth one. Plain operator language — what's wrong, what to do —\n 1172│        // never the old freeze-forever \"Reconnecting…\" or a silent WMI spawn.\n*1173│        // [impl->REQ-RC-RECONNECT-TRUTH]\n 1174│        Err(EstablishFail::DaemonDown) => {\n 1175│            println!(\n 1176│                \"The spt daemon isn't running — nothing to attach to. \\\n     │...\n 1251│        Ok(PumpEnd::ReconnectGaveUp { detail, daemon_down }) => {\n*1252│            // B-3 (REQ-RC-RECONNECT): the reconnect window expired without a\n 1253│            // successful re-attach. Plain operator language (Q4 UX rule): what\n 1254│            // happened, what it means, what to do next — never op/read-err lingo.\n 1255│            if daemon_down {\n*1256│                // Loud daemon-down truth (REQ-RC-RECONNECT-TRUTH): the local daemon\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     │...\n 1263│                     back with `spt daemon start`, then `spt rc {endpoint_id}`]\",\n*1264│                    RECONNECT_WINDOW.as_secs()\n 1265│                );\n 1266│            } else {\n 1267│                let _ = writeln!(\n     │...\n 1271│                     {endpoint_id}` again once the node/daemon is back]\",\n*1272│                    RECONNECT_WINDOW.as_secs()\n 1273│                );\n 1274│            }\n 1275│            Ok(())\n     │...\n 1317│/// F-1 (REQ-PUBLIC-ERROR-SURFACES): the residual attach-failure line is the one\n*1318│/// rc surface that still leaked transport lingo (\"brain IPC read deadline\n 1319│/// elapsed\") to an operator. Map the known daemon-didn't-answer class to the\n 1320│/// observable situation + next action; every OTHER error passes through\n 1321│/// verbatim (no over-matching — unknown failures keep their detail). Pure, so\n     │...\n 1324│fn public_attach_failure(endpoint_id: &str, e: String) -> String {\n*1325│    if e.contains(\"brain IPC read deadline\") {\n 1326│        format!(\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     │...\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 1342│    brain: Brain,\n 1343│    ops: OpMinter,\n     │...\n 1357│    /// The daemon isn't running — rc is CONNECT-ONLY and refuses to auto-start it\n*1358│    /// (REQ-RC-RECONNECT-TRUTH). On the initial attach this is a loud \"daemon\n 1359│    /// isn't running — start it\" exit; inside the reconnect loop it's just another\n 1360│    /// retry (the operator may bring the daemon back), never a resurrection.\n*1361│    /// [impl->REQ-RC-RECONNECT-TRUTH]\n 1362│    DaemonDown,\n 1363│    /// A setup error (broker connect / dial / attach-open / subscribe).\n 1364│    Error(String),\n     │...\n 1369│/// the A-4b tracing retry) → stream subscribe. Factored from `run_attach_inner`\n*1370│/// so the B-3 reconnect loop can re-drive it verbatim (REQ-RC-RECONNECT).\n 1371│///\n 1372│/// Transport for a LOCAL attach: a broker-minted in-process LOOPBACK connection\n 1373│/// (M12 W1.5, B1). A QUIC self-dial is refused by iroh (\"Connecting to ourself\n     │...\n 1387│/// a hang (D2/D6).\n*1388│// [impl->REQ-RC-1] [impl->REQ-RC-CROSS-NODE-ATTACH] [impl->REQ-RC-RECONNECT]\n 1389│fn establish_attach(endpoint_id: &str, intent: AttachIntent) -> Result<Established, EstablishFail> {\n*1390│    // CONNECT-ONLY (REQ-RC-RECONNECT-TRUTH): rc is an attach pump, NOT a daemon\n 1391│    // launcher. It must NEVER auto-start the broker — the historical\n 1392│    // `ensure_running()` here WMI-resurrected a daemon on every (re)connect,\n 1393│    // which fought the operator's `spt daemon stop` (the \"stop 2-4 times\" bug)\n     │...\n 1396│    // loud (initial) or keeps its bounded retry without spawning (reconnect). The\n*1397│    // broker cold-connect below is already deadline-bounded, so a down daemon\n 1398│    // never hangs.\n*1399│    // [impl->REQ-RC-RECONNECT-TRUTH]\n 1400│    if !spt_daemon::daemon::is_running() {\n 1401│        return Err(EstablishFail::DaemonDown);\n 1402│    }\n*1403│    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline\n 1404│    // (a `Whole` conn can't time out — nonblocking is dead on Windows named\n 1405│    // pipes), so the single-threaded pump interleaves stdin + broker events\n 1406│    // without wedging. The generous io_timeout bounds the setup calls (dial,\n     │...\n 1503│/// Drive an established viewport to its FINAL end: the B-3 reconnect loop\n*1504│/// (REQ-RC-RECONNECT) — each pass is one full viewport (initial resize → pump).\n 1505│/// A severed viewport (broker bounce, remote conn drop — the reconnectable\n 1506│/// class) paints a centered reconnect banner and RE-DRIVES [`establish_attach`]\n 1507│/// inside a bounded window; every other end is FINAL and returns to the\n     │...\n 1572│        // aborts the retry loop honestly. The banner repaints each iteration with a\n*1573│        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\n 1574│        // and working, never the old apparent freeze at a static \"Reconnecting…\".\n 1575│        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\n 1576│        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\n\n## spt-term/src/\n### reader.rs\n 278│            }\n*279│            std::thread::sleep(Duration::from_millis(5));\n 280│        }\n 281│        let answered = drain.dsr_answered();\n 282│        drain.join();\n### stream.rs\n 45│    /// Block up to `timeout` for the next chunk.\n*46│    pub fn recv_timeout(&self, timeout: Duration) -> Option<Vec<u8>> {\n*47│        self.rx.recv_timeout(timeout).ok()\n 48│    }\n 49│\n 50│    /// The underlying drain — live `bytes_forwarded` / `dsr_answered` counters"},"isError":false,"timestamp":1783730632804}}
{"type":"message","id":"525539ff","parentId":"0b009ab1","timestamp":"2026-07-11T00:44:05.675Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Identifying synchronous stdout blocking**\n\n<!-- -->\n\n**Analyzing unbounded stdout write implications**\n\n<!-- -->"},{"type":"toolCall","id":"call_sE3qr9GbaOltUxMGxCj32d8F|fc_0a129b8eba4c900a016a5191d6043c819aa3a2e7f092227f3a","name":"grep","arguments":{"i":"Locate pump grace constants","pattern":"POLL_SLICE|FIRST_EVENT_GRACE|first_event_stalled","path":"spt-core/crates/spt/src/rc.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_lwQIsvhkxBTLYUObZeG5TM3U|fc_0a129b8eba4c900a016a5191d6045c819aa654f0a6bc377583","name":"grep","arguments":{"i":"Audit direct terminal output","pattern":"stdout\\.write_all|out\\.write_all|stdout\\.flush|out\\.flush","path":"spt-core/crates/spt/src/rc.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_sUMsbqPZJ4toSwL1zIyyWJxw|fc_0a129b8eba4c900a016a5191d60464819a95e414ed9d624cd3","name":"grep","arguments":{"i":"Audit broker transport construction","pattern":"BrokerConn::new|brain_write_deadline\\(\\)","path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_NZ8bqdnpbEHqyTiWyL1e0IqZ|fc_0a129b8eba4c900a016a5191d6046c819a8cd665ba37793fc9","name":"read","arguments":{"i":"Inspect pump constants","path":"spt-core/crates/spt/src/rc.rs","selector":"1780-1825,1935-1980"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":18390,"output":576,"cacheRead":164352,"cacheWrite":0,"totalTokens":183318,"reasoningTokens":301,"cost":{"input":0.09195,"output":0.01728,"cacheRead":0.082176,"cacheWrite":0,"total":0.19140600000000002}},"stopReason":"toolUse","timestamp":1783730633021,"responseId":"resp_0a129b8eba4c900a016a5191c9c474819abdbb1410a2cf8060","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a129b8eba4c900a016a5191cb35f0819ab88318be0b31ab74","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZHWSxE_3JtFBIsqJnIvvAo3zSK8PQoAXCZThivzcAcnVS-EjW-5-Kftw8HtH8rNqOOYrrm2QcSL2itpyqB5qcgUY2nxetGdRmP2qvvmS1Btq4I58qdglxaa5apVfm1utWX3g2GL_8shOaC1QIHa8g5yufcTpmq6PiMW4BLuDN6EUu3-2grG0fAx7txMpRSOv6-G5wI5Sno_gy7VhIKoHiGWGVwBMVNo68Q-hEQdk8INYlPHBpipEmpDwaB4DoG69b5zHYOm5r4q8YiP9gnfGLUzyzjEi1G2bhYJh4ZqJiVwi3x2fUdItq6qSqwx4e0rniKr3h8RCS4OLSCJ8i1XsP8Q9VOjgJO05U9zRI1NF2Fe1-b46MKv2ABT8xcbVfGyAkRpm2E17OZOhpdiBNR9CZOGbs23iDZe3BAsR3b5yvsDozYRMNfeJu5UJXAK5BrQbigz89OMPnpzodAf8t47vkrMjGRKCKQO5KcftQ1wF_kCe6FkS6Nl8YIA0nezGzb8NyXgWRHP1hGjE86PL0kKfbiP-fgMEO--VyuKzocyyc77WPMZOAN81ULWKHj7BkyH4YbYES72wHQVE9WkbKBHDPBJ_5Th5B4i9V-vav1iilp4f0nHsvb3P-H5_bmkUWvUbv7JO5SlW-rF6h-ATZr3huK110OC21S-AI2NzFkP-4R-WcuU2pmaw9U1LOVKSXnkn1ELFtOkSp7JnubnreMtTxO0HJ_0HXTI_JTSZwRaFKTYATu8sC7YsHS_Qzp1d3pLD6Rv4mH546xYgXZASsRXMjcnrB68YfD0vRczC8860GWG7Avvm9nTyG5ovX6iedi2yzE201ydZkD_wtYSqOkzZgBUiHXdSYZ4gtKsWm2ac45RLtk3Y-zXoOo3Ycsz5Eygb-2mJMZPn5pNwZdacMlSjWko_Ih1a1bf1r91-GkKzbrcDdkOGI1s-KBUP4XWVU8PcJe5TUhrEYdQbxW2kuWoKD_XuGioel0S-Fl-KYIn3A1N-Nc-pIv5i0R2wdIPTmMFpOI0GnX9KttVbmtm1FNFjxdPTHOvP9h1ISVCj5fcSwXkiFNhDYRvkzBvQjikDJ-ss05oM-GJvpKMKqUmh4DiXindPBBoAvaZTdsoojjcoTjhTND0EopeQW1qAA9dsCl6kuNGu-sBP84Nyd6eUTE0uOiRyxLZa92x_MFZt1dyuRB-ilSzSr68ot8-NGEj_6lCSIHI3dWctyHf-wrw7ppnbO-nMokt5KiF6ATtgDY5DWAEPsfhcYp7h4fTW864cxFhZTJba3YwS_5aQXnTil6mpUlLdF3sR97TQpqxnycgiD-jt_eTojgQzBQMiZv1yZqooWCAxxIM6k4PdjNlbEoJqRJ7h9RaintjTL2ErFnDufFeFq1kAjtjjowrgrV1EHc9y6-Mjx_Pph_dbUIH4E5tMokYoOftvJZSywL9LFHwee7300cHLOpJq_o1OcvmdCFfNmb9U2e9pFG2F4ryQdRceo9VC6Jes7eO-qwshaunVIq0gLXFYZPXrpkqlwQOglx8GuMtg6pYc7brujcGZxKqpOOmdfaMrAYGXj0Gp3vidY-hr6UICVihYBy3IXa88g7tfMEP97XmUFMTurDasGSFVit8tMKKYxKSMC0oJzve-kbVEplRVtzDHfMY5dTvJMpyoAc7ms0yicyGSDInYrxXHdU3idVyLsB58VkzQBzQhKcnyL9Kv2v2jtg5P1bMcn57sB2ED5o0BrHwCKO2-leivxrhlZI4GGfTFYjyc2eUb4kHJpPo4vloziW5xfV9IW3AwsSPH3FLNJ3Nm8BiQcKlhAbx7gcEGh21VFvYpcSReRHN6iPG8Yi2nWU4U38N-2HE9r_j-6WbUaRmL-Zm6gJ-Zpf8HRJUWkePQO-e3bh350UJaaDpB4xBuzYZXSWAWNjXzAKkHAq7xf5E6yBUtyUOoXAC-P7fat8eRDGOSniMpnqBzj-Dhq6GSjgIvRalM6qQ07TcHXhbdexTxuenjwuoivj_5KEtvC5X0qNS8L0bKNYBeX45uTV_EptTLGl1BHYlwallAefwHYp_cBtrIhtsMBjGs6AqBzTYtLxiwlSWHfSvGAB-ym2oXl-CnwYpijkF8xF3M2WqZ3gDgGWwunWS8-r8MoHJxLtWciChybf4OeJDHztDTmAz9odlpQtWaYuKbnO0JxJsK-WlGaDvRoDGNLQK5qrOwwZ1IVdYQxbPJ-94UDb8XxnoXbgAKQFzl8bMQTUoQLqLrx1qcg7kalrdEOcFjBrz8uFyxlsoiOkvAj3Yh7a8eZphng9UKcByq99G8RhHjqvqTWyTEBhJWElONYNO7n4XzwAuhuGyxev7jNEcnw76ng9xWTw4j4XqImlgE9pWEQSJ55YDEexkskyK1sVKXHt0SshbIktIwCxU2yn4TIXbndVN5Ac240Cp6gDke9eYqs0JcWFm25Ievt31ZsBDIX_RC9t5s7did15LGEULISMg_b8MTd_Dn2GAJFKtcthyfIZhH_QT5Mk-fWakfw0RO6GvqLfcmWm2Keub3i7FgthK6zzkquh6xgKDnSJE4HEufTFa04LHuS7oPTYfBn8R_HPj9AW4zG232Z6ljFeGmasd-OAMZ5bQ2swLJnNAHSFU8CmKYLYig0_Jgy1oux9X9Lubp1Oxjp0BaypckIXf684qjkQg4F6G61g6HlNazbadbtk_hF-hJHAmjBRnRRHBS76xXC6hv0OyYINggvEb1rxg6-LqjO_O4Mh8ypqTchUfh_OMQcC7eGrqp-WphP7Az0sIY4MsDMCx6bSR6sBzt83EOE-UZdiZ38E5lk8cDh1T98OzFFuyd4yHsejcHH2el42coCADjri0E6UBViYVVUxSmv81IQyluiq9AolsGzKDWxstxs1nbfn6UnLrXZ4KklUVTJhEYCEOd58TXDmZo7isj92smYR35JNEeYr_T-fq_5uENCFFfj686pqql7A8cxWcxpJhiUeGCX6tgSIm3o82Y2bf2BiPttPeBdOPBV9Z","summary":[{"type":"summary_text","text":"**Identifying synchronous stdout blocking**\n\n<!-- -->"},{"type":"summary_text","text":"**Analyzing unbounded stdout write implications**\n\n<!-- -->"}]},{"id":"fc_0a129b8eba4c900a016a5191d6043c819aa3a2e7f092227f3a","type":"function_call","status":"completed","arguments":"{\"i\":\"Locate pump grace constants\",\"pattern\":\"POLL_SLICE|FIRST_EVENT_GRACE|first_event_stalled\",\"path\":\"spt-core/crates/spt/src/rc.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_sE3qr9GbaOltUxMGxCj32d8F","name":"grep"},{"id":"fc_0a129b8eba4c900a016a5191d6045c819aa654f0a6bc377583","type":"function_call","status":"completed","arguments":"{\"i\":\"Audit direct terminal output\",\"pattern\":\"stdout\\\\.write_all|out\\\\.write_all|stdout\\\\.flush|out\\\\.flush\",\"path\":\"spt-core/crates/spt/src/rc.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_lwQIsvhkxBTLYUObZeG5TM3U","name":"grep"},{"id":"fc_0a129b8eba4c900a016a5191d60464819a95e414ed9d624cd3","type":"function_call","status":"completed","arguments":"{\"i\":\"Audit broker transport construction\",\"pattern\":\"BrokerConn::new|brain_write_deadline\\\\(\\\\)\",\"path\":\"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_sUMsbqPZJ4toSwL1zIyyWJxw","name":"grep"},{"id":"fc_0a129b8eba4c900a016a5191d6046c819a8cd665ba37793fc9","type":"function_call","status":"completed","arguments":"{\"i\":\"Inspect pump constants\",\"path\":\"spt-core/crates/spt/src/rc.rs\",\"selector\":\"1780-1825,1935-1980\"}","call_id":"call_NZ8bqdnpbEHqyTiWyL1e0IqZ","name":"read"}]},"duration":12530.887099999934,"ttft":1578.1082999999635,"contextSnapshot":{"promptTokens":182742,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_sE3qr9GbaOltUxMGxCj32d8F|fc_0a129b8eba4c900a016a5191d6043c819aa3a2e7f092227f3a","toolName":"grep","startedAt":"2026-07-11T00:44:05.676Z","args":{"path":"spt-core/crates/spt/src/rc.rs"},"intent":"Locate pump grace constants"},"id":"3b175b0e","parentId":"525539ff","timestamp":"2026-07-11T00:44:05.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_lwQIsvhkxBTLYUObZeG5TM3U|fc_0a129b8eba4c900a016a5191d6045c819aa654f0a6bc377583","toolName":"grep","startedAt":"2026-07-11T00:44:05.676Z","args":{"path":"spt-core/crates/spt/src/rc.rs"},"intent":"Audit direct terminal output"},"id":"43a23803","parentId":"3b175b0e","timestamp":"2026-07-11T00:44:05.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_sUMsbqPZJ4toSwL1zIyyWJxw|fc_0a129b8eba4c900a016a5191d60464819a95e414ed9d624cd3","toolName":"grep","startedAt":"2026-07-11T00:44:05.676Z","args":{"path":"spt-core/crates/spt-daemon/src/broker.rs; spt-core/crates/spt-daemon/src"},"intent":"Audit broker transport construction"},"id":"02553212","parentId":"43a23803","timestamp":"2026-07-11T00:44:05.676Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_NZ8bqdnpbEHqyTiWyL1e0IqZ|fc_0a129b8eba4c900a016a5191d6046c819a8cd665ba37793fc9","toolName":"read","startedAt":"2026-07-11T00:44:05.676Z","args":{"path":"spt-core/crates/spt/src/rc.rs"},"intent":"Inspect pump constants"},"id":"ab248156","parentId":"02553212","timestamp":"2026-07-11T00:44:05.676Z"}
{"type":"message","id":"2b6a2c73","parentId":"ab248156","timestamp":"2026-07-11T00:44:05.681Z","message":{"role":"toolResult","toolCallId":"call_sE3qr9GbaOltUxMGxCj32d8F|fc_0a129b8eba4c900a016a5191d6043c819aa3a2e7f092227f3a","toolName":"grep","content":[{"type":"text","text":" 43|/// drain stdin — the full-duplex interleave granularity.\n*44|const POLL_SLICE: Duration = Duration::from_millis(40);\n 45|\n 46|/// A message from the stdin reader thread to the pump.\n 47|enum StdinMsg {\n...\n 1768|/// true hang, never the working fresh-attach.\n*1769|const FIRST_EVENT_GRACE: Duration = Duration::from_secs(30);\n 1770|\n 1771|/// The path-b stall decision (REQ-HAZARD-RC-ATTACH-FAILFAST): the attach has\n 1772|/// stalled iff NO broker event has ever arrived AND the generous grace has\n...\n 1774|// [impl->REQ-HAZARD-RC-ATTACH-FAILFAST]\n*1775|fn first_event_stalled(seen_any: bool, elapsed: Duration) -> bool {\n*1776|    !seen_any && elapsed >= FIRST_EVENT_GRACE\n 1777|}\n 1778|\n 1779|/// How the rc pump treats a `read_event_until` error (REQ-HAZARD-RC-EOF).\n...\n 1860|    // First-event backstop (REQ-HAZARD-RC-ATTACH-FAILFAST path b): a dead/wedged\n*1861|    // attach produces nothing — bail with a clear message past FIRST_EVENT_GRACE\n 1862|    // rather than an infinite blank. Any broker event clears it.\n 1863|    let started = Instant::now();\n 1864|    let mut seen_any = false;\n...\n 1914|        // ── session → terminal ─────────────────────────────────────────────\n*1915|        let ev = match brain.read_event_until(Some(Instant::now() + POLL_SLICE)) {\n 1916|            Ok(ev) => ev,\n 1917|            Err(e) => match classify_read_err(e.kind()) {\n 1918|                // A poll-slice timeout: no event this slice. Before looping, the\n...\n 1921|                ReadDisposition::Retry => {\n*1922|                    if first_event_stalled(seen_any, started.elapsed()) {\n 1923|                        return Ok(PumpEnd::Stalled);\n 1924|                    }\n 1925|                    continue;\n...\n 2063|        // No event + past the generous grace → stalled.\n*2064|        assert!(first_event_stalled(false, FIRST_EVENT_GRACE));\n*2065|        assert!(first_event_stalled(false, FIRST_EVENT_GRACE + Duration::from_secs(1)));\n 2066|        // Any event seen → never stalled (the working/mid-init attach guard).\n*2067|        assert!(!first_event_stalled(true, FIRST_EVENT_GRACE + Duration::from_secs(60)));\n 2068|        // Before the grace → not yet (a slow-painting session still has time).\n*2069|        assert!(!first_event_stalled(false, Duration::from_secs(1)));\n*2070|        assert!(!first_event_stalled(false, FIRST_EVENT_GRACE - Duration::from_millis(1)));\n 2071|    }\n 2072|\n 2073|    /// Shared capture sink for a headless viewport drive (the seam's `out`)."}],"details":{"scopePath":"spt-core/crates/spt/src/rc.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":12,"fileCount":1,"files":["spt-core/crates/spt/src/rc.rs"],"fileMatches":[{"path":"spt-core/crates/spt/src/rc.rs","count":12}],"truncated":false,"displayContent":"   43│/// drain stdin — the full-duplex interleave granularity.\n  *44│const POLL_SLICE: Duration = Duration::from_millis(40);\n   45│\n   46│/// A message from the stdin reader thread to the pump.\n   47│enum StdinMsg {\n     │...\n 1768│/// true hang, never the working fresh-attach.\n*1769│const FIRST_EVENT_GRACE: Duration = Duration::from_secs(30);\n 1770│\n 1771│/// The path-b stall decision (REQ-HAZARD-RC-ATTACH-FAILFAST): the attach has\n 1772│/// stalled iff NO broker event has ever arrived AND the generous grace has\n     │...\n 1774│// [impl->REQ-HAZARD-RC-ATTACH-FAILFAST]\n*1775│fn first_event_stalled(seen_any: bool, elapsed: Duration) -> bool {\n*1776│    !seen_any && elapsed >= FIRST_EVENT_GRACE\n 1777│}\n 1778│\n 1779│/// How the rc pump treats a `read_event_until` error (REQ-HAZARD-RC-EOF).\n     │...\n 1860│    // First-event backstop (REQ-HAZARD-RC-ATTACH-FAILFAST path b): a dead/wedged\n*1861│    // attach produces nothing — bail with a clear message past FIRST_EVENT_GRACE\n 1862│    // rather than an infinite blank. Any broker event clears it.\n 1863│    let started = Instant::now();\n 1864│    let mut seen_any = false;\n     │...\n 1914│        // ── session → terminal ─────────────────────────────────────────────\n*1915│        let ev = match brain.read_event_until(Some(Instant::now() + POLL_SLICE)) {\n 1916│            Ok(ev) => ev,\n 1917│            Err(e) => match classify_read_err(e.kind()) {\n 1918│                // A poll-slice timeout: no event this slice. Before looping, the\n     │...\n 1921│                ReadDisposition::Retry => {\n*1922│                    if first_event_stalled(seen_any, started.elapsed()) {\n 1923│                        return Ok(PumpEnd::Stalled);\n 1924│                    }\n 1925│                    continue;\n     │...\n 2063│        // No event + past the generous grace → stalled.\n*2064│        assert!(first_event_stalled(false, FIRST_EVENT_GRACE));\n*2065│        assert!(first_event_stalled(false, FIRST_EVENT_GRACE + Duration::from_secs(1)));\n 2066│        // Any event seen → never stalled (the working/mid-init attach guard).\n*2067│        assert!(!first_event_stalled(true, FIRST_EVENT_GRACE + Duration::from_secs(60)));\n 2068│        // Before the grace → not yet (a slow-painting session still has time).\n*2069│        assert!(!first_event_stalled(false, Duration::from_secs(1)));\n*2070│        assert!(!first_event_stalled(false, FIRST_EVENT_GRACE - Duration::from_millis(1)));\n 2071│    }\n 2072│\n 2073│    /// Shared capture sink for a headless viewport drive (the seam's `out`)."},"isError":false,"timestamp":1783730645681}}
{"type":"message","id":"1f4fe48a","parentId":"2b6a2c73","timestamp":"2026-07-11T00:44:05.683Z","message":{"role":"toolResult","toolCallId":"call_lwQIsvhkxBTLYUObZeG5TM3U|fc_0a129b8eba4c900a016a5191d6045c819aa654f0a6bc377583","toolName":"grep","content":[{"type":"text","text":" 1228|    if status.is_some() {\n*1229|        let _ = stdout.write_all(&StatusRow::restore_bytes());\n*1230|        let _ = stdout.flush();\n 1231|    }\n 1232|    // Out of raw mode (guard drops after) before the parting line.\n 1233|    drop(_raw);\n...\n 1585|            let remaining = reconnect_remaining_secs(elapsed);\n*1586|            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n*1587|            let _ = out.flush();\n 1588|            // Operator gives up mid-reconnect: honor the detach keys.\n 1589|            if let Some(rx) = rx {\n 1590|                let mut detached = false;\n...\n 1838|            .map_err(|e| format!(\"status assert: {e}\"))?;\n*1839|        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840|    }\n 1841|    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n 1842|    // (the session outlives the viewport). Best-effort: an already-finished or\n...\n 1884|                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885|                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n*1886|                                let _ = stdout.flush();\n 1887|                            }\n 1888|                        }\n 1889|                        None => {\n...\n 1948|                            mouse_scanner.feed(mouse_mode, &bytes);\n*1949|                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n 1950|                            // Re-assert the reserved row if the harness output just\n 1951|                            // destroyed our scroll region — alt-screen enter or a\n 1952|                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n...\n 1964|                            }\n*1965|                            stdout.flush().map_err(|e| format!(\"flush: {e}\"))?;\n 1966|                        }\n 1967|                        AttachRecord::Exit { code } => return Ok(PumpEnd::Exited(code)),\n 1968|                        AttachRecord::Displaced { by } => {\n...\n 1982|                                    );\n*1983|                                    let _ = stdout.flush();\n 1984|                                }\n 1985|                                _ => {}\n 1986|                            }"}],"details":{"scopePath":"spt-core/crates/spt/src/rc.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":10,"fileCount":1,"files":["spt-core/crates/spt/src/rc.rs"],"fileMatches":[{"path":"spt-core/crates/spt/src/rc.rs","count":10}],"truncated":false,"displayContent":" 1228│    if status.is_some() {\n*1229│        let _ = stdout.write_all(&StatusRow::restore_bytes());\n*1230│        let _ = stdout.flush();\n 1231│    }\n 1232│    // Out of raw mode (guard drops after) before the parting line.\n 1233│    drop(_raw);\n     │...\n 1585│            let remaining = reconnect_remaining_secs(elapsed);\n*1586│            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n*1587│            let _ = out.flush();\n 1588│            // Operator gives up mid-reconnect: honor the detach keys.\n 1589│            if let Some(rx) = rx {\n 1590│                let mut detached = false;\n     │...\n 1838│            .map_err(|e| format!(\"status assert: {e}\"))?;\n*1839│        stdout.flush().map_err(|e| format!(\"status flush: {e}\"))?;\n 1840│    }\n 1841│    // Finish our send side so the target's `serve_attach` sees EOF → Detached\n 1842│    // (the session outlives the viewport). Best-effort: an already-finished or\n     │...\n 1884│                            let _ = send_attach_resize(brain, stream_id, pty_rows, cols);\n*1885│                            if stdout.write_all(&status.assert_bytes()).is_ok() {\n*1886│                                let _ = stdout.flush();\n 1887│                            }\n 1888│                        }\n 1889│                        None => {\n     │...\n 1948│                            mouse_scanner.feed(mouse_mode, &bytes);\n*1949│                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n 1950│                            // Re-assert the reserved row if the harness output just\n 1951│                            // destroyed our scroll region — alt-screen enter or a\n 1952│                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n     │...\n 1964│                            }\n*1965│                            stdout.flush().map_err(|e| format!(\"flush: {e}\"))?;\n 1966│                        }\n 1967│                        AttachRecord::Exit { code } => return Ok(PumpEnd::Exited(code)),\n 1968│                        AttachRecord::Displaced { by } => {\n     │...\n 1982│                                    );\n*1983│                                    let _ = stdout.flush();\n 1984│                                }\n 1985│                                _ => {}\n 1986│                            }"},"isError":false,"timestamp":1783730645682}}
{"type":"message","id":"c0300c8e","parentId":"1f4fe48a","timestamp":"2026-07-11T00:44:05.800Z","message":{"role":"toolResult","toolCallId":"call_NZ8bqdnpbEHqyTiWyL1e0IqZ|fc_0a129b8eba4c900a016a5191d6046c819a8cd665ba37793fc9","toolName":"read","content":[{"type":"text","text":"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ReadDisposition {\n    /// Transient poll-slice timeout — loop and poll again.\n    Retry,\n    /// Severed broker stream (broker bounced / hung up) — recoverable, surface\n    /// gracefully.\n    BrokerGone,\n    /// A genuine read failure — propagate.\n    Fatal,\n}\n\n/// Classify a `read_event_until` error kind. A poll-slice timeout retries; a\n/// severed-broker-stream EOF (the broker bounced — e.g. `spt daemon stop` —\n/// surfacing as `UnexpectedEof` \"failed to fill whole buffer\", or a\n/// reset/abort/broken-pipe in the same class) is RECOVERABLE and surfaces\n/// gracefully rather than crashing the PTY with the raw io text (the same EOF\n/// family the v0.9.1 seed fix handles); anything else is fatal.\n// [impl->REQ-HAZARD-RC-EOF]\nfn classify_read_err(kind: std::io::ErrorKind) -> ReadDisposition {\n    use std::io::ErrorKind::*;\n    match kind {\n        WouldBlock | TimedOut => ReadDisposition::Retry,\n        UnexpectedEof | BrokenPipe | ConnectionReset | ConnectionAborted => {\n            ReadDisposition::BrokerGone\n        }\n        _ => ReadDisposition::Fatal,\n    }\n}\n\n/// The full-duplex pump: drain stdin → `send_attach_input`; render inbound\n/// `AttachRecord::Output` → stdout; end on `Exit`, EOF, or detach.\n#[allow(clippy::too_many_arguments)]\nfn pump(\n    brain: &mut Brain,\n    stream_id: u64,\n    ops: &OpMinter,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    decoder: &mut AttachDecoder,\n    stdout: &mut impl std::io::Write,\n    controller: bool,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n) -> Result<PumpEnd, String> {\n    // Track the harness's mouse-reporting mode from its output DECSET sequences so\n    // the stdin reader knows whether to forward scroll (REQ-RC-MOUSE-FORWARD). The\n    // scanner is pump-local (single producer) and survives a sequence split across\n…\n            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Output { seq, data_b64 } => {\n                            if seq < cursor {\n                                continue; // already rendered — re-serve dedup\n                            }\n                            cursor = seq + 1;\n                            rendered_any = true;\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| format!(\"decode output: {e}\"))?;\n                            // Track the harness's mouse-reporting mode from its output\n                            // (REQ-RC-MOUSE-FORWARD) before rendering it verbatim.\n                            mouse_scanner.feed(mouse_mode, &bytes);\n                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n                            // Re-assert the reserved row if the harness output just\n                            // destroyed our scroll region — alt-screen enter or a\n                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n                            // margin is gone (alt-screen has its own full-screen\n                            // margins; a DECSTBM reset clears ours), so re-set the\n                            // MARGIN + repaint (assert_bytes), not just the text —\n                            // else the harness scrolls back over row 1. Scanned on the\n                            // SAME bytes we rendered, after writing them.\n                            if let Some(status) = status.as_deref_mut() {\n                                if reassert_scanner.feed(&bytes) {\n                                    stdout\n                                        .write_all(&status.assert_bytes())\n                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n                                }\n                            }\n                            stdout.flush().map_err(|e| format!(\"flush: {e}\"))?;\n                        }\n                        AttachRecord::Exit { code } => return Ok(PumpEnd::Exited(code)),\n                        AttachRecord::Displaced { by } => {\n                            return Ok(PumpEnd::Displaced(by));\n                        }\n                        // Viewer letterbox feed (REQ-RCVIEW-1): on a size change,\n                        // show a one-line indicator when our terminal differs from\n                        // the new PTY size (rc is a byte pump — true clip/pad needs\n                        // a grid model, deferred; the honest size cue renders here).\n                        AttachRecord::Size { rows, cols } if pty_size != Some((rows, cols)) => {\n                            pty_size = Some((rows, cols));\n                            match crossterm::terminal::size() {\n                                Ok((mycols, myrows)) if (myrows, mycols) != (rows, cols) => {\n                                    let _ = write!(\n                                        stdout,\n…\n                                    );\n…\n                                }\n…\n                            }\n                        }\n…\n                    }\n                }\n            }\n…\n}"}],"details":{"displayContent":{"text":"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ReadDisposition {\n    /// Transient poll-slice timeout — loop and poll again.\n    Retry,\n    /// Severed broker stream (broker bounced / hung up) — recoverable, surface\n    /// gracefully.\n    BrokerGone,\n    /// A genuine read failure — propagate.\n    Fatal,\n}\n\n/// Classify a `read_event_until` error kind. A poll-slice timeout retries; a\n/// severed-broker-stream EOF (the broker bounced — e.g. `spt daemon stop` —\n/// surfacing as `UnexpectedEof` \"failed to fill whole buffer\", or a\n/// reset/abort/broken-pipe in the same class) is RECOVERABLE and surfaces\n/// gracefully rather than crashing the PTY with the raw io text (the same EOF\n/// family the v0.9.1 seed fix handles); anything else is fatal.\n// [impl->REQ-HAZARD-RC-EOF]\nfn classify_read_err(kind: std::io::ErrorKind) -> ReadDisposition {\n    use std::io::ErrorKind::*;\n    match kind {\n        WouldBlock | TimedOut => ReadDisposition::Retry,\n        UnexpectedEof | BrokenPipe | ConnectionReset | ConnectionAborted => {\n            ReadDisposition::BrokerGone\n        }\n        _ => ReadDisposition::Fatal,\n    }\n}\n\n/// The full-duplex pump: drain stdin → `send_attach_input`; render inbound\n/// `AttachRecord::Output` → stdout; end on `Exit`, EOF, or detach.\n#[allow(clippy::too_many_arguments)]\nfn pump(\n    brain: &mut Brain,\n    stream_id: u64,\n    ops: &OpMinter,\n    rx: Option<&mpsc::Receiver<StdinMsg>>,\n    decoder: &mut AttachDecoder,\n    stdout: &mut impl std::io::Write,\n    controller: bool,\n    mouse_mode: &MouseMode,\n    mut status: Option<&mut StatusRow>,\n) -> Result<PumpEnd, String> {\n    // Track the harness's mouse-reporting mode from its output DECSET sequences so\n    // the stdin reader knows whether to forward scroll (REQ-RC-MOUSE-FORWARD). The\n    // scanner is pump-local (single producer) and survives a sequence split across\n…\n            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Output { seq, data_b64 } => {\n                            if seq < cursor {\n                                continue; // already rendered — re-serve dedup\n                            }\n                            cursor = seq + 1;\n                            rendered_any = true;\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| format!(\"decode output: {e}\"))?;\n                            // Track the harness's mouse-reporting mode from its output\n                            // (REQ-RC-MOUSE-FORWARD) before rendering it verbatim.\n                            mouse_scanner.feed(mouse_mode, &bytes);\n                            stdout.write_all(&bytes).map_err(|e| format!(\"write: {e}\"))?;\n                            // Re-assert the reserved row if the harness output just\n                            // destroyed our scroll region — alt-screen enter or a\n                            // DECSTBM reset (REQ-RC-IDENTITY). The trigger MEANS the\n                            // margin is gone (alt-screen has its own full-screen\n                            // margins; a DECSTBM reset clears ours), so re-set the\n                            // MARGIN + repaint (assert_bytes), not just the text —\n                            // else the harness scrolls back over row 1. Scanned on the\n                            // SAME bytes we rendered, after writing them.\n                            if let Some(status) = status.as_deref_mut() {\n                                if reassert_scanner.feed(&bytes) {\n                                    stdout\n                                        .write_all(&status.assert_bytes())\n                                        .map_err(|e| format!(\"status reassert: {e}\"))?;\n                                }\n                            }\n                            stdout.flush().map_err(|e| format!(\"flush: {e}\"))?;\n                        }\n                        AttachRecord::Exit { code } => return Ok(PumpEnd::Exited(code)),\n                        AttachRecord::Displaced { by } => {\n                            return Ok(PumpEnd::Displaced(by));\n                        }\n                        // Viewer letterbox feed (REQ-RCVIEW-1): on a size change,\n                        // show a one-line indicator when our terminal differs from\n                        // the new PTY size (rc is a byte pump — true clip/pad needs\n                        // a grid model, deferred; the honest size cue renders here).\n                        AttachRecord::Size { rows, cols } if pty_size != Some((rows, cols)) => {\n                            pty_size = Some((rows, cols));\n                            match crossterm::terminal::size() {\n                                Ok((mycols, myrows)) if (myrows, mycols) != (rows, cols) => {\n                                    let _ = write!(\n                                        stdout,\n…\n                                    );\n…\n                                }\n…\n                            }\n                        }\n…\n                    }\n                }\n            }\n…\n}","startLine":1780,"lineNumbers":[1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,null,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,null,1982,null,1984,null,1986,1987,null,1989,1990,1991,null,1999]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs"}}},"isError":false,"timestamp":1783730645800}}
{"type":"message","id":"b8a5519f","parentId":"c0300c8e","timestamp":"2026-07-11T00:44:05.802Z","message":{"role":"toolResult","toolCallId":"call_sUMsbqPZJ4toSwL1zIyyWJxw|fc_0a129b8eba4c900a016a5191d60464819a95e414ed9d624cd3","toolName":"grep","content":[{"type":"text","text":"# spt-core/crates/spt-daemon/src/\n## broker.rs\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...\n 1045|    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n*1046|        let deadline = brain_write_deadline();\n 1047|        if !self.controller_write_stalled(now, deadline) {\n 1048|            return false;\n 1049|        }\n...\n 2731|        let (_recv, send_half) = server.split();\n*2732|        let send: SharedSend = Arc::new(BrokerConn::new(send_half, brain_write_deadline()));\n 2733|        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 2734|        // Drain-forever (never a socket write, never finishes while tx lives):\n 2735|        // stays un-reapable as a live-but-wedged writer even under child output.\n...\n 2835|        let now = Instant::now();\n*2836|        let deadline = brain_write_deadline();\n 2837|        let sessions = match self.sessions.lock() {\n 2838|            Ok(g) => g,\n 2839|            Err(p) => p.into_inner(),\n...\n 2897|        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2898|        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n 2899|\n 2900|        // Version handshake: this connection must be a brain (2.3).\n 2901|        recv_hello(&mut recv, Role::Brain)?;\n...\n 5367|        (\n*5368|            Arc::new(BrokerConn::new(send, brain_write_deadline())),\n 5369|            client,\n 5370|            recv,\n 5371|        )\n## conn.rs\n 316|    /// deadline (gate-wait + OS write completion) — the broker passes its\n*317|    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 318|    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 319|        let raw = raw_of(&half);\n 320|        let inner = Arc::new(Inner {"}],"details":{"scopePath":"spt-core/crates/spt-daemon/src/broker.rs, spt-core/crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects","matchCount":7,"fileCount":2,"files":["spt-core/crates/spt-daemon/src/broker.rs","spt-core/crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"spt-core/crates/spt-daemon/src/broker.rs","count":6},{"path":"spt-core/crates/spt-daemon/src/conn.rs","count":1}],"truncated":false,"displayContent":"# spt-core/crates/spt-daemon/src/\n## broker.rs\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     │...\n 1045│    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n*1046│        let deadline = brain_write_deadline();\n 1047│        if !self.controller_write_stalled(now, deadline) {\n 1048│            return false;\n 1049│        }\n     │...\n 2731│        let (_recv, send_half) = server.split();\n*2732│        let send: SharedSend = Arc::new(BrokerConn::new(send_half, brain_write_deadline()));\n 2733│        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 2734│        // Drain-forever (never a socket write, never finishes while tx lives):\n 2735│        // stays un-reapable as a live-but-wedged writer even under child output.\n     │...\n 2835│        let now = Instant::now();\n*2836│        let deadline = brain_write_deadline();\n 2837│        let sessions = match self.sessions.lock() {\n 2838│            Ok(g) => g,\n 2839│            Err(p) => p.into_inner(),\n     │...\n 2897│        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2898│        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n 2899│\n 2900│        // Version handshake: this connection must be a brain (2.3).\n 2901│        recv_hello(&mut recv, Role::Brain)?;\n     │...\n 5367│        (\n*5368│            Arc::new(BrokerConn::new(send, brain_write_deadline())),\n 5369│            client,\n 5370│            recv,\n 5371│        )\n## conn.rs\n 316│    /// deadline (gate-wait + OS write completion) — the broker passes its\n*317│    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 318│    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 319│        let raw = raw_of(&half);\n 320│        let inner = Arc::new(Inner {"},"isError":false,"timestamp":1783730645802}}
{"type":"message","id":"5058223f","parentId":"b8a5519f","timestamp":"2026-07-11T00:44:19.536Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning environment deadline handling**\n\n<!-- -->"},{"type":"toolCall","id":"call_vL8w75InCeoXB1gPGsvVaCsT|fc_0a129b8eba4c900a016a5191dd48b8819ab8feb024d25e9611","name":"irc","arguments":{"i":"Clarify residual root","op":"send","to":"Main","message":"Residual v0.30.6 is NOT pinned by historical RCA. If running broker_image really is 0.30.6/default 15s, old physical-write wedge is structurally bounded: every conn constructed with BrokerConn::new(..., brain_write_deadline()) at broker.rs:2897-2899; conn.rs:360-465 covers gate wait+OS write, poisons/severs on expiry. Same-conn writes cannot stack: first expiry poisons and wakes waiters. Current code still permits (A) per-rc indefinite local terminal-output block at spt/src/rc.rs:1948-1965 (`stdout.write_all`+`flush`, no deadline), freezing that rc pump only; and (B) per-session indefinite PTY-input writer block at broker.rs:1706-1709 -> spt-term/src/pty.rs:235-239, after which bounded queue drops input but output/other sessions remain live. Neither explains simultaneous multi-terminal streaming freeze without shared terminal-host/OS pressure [INFERENCE]. Attach setup/read/reconnect are bounded: 10s setup rc.rs:1403+, 40ms poll, 30s first-event grace rc.rs:1768-1776, 30s reconnect rc.rs:1631+. Inject verify is worker-local and finite defaults, though env knobs can enlarge waits. Therefore residual root requires timestamp-correlated v0.30.6 logs/repro; check CONN_WRITE_POISONED / handler cleanup / broker_image at exact event. Do not label d634b67 root as residual cause absent evidence.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4504,"output":516,"cacheRead":181760,"cacheWrite":0,"totalTokens":186780,"reasoningTokens":136,"cost":{"input":0.022520000000000002,"output":0.01548,"cacheRead":0.09088,"cacheWrite":0,"total":0.12888}},"stopReason":"toolUse","timestamp":1783730645950,"responseId":"resp_0a129b8eba4c900a016a5191d7cd8c819aaf6bc65a4e394bfe","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a129b8eba4c900a016a5191d96754819aa42d6181add3ca5f","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZHdtpWE7g_DcV2yCBDAzLmFF4-HtQHl_FcD7044GPOmpPwwW0dO3F0Al6T_9OdgTxuKRqJunlPLofH7KXq2XdBqGimjAmk3ycie_GztdLNtMbwlr7Jn6YwxLHi3SYgifQzGUmbzlZ7iDt5hY7wUBCBK8cUoCc6rEDC12ZGRK5HyRnCihp7-XkBEVEhUje6DsZLlOzAGN1ppcsvmEittS3QFqxukJ8gRQyWW0AIOAMQhPaxVkYYzkCLCAvPOrQdjVOc671SHzztHvDpWJh4n-MEQujxiaI3Q_Lx-gvu_2EX6Jr-NEAeA8SKBKuEU2pAxr2NjJoxTZKospMrLy9pGELWc8WitoRLfnVqzQT8GCygJUvOAt1_oug_JrhR_5BSGKbLFbAwLC4j7gjbFdxnGiDJ8L-1RBtJ5Yc_jPNjDkxB1L_fREu-UlkIyOWwKWeY0701G7seSI14kcGGXZu5i-5vMVwoRBRkcMTslRI_ry6GWZrpaW0RI6wqXc_CYoMJGndBwKJHxif9mv1CKO1KzTVxLuG8udlPKdQRGllIWthcCmP-I64mpkq0PqCrtcipUlGXd26G3uwcdxbbASiVD9fW42fo_6w2G0PlPoixcn-MZnXMKnCtD8EfOSvICkBxMXt5F8SwXSZ1_22rM1UvE1HDUnISNnoLH-3K9fwdcQoowrs8G5XWEo0lEzxFcYDnf0GCeoR9KtISE82RYRoZ4RyoAwDHlY9CUM_a4yrT-wMmm2idclyivFxaUVg7fVGmwjogJ_J1LTRjmX1f48yb31OGyIoOLrl-1CYu-3Fl31Su7OAif0BGqTL3Y66TtHcwNIWNLLmV9p3SNUKBvUTwMOls7cUyS9vnka3mBVA82vfFvweDkGlmUDSRNhb5Ysdl2INOBPSPIh1SBKcaDj1hoHh1V7N1Yxw0Edmv-0TZrQtMo8G7McUjFIxkrjrcn_F_MLIiIVQlI8WMyKCG-MNMDySH9tpBVPFe_GH-W8DLKKbbpfQDHhxL6yCoVkIVTHn4MoBP4uQzCF4ELYmCwlLoKV2scvvRDGnYvyHjvLnAvG5RnMZMt6ZRd2xSvR6Hx2KJPBPzjGw3oej95W_LtX6H95FA40Cphcfj8Db8mOJ_U9j5nBDG-qM8S950pSTbKf9lEayQBOU834b8_k-4T569WUXHWQ2vM_cki-_ztFzA4GPxQooOcVK6BZqUf1kJtecHVpBn5T_z5XoijW6FDUWreI05EcNmNdoceaPNn9dcwMZti_y8BMrKMcRfBeRHbpy4MWDLXquylTnaVOTfqutmnJHgQsS7Y5TuKshvCoYW_8MCc_9FS8AwNvW3iyTUtxz8BnfPOyYTbOCAxLSRurU_dFQP4pFUpIGFmOUcJcmSQCukewyWYP7Vr-el4gSE8tGwRNtl0_swuuG_scCKsCFqaWLW16BxSR7BBSHrTT0AQtcDOGcuYaWsE1Nthbx8PWr2UYEaP1O64u2JrIquY0HvgFYm44C3Lw8ulVfEGE2fWZ6J3dU513YgC0z6xWxuwKCmz8bDmN5YxaXyVmaM6nbcjLCkd_Yk2AHYRa1cxwoo4tThK63Jqd8k2wAzzd6S-OC2PuzebqMtZNOgfoIf2Ulq2yPBEQs8QDr4Mo_e3QQdsfLFX6xpgUd9H3h_Y-3l6qDFiYpFE5nZ0Ry-kijlpuFIXGVFnSI94T_7IbUfGDJASCy0F2GtZQFjsTXS9baZTnCO60Fc8k1qCjdaa_mC4UcBV5WgA88wouCC-JSXaDWdEiI5dCLAzb2Jhq4Yi5sr3fXGxKXyw","summary":[{"type":"summary_text","text":"**Planning environment deadline handling**\n\n<!-- -->"}]},{"id":"fc_0a129b8eba4c900a016a5191dd48b8819ab8feb024d25e9611","type":"function_call","status":"completed","arguments":"{\"i\":\"Clarify residual root\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Residual v0.30.6 is NOT pinned by historical RCA. If running broker_image really is 0.30.6/default 15s, old physical-write wedge is structurally bounded: every conn constructed with BrokerConn::new(..., brain_write_deadline()) at broker.rs:2897-2899; conn.rs:360-465 covers gate wait+OS write, poisons/severs on expiry. Same-conn writes cannot stack: first expiry poisons and wakes waiters. Current code still permits (A) per-rc indefinite local terminal-output block at spt/src/rc.rs:1948-1965 (`stdout.write_all`+`flush`, no deadline), freezing that rc pump only; and (B) per-session indefinite PTY-input writer block at broker.rs:1706-1709 -> spt-term/src/pty.rs:235-239, after which bounded queue drops input but output/other sessions remain live. Neither explains simultaneous multi-terminal streaming freeze without shared terminal-host/OS pressure [INFERENCE]. Attach setup/read/reconnect are bounded: 10s setup rc.rs:1403+, 40ms poll, 30s first-event grace rc.rs:1768-1776, 30s reconnect rc.rs:1631+. Inject verify is worker-local and finite defaults, though env knobs can enlarge waits. Therefore residual root requires timestamp-correlated v0.30.6 logs/repro; check CONN_WRITE_POISONED / handler cleanup / broker_image at exact event. Do not label d634b67 root as residual cause absent evidence.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_vL8w75InCeoXB1gPGsvVaCsT","name":"irc"}]},"duration":13530.683299999684,"ttft":2839.7310999999754,"contextSnapshot":{"promptTokens":186264,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_vL8w75InCeoXB1gPGsvVaCsT|fc_0a129b8eba4c900a016a5191dd48b8819ab8feb024d25e9611","toolName":"irc","startedAt":"2026-07-11T00:44:19.537Z","intent":"Clarify residual root"},"id":"e62ff60b","parentId":"5058223f","timestamp":"2026-07-11T00:44:19.537Z"}
{"type":"message","id":"c11c9848","parentId":"e62ff60b","timestamp":"2026-07-11T00:44:19.559Z","message":{"role":"toolResult","toolCallId":"call_vL8w75InCeoXB1gPGsvVaCsT|fc_0a129b8eba4c900a016a5191dd48b8819ab8feb024d25e9611","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"FreezeCodePath","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1783730659537}}
{"type":"message","id":"5c3da4c4","parentId":"c11c9848","timestamp":"2026-07-11T00:45:00.265Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Finalizing structured output format**\n\n<!-- -->"},{"type":"toolCall","id":"call_nL2QGJexMeH3Na7tsiIrhRfc|fc_0a129b8eba4c900a016a5191e8d03c819a8e502a33e6218cbd","name":"yield","arguments":{"type":"result","result":{"data":{"summary":"Residual-v0.30.6 clarification: the proven historical root and the residual report must be separated. The historical pre-v0.30.6 root is pinned: an unbounded broker physical-connection write held the shared send mutex while a controller stopped reading. The current v0.30.6 tree structurally bounds that class at 15 seconds by default and retires the entire physical connection, so it is not a defensible explanation for a new multi-minute freeze unless (a) the running broker image is actually older/stale, (b) `SPT_BRAIN_WRITE_DEADLINE_MS` was overridden to a very large value, or (c) cancellation itself failed in a way not covered by the existing Windows gate. Current code alone does not pin the residual root; it requires an exact incident timestamp and reproduction/log correlation.\n\nWhy the historical class should be closed: every accepted broker connection wraps its send half in `BrokerConn::new(send, brain_write_deadline())` at `crates/spt-daemon/src/broker.rs:2897-2899`. The default bound is 15 seconds (`broker.rs:146-184`). `BrokerConn::write_gated` stamps one absolute deadline at entry covering both serialization-gate wait and OS write completion (`conn.rs:350-405`); on timeout/failure it poisons, aborts, severs, parks, and never reuses the connection (`conn.rs:407-465`). The first stuck write's timeout wakes all gate waiters (`conn.rs:176-203`), and waiters reject a poisoned connection, so bounded writes on one physical connection do not serially stack into minutes. Independent connections can each consume their own bound concurrently, but they do not serialize the global PTY drain: `OutputLog::append` only performs non-blocking `try_send` fan-out (`broker.rs:715-858`).\n\nCurrent paths that can still block for multiple minutes, but only with narrower blast radii:\n1. `spt rc` terminal-output sink: the pump synchronously calls `stdout.write_all(&bytes)` and `stdout.flush()` with no timeout or cancellation at `crates/spt/src/rc.rs:1948-1965`; status/reconnect painting has the same direct-output posture at `rc.rs:1585-1587,1836-1839,1884-1886`. If the local Windows terminal/redirected stdout stops accepting output, this call can remain blocked indefinitely [INFERENCE from the absence of any code deadline]. While blocked, that particular `rc` pump cannot read broker events or process its stdin queue. This is a plausible per-terminal residual, not a proven incident root. It does not by itself explain several independent terminals freezing simultaneously unless they share a stalled terminal host/output resource [INFERENCE]. The target broker should eventually retire that non-draining consumer connection under its 15-second connection bound, but that does not interrupt the already-blocked local stdout call inside the `rc` process.\n2. Per-session PTY input writer: `input_writer` can block indefinitely in `session.write_input` at `crates/spt-daemon/src/broker.rs:1706-1709`; the concrete call holds the PTY writer mutex across `write_all` and `flush` at `crates/spt-term/src/pty.rs:235-239`. The architecture intentionally isolates this onto one thread per session. Once its bounded input FIFO fills, `InputWriter::enqueue` drops and stamps backpressure rather than blocking dispatch (`broker.rs:1600-1699`). This can make one harness stop responding to keyboard/inject input for minutes, but PTY output, attaches, broker dispatch, and other sessions remain live; it does not fit a simultaneous output-streaming freeze.\n3. Inject verification can produce a long per-session delivery delay if environment knobs are enlarged, but defaults are finite and worker-local: 400 ms settle, 1.2 s echo verify, up to two attempts, and 5 s commit windows with a three-strike budget (`broker.rs:286-317,363-445,2160-2285`). The live daemon's non-default `SPT_INJECT_VERIFY_ECHO` enables the second layer but does not itself remove any bound. Sleeps occur off the output-log and inject-floor locks, and PTY writes are non-blocking queue submissions. This remains a load/timing confounder, not a pinned global residual root.\n\nOther apparent-freeze windows are explicitly bounded and too short to explain a continuous multi-minute event without repeated external failures: setup IPC uses a 10-second bounded pump connection (`rc.rs:1403-1414`); the normal pump reads in 40 ms slices (`rc.rs:43-44,1914-1925`); a completely blank first attach exits after 30 seconds (`rc.rs:1768-1776`); reconnect gives up after 30 seconds with one-second pauses (`rc.rs:1503-1636`). Viewer eviction re-subscribe sleeps at most 200 ms (`attach.rs:95-102,473-491`). Controller gap recovery either re-fetches once from its frozen floor or reports irrecoverable-behind on the repeated floor; it does not spin (`attach.rs:138-194`).\n\nResidual incident decision: if logs at the exact report time show `broker_image=0.30.6`/`broker_stale=false`, a `CONN_WRITE_POISONED` followed by handler cleanup within the bound, and multiple independent terminals still stopped streaming for minutes, the old d634b67 failure class is falsified for that incident. If no `CONN_WRITE_POISONED` appears, determine whether PTY output high-water continued, whether each `rc` process was blocked in terminal `WriteFile`/console output, and whether the broker remained responsive to independent status/sessions requests. Without those timestamps/thread stacks or a reproduction, labeling any current path the residual root would be speculation.","files":[{"path":"spt-core/crates/spt-daemon/src/conn.rs","description":"Current v0.30.6 bounded physical-connection write primitive: absolute bound across gate+write, watchdog abort, poison/sever/no-reuse."},{"path":"spt-core/crates/spt-daemon/src/broker.rs","description":"Connection construction at 2897-2899; 15-second default at 146-184; non-blocking output fan-out at 715-858; isolated potentially-blocking PTY input writer at 1600-1709."},{"path":"spt-core/crates/spt/src/rc.rs","description":"Residual per-client risk: unbounded synchronous stdout writes/flushes at 1948-1965. Also proves bounded 10-second setup, 30-second first-event backstop, and 30-second reconnect window."},{"path":"spt-core/crates/spt-term/src/pty.rs","description":"Concrete per-session PTY input write holds writer mutex across blocking write_all+flush at 235-239."},{"path":"spt-core/crates/spt-daemon/src/attach.rs","description":"Viewer re-subscribe bounded at 200 ms and controller gap resume terminates after a repeated frozen floor rather than spinning."},{"path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","description":"Historical root only: pinned pre-v0.30.6 physical writer failure and explicit statement that the historical non-draining process identity is unproven."}],"architecture":"Current v0.30.6 has two different blocking domains. Broker-to-client framed writes sit below isolated controller/viewer queues but now pass through one deadline-bounded, poison-on-failure `BrokerConn` per physical connection; they cannot indefinitely hold the broker send gate or globally serialize PTY output. Client-side rendering remains a synchronous `spt rc` stdout write with no deadline, so a stalled local terminal sink can freeze one viewport even after the broker retires its connection. PTY input remains a synchronous per-session writer thread, deliberately isolated behind a bounded drop-on-full FIFO. Thus current code admits indefinite single-viewport output stalls and single-session input stalls, but not the same proven global/multi-session broker freeze mechanism. A residual simultaneous multi-terminal incident is unpinned pending timestamped logs or a reproduction that identifies the shared blocked boundary."}}}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":946,"output":1784,"cacheRead":185856,"cacheWrite":0,"totalTokens":188586,"reasoningTokens":22,"cost":{"input":0.004730000000000001,"output":0.05352,"cacheRead":0.092928,"cacheWrite":0,"total":0.15117799999999998}},"stopReason":"toolUse","timestamp":1783730659621,"responseId":"resp_0a129b8eba4c900a016a5191e46544819ab80d98b2163ab912","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a129b8eba4c900a016a5191e7ce8c819ab78fd2fa7ddc852d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqUZHoDiy8xow6B015Hu2PtyuzZ-igXnf3VsNhUt_yIk-pIL6TuPrM5LBjs89AB_CqAcau41IGpk3hy24jPPDejp6JZe3-oqcY3tCplR1ZpkELc-39t8ZefnLMKONZrKtDDS7tClLPoR7Ja5OujY6sec2cSuvQG4mnDpgNwcAZw2mpJlmrr5qlK8JdM7iwbzrZ15hq7Th7GdXHZPOD9sZkLbczfR5WpMSSMzTlnqTGnvSqHvnIgn_wiEKPEf4dtvmYJBmtQ1l2StsJvKoJ25zM5ARXBbcKtn2aYdCTIrSFdlYCiKA4D1zd84oyFVatcxyi6dWtW78eo7SCAI7hEMalwq-fY4sAiSU0ESVy9e9JC58FdG1t4Uf99Guga9txAKRFQoBdnPh70Hr1h3Jz1TOxviE3G9je3IFLYCZRzM7m1erNcRgCBz6aKqaE6wHAzA_Xg8JHdBC_2bMLMRDHYOYldi6Hu4Ch0dAEfgpUxij6mNlZM_8dA7IEBPbPF6LFfF3OicAQIZ0s_-U_pzg9kvbvDc6ha_17nWzhQSeM2jXtOAywoCR1ywx55JHFf1iPF9uD3ZL5MrYGC6My_pUslzdV6fvWquaQT7Dnmh0glK5hvnbZZbit4vgM7KxZUP7ACWH9b-v-ArnINDNzjYgSGdpUdge5A_NKREb0azdJFDr63YHTz-6G1x0V5v4yPZV_ziVeM2RM92Cop5IJlMdRInzg3RE0T4HVc4jQTVokT95SBQhPASm0yzcQtNo1XiQWPCI1cXq3iRPTsHl5ZE9QE0434uhw8_5rjyvP6MXaYQ3nMQ6WsuTHqz-vlK2ZR_PoQvC3XZQY5_afIP9k1R9M6Z1BCdNv6WpUOr3b1145h0-_1zoYs4jKLsSI6QaE_B86WxV0QY3Ca_iTH1XWf82-fXywwwh6EVW9Zkk8dQmHxDecOBzC_Pt53D7Y0_8lQAjRNXUsymA0SRHJi5BB1OCE-HJstQh_MwJQ-DlOf0_Ayxx0iXXTNuLxzV8hJ-8F0CZ4ILYZ5BvBxTfKjDy90JTwK6ds8tMhgA_ipcxtfA8GVR3F50xadXtA_KSHo0riO_fIv_tf2XdU","summary":[{"type":"summary_text","text":"**Finalizing structured output format**\n\n<!-- -->"}]},{"id":"fc_0a129b8eba4c900a016a5191e8d03c819a8e502a33e6218cbd","type":"function_call","status":"completed","arguments":"{\"type\":\"result\",\"result\":{\"data\":{\"summary\":\"Residual-v0.30.6 clarification: the proven historical root and the residual report must be separated. The historical pre-v0.30.6 root is pinned: an unbounded broker physical-connection write held the shared send mutex while a controller stopped reading. The current v0.30.6 tree structurally bounds that class at 15 seconds by default and retires the entire physical connection, so it is not a defensible explanation for a new multi-minute freeze unless (a) the running broker image is actually older/stale, (b) `SPT_BRAIN_WRITE_DEADLINE_MS` was overridden to a very large value, or (c) cancellation itself failed in a way not covered by the existing Windows gate. Current code alone does not pin the residual root; it requires an exact incident timestamp and reproduction/log correlation.\\n\\nWhy the historical class should be closed: every accepted broker connection wraps its send half in `BrokerConn::new(send, brain_write_deadline())` at `crates/spt-daemon/src/broker.rs:2897-2899`. The default bound is 15 seconds (`broker.rs:146-184`). `BrokerConn::write_gated` stamps one absolute deadline at entry covering both serialization-gate wait and OS write completion (`conn.rs:350-405`); on timeout/failure it poisons, aborts, severs, parks, and never reuses the connection (`conn.rs:407-465`). The first stuck write's timeout wakes all gate waiters (`conn.rs:176-203`), and waiters reject a poisoned connection, so bounded writes on one physical connection do not serially stack into minutes. Independent connections can each consume their own bound concurrently, but they do not serialize the global PTY drain: `OutputLog::append` only performs non-blocking `try_send` fan-out (`broker.rs:715-858`).\\n\\nCurrent paths that can still block for multiple minutes, but only with narrower blast radii:\\n1. `spt rc` terminal-output sink: the pump synchronously calls `stdout.write_all(&bytes)` and `stdout.flush()` with no timeout or cancellation at `crates/spt/src/rc.rs:1948-1965`; status/reconnect painting has the same direct-output posture at `rc.rs:1585-1587,1836-1839,1884-1886`. If the local Windows terminal/redirected stdout stops accepting output, this call can remain blocked indefinitely [INFERENCE from the absence of any code deadline]. While blocked, that particular `rc` pump cannot read broker events or process its stdin queue. This is a plausible per-terminal residual, not a proven incident root. It does not by itself explain several independent terminals freezing simultaneously unless they share a stalled terminal host/output resource [INFERENCE]. The target broker should eventually retire that non-draining consumer connection under its 15-second connection bound, but that does not interrupt the already-blocked local stdout call inside the `rc` process.\\n2. Per-session PTY input writer: `input_writer` can block indefinitely in `session.write_input` at `crates/spt-daemon/src/broker.rs:1706-1709`; the concrete call holds the PTY writer mutex across `write_all` and `flush` at `crates/spt-term/src/pty.rs:235-239`. The architecture intentionally isolates this onto one thread per session. Once its bounded input FIFO fills, `InputWriter::enqueue` drops and stamps backpressure rather than blocking dispatch (`broker.rs:1600-1699`). This can make one harness stop responding to keyboard/inject input for minutes, but PTY output, attaches, broker dispatch, and other sessions remain live; it does not fit a simultaneous output-streaming freeze.\\n3. Inject verification can produce a long per-session delivery delay if environment knobs are enlarged, but defaults are finite and worker-local: 400 ms settle, 1.2 s echo verify, up to two attempts, and 5 s commit windows with a three-strike budget (`broker.rs:286-317,363-445,2160-2285`). The live daemon's non-default `SPT_INJECT_VERIFY_ECHO` enables the second layer but does not itself remove any bound. Sleeps occur off the output-log and inject-floor locks, and PTY writes are non-blocking queue submissions. This remains a load/timing confounder, not a pinned global residual root.\\n\\nOther apparent-freeze windows are explicitly bounded and too short to explain a continuous multi-minute event without repeated external failures: setup IPC uses a 10-second bounded pump connection (`rc.rs:1403-1414`); the normal pump reads in 40 ms slices (`rc.rs:43-44,1914-1925`); a completely blank first attach exits after 30 seconds (`rc.rs:1768-1776`); reconnect gives up after 30 seconds with one-second pauses (`rc.rs:1503-1636`). Viewer eviction re-subscribe sleeps at most 200 ms (`attach.rs:95-102,473-491`). Controller gap recovery either re-fetches once from its frozen floor or reports irrecoverable-behind on the repeated floor; it does not spin (`attach.rs:138-194`).\\n\\nResidual incident decision: if logs at the exact report time show `broker_image=0.30.6`/`broker_stale=false`, a `CONN_WRITE_POISONED` followed by handler cleanup within the bound, and multiple independent terminals still stopped streaming for minutes, the old d634b67 failure class is falsified for that incident. If no `CONN_WRITE_POISONED` appears, determine whether PTY output high-water continued, whether each `rc` process was blocked in terminal `WriteFile`/console output, and whether the broker remained responsive to independent status/sessions requests. Without those timestamps/thread stacks or a reproduction, labeling any current path the residual root would be speculation.\",\"files\":[{\"path\":\"spt-core/crates/spt-daemon/src/conn.rs\",\"description\":\"Current v0.30.6 bounded physical-connection write primitive: absolute bound across gate+write, watchdog abort, poison/sever/no-reuse.\"},{\"path\":\"spt-core/crates/spt-daemon/src/broker.rs\",\"description\":\"Connection construction at 2897-2899; 15-second default at 146-184; non-blocking output fan-out at 715-858; isolated potentially-blocking PTY input writer at 1600-1709.\"},{\"path\":\"spt-core/crates/spt/src/rc.rs\",\"description\":\"Residual per-client risk: unbounded synchronous stdout writes/flushes at 1948-1965. Also proves bounded 10-second setup, 30-second first-event backstop, and 30-second reconnect window.\"},{\"path\":\"spt-core/crates/spt-term/src/pty.rs\",\"description\":\"Concrete per-session PTY input write holds writer mutex across blocking write_all+flush at 235-239.\"},{\"path\":\"spt-core/crates/spt-daemon/src/attach.rs\",\"description\":\"Viewer re-subscribe bounded at 200 ms and controller gap resume terminates after a repeated frozen floor rather than spinning.\"},{\"path\":\"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md\",\"description\":\"Historical root only: pinned pre-v0.30.6 physical writer failure and explicit statement that the historical non-draining process identity is unproven.\"}],\"architecture\":\"Current v0.30.6 has two different blocking domains. Broker-to-client framed writes sit below isolated controller/viewer queues but now pass through one deadline-bounded, poison-on-failure `BrokerConn` per physical connection; they cannot indefinitely hold the broker send gate or globally serialize PTY output. Client-side rendering remains a synchronous `spt rc` stdout write with no deadline, so a stalled local terminal sink can freeze one viewport even after the broker retires its connection. PTY input remains a synchronous per-session writer thread, deliberately isolated behind a bounded drop-on-full FIFO. Thus current code admits indefinite single-viewport output stalls and single-session input stalls, but not the same proven global/multi-session broker freeze mechanism. A residual simultaneous multi-terminal incident is unpinned pending timestamped logs or a reproduction that identifies the shared blocked boundary.\"}}}","call_id":"call_nL2QGJexMeH3Na7tsiIrhRfc","name":"yield"}]},"duration":40642.16669999994,"ttft":3565.521199999843,"contextSnapshot":{"promptTokens":186802,"nonMessageTokens":17877}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_nL2QGJexMeH3Na7tsiIrhRfc|fc_0a129b8eba4c900a016a5191e8d03c819a8e502a33e6218cbd","toolName":"yield","startedAt":"2026-07-11T00:45:00.266Z"},"id":"f1ff0ef6","parentId":"5c3da4c4","timestamp":"2026-07-11T00:45:00.266Z"}
{"type":"message","id":"2215d2e5","parentId":"f1ff0ef6","timestamp":"2026-07-11T00:45:00.266Z","message":{"role":"toolResult","toolCallId":"call_nL2QGJexMeH3Na7tsiIrhRfc|fc_0a129b8eba4c900a016a5191e8d03c819a8e502a33e6218cbd","toolName":"yield","content":[{"type":"text","text":"Result submitted."}],"details":{"data":{"summary":"Residual-v0.30.6 clarification: the proven historical root and the residual report must be separated. The historical pre-v0.30.6 root is pinned: an unbounded broker physical-connection write held the shared send mutex while a controller stopped reading. The current v0.30.6 tree structurally bounds that class at 15 seconds by default and retires the entire physical connection, so it is not a defensible explanation for a new multi-minute freeze unless (a) the running broker image is actually older/stale, (b) `SPT_BRAIN_WRITE_DEADLINE_MS` was overridden to a very large value, or (c) cancellation itself failed in a way not covered by the existing Windows gate. Current code alone does not pin the residual root; it requires an exact incident timestamp and reproduction/log correlation.\n\nWhy the historical class should be closed: every accepted broker connection wraps its send half in `BrokerConn::new(send, brain_write_deadline())` at `crates/spt-daemon/src/broker.rs:2897-2899`. The default bound is 15 seconds (`broker.rs:146-184`). `BrokerConn::write_gated` stamps one absolute deadline at entry covering both serialization-gate wait and OS write completion (`conn.rs:350-405`); on timeout/failure it poisons, aborts, severs, parks, and never reuses the connection (`conn.rs:407-465`). The first stuck write's timeout wakes all gate waiters (`conn.rs:176-203`), and waiters reject a poisoned connection, so bounded writes on one physical connection do not serially stack into minutes. Independent connections can each consume their own bound concurrently, but they do not serialize the global PTY drain: `OutputLog::append` only performs non-blocking `try_send` fan-out (`broker.rs:715-858`).\n\nCurrent paths that can still block for multiple minutes, but only with narrower blast radii:\n1. `spt rc` terminal-output sink: the pump synchronously calls `stdout.write_all(&bytes)` and `stdout.flush()` with no timeout or cancellation at `crates/spt/src/rc.rs:1948-1965`; status/reconnect painting has the same direct-output posture at `rc.rs:1585-1587,1836-1839,1884-1886`. If the local Windows terminal/redirected stdout stops accepting output, this call can remain blocked indefinitely [INFERENCE from the absence of any code deadline]. While blocked, that particular `rc` pump cannot read broker events or process its stdin queue. This is a plausible per-terminal residual, not a proven incident root. It does not by itself explain several independent terminals freezing simultaneously unless they share a stalled terminal host/output resource [INFERENCE]. The target broker should eventually retire that non-draining consumer connection under its 15-second connection bound, but that does not interrupt the already-blocked local stdout call inside the `rc` process.\n2. Per-session PTY input writer: `input_writer` can block indefinitely in `session.write_input` at `crates/spt-daemon/src/broker.rs:1706-1709`; the concrete call holds the PTY writer mutex across `write_all` and `flush` at `crates/spt-term/src/pty.rs:235-239`. The architecture intentionally isolates this onto one thread per session. Once its bounded input FIFO fills, `InputWriter::enqueue` drops and stamps backpressure rather than blocking dispatch (`broker.rs:1600-1699`). This can make one harness stop responding to keyboard/inject input for minutes, but PTY output, attaches, broker dispatch, and other sessions remain live; it does not fit a simultaneous output-streaming freeze.\n3. Inject verification can produce a long per-session delivery delay if environment knobs are enlarged, but defaults are finite and worker-local: 400 ms settle, 1.2 s echo verify, up to two attempts, and 5 s commit windows with a three-strike budget (`broker.rs:286-317,363-445,2160-2285`). The live daemon's non-default `SPT_INJECT_VERIFY_ECHO` enables the second layer but does not itself remove any bound. Sleeps occur off the output-log and inject-floor locks, and PTY writes are non-blocking queue submissions. This remains a load/timing confounder, not a pinned global residual root.\n\nOther apparent-freeze windows are explicitly bounded and too short to explain a continuous multi-minute event without repeated external failures: setup IPC uses a 10-second bounded pump connection (`rc.rs:1403-1414`); the normal pump reads in 40 ms slices (`rc.rs:43-44,1914-1925`); a completely blank first attach exits after 30 seconds (`rc.rs:1768-1776`); reconnect gives up after 30 seconds with one-second pauses (`rc.rs:1503-1636`). Viewer eviction re-subscribe sleeps at most 200 ms (`attach.rs:95-102,473-491`). Controller gap recovery either re-fetches once from its frozen floor or reports irrecoverable-behind on the repeated floor; it does not spin (`attach.rs:138-194`).\n\nResidual incident decision: if logs at the exact report time show `broker_image=0.30.6`/`broker_stale=false`, a `CONN_WRITE_POISONED` followed by handler cleanup within the bound, and multiple independent terminals still stopped streaming for minutes, the old d634b67 failure class is falsified for that incident. If no `CONN_WRITE_POISONED` appears, determine whether PTY output high-water continued, whether each `rc` process was blocked in terminal `WriteFile`/console output, and whether the broker remained responsive to independent status/sessions requests. Without those timestamps/thread stacks or a reproduction, labeling any current path the residual root would be speculation.","files":[{"path":"spt-core/crates/spt-daemon/src/conn.rs","description":"Current v0.30.6 bounded physical-connection write primitive: absolute bound across gate+write, watchdog abort, poison/sever/no-reuse."},{"path":"spt-core/crates/spt-daemon/src/broker.rs","description":"Connection construction at 2897-2899; 15-second default at 146-184; non-blocking output fan-out at 715-858; isolated potentially-blocking PTY input writer at 1600-1709."},{"path":"spt-core/crates/spt/src/rc.rs","description":"Residual per-client risk: unbounded synchronous stdout writes/flushes at 1948-1965. Also proves bounded 10-second setup, 30-second first-event backstop, and 30-second reconnect window."},{"path":"spt-core/crates/spt-term/src/pty.rs","description":"Concrete per-session PTY input write holds writer mutex across blocking write_all+flush at 235-239."},{"path":"spt-core/crates/spt-daemon/src/attach.rs","description":"Viewer re-subscribe bounded at 200 ms and controller gap resume terminates after a repeated frozen floor rather than spinning."},{"path":"spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md","description":"Historical root only: pinned pre-v0.30.6 physical writer failure and explicit statement that the historical non-draining process identity is unproven."}],"architecture":"Current v0.30.6 has two different blocking domains. Broker-to-client framed writes sit below isolated controller/viewer queues but now pass through one deadline-bounded, poison-on-failure `BrokerConn` per physical connection; they cannot indefinitely hold the broker send gate or globally serialize PTY output. Client-side rendering remains a synchronous `spt rc` stdout write with no deadline, so a stalled local terminal sink can freeze one viewport even after the broker retires its connection. PTY input remains a synchronous per-session writer thread, deliberately isolated behind a bounded drop-on-full FIFO. Thus current code admits indefinite single-viewport output stalls and single-session input stalls, but not the same proven global/multi-session broker freeze mechanism. A residual simultaneous multi-terminal incident is unpinned pending timestamped logs or a reproduction that identifies the shared blocked boundary."},"status":"success","type":"result"},"isError":false,"timestamp":1783730700266}}
{"type":"message","id":"c75310e1","parentId":"2215d2e5","timestamp":"2026-07-11T00:45:04.840Z","message":{"role":"assistant","content":[{"type":"text","text":"","textSignature":"{\"v\":1,\"id\":\"msg_0a129b8eba4c900a016a51920edc74819aad9cfd319bc807dc\",\"phase\":\"final_answer\"}"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":2744,"output":4,"cacheRead":185856,"cacheWrite":0,"totalTokens":188604,"cost":{"input":0.013720000000000001,"output":0.00012,"cacheRead":0.092928,"cacheWrite":0,"total":0.106768}},"stopReason":"stop","timestamp":1783730700272,"responseId":"resp_0a129b8eba4c900a016a51920d44e4819aa8d29664e3f0c318","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"msg_0a129b8eba4c900a016a51920edc74819aad9cfd319bc807dc","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":""}],"phase":"final_answer","role":"assistant"}]},"duration":4520.068599999882,"ttft":4516.248000000138,"contextSnapshot":{"promptTokens":188600,"nonMessageTokens":17877}}}
{"type":"custom","customType":"session_exit","data":{"reason":"dispose","kind":"normal","recordedAt":"2026-07-11T00:52:04.851Z"},"id":"437b44f0","parentId":"c75310e1","timestamp":"2026-07-11T00:52:04.852Z"}
