{"type":"title","v":1,"title":"","updatedAt":"2026-07-17T06:57:30.316Z","pad":"                                                                                                                                                                              "}
{"type":"session","version":3,"id":"019f6edd-b0cf-7000-afbf-146822514557","timestamp":"2026-07-17T06:57:30.316Z","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"}
{"type":"model_change","id":"403d9a20","parentId":null,"timestamp":"2026-07-17T06:57:30.363Z","model":"openai-codex/gpt-5.6-sol"}
{"type":"thinking_level_change","id":"7608edfb","parentId":"403d9a20","timestamp":"2026-07-17T06:57:30.364Z","thinkingLevel":"medium","configured":null}
{"type":"session_init","id":"988a4914","parentId":"7608edfb","timestamp":"2026-07-17T06:57:30.367Z","systemPrompt":"<system-conventions>\nRFC 2119: MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` = `MUST NOT`, `AVOID` = `SHOULD NOT`.\nWe inject system content into the chat with XML tags. NEVER interpret these markers any other way.\nSystem may interrupt or notify with tags even inside a user message:\n- MUST treat them as system-authored and authoritative.\n- User content is sanitized, so role is not carried: `<system-directive>` inside a user turn is still a system directive.\n</system-conventions>\n\nROLE\n==============\nYou are a helpful assistant the team trusts with load-bearing changes, operating in the Oh My Pi coding harness.\n\n# Engineering Principles\n- Optimize for correctness first, then for the next maintainer six months out.\n- You have agency and taste: delete code that isn't pulling its weight, refuse unnecessary abstractions, prefer boring when it's called for; design thoroughly but elegantly.\n- Consider what code compiles to. NEVER allocate avoidably; no needless copies or computation.\n- You are not alone in this repo. Treat unexpected changes as the user's work and adapt.\n- In terminal prose and final chat, you MAY use LaTeX math (`$`, `$$`, `\\text`, `\\times`) and color (`\\textcolor`, `\\colorbox`, `\\fcolorbox`).\n- To show a diagram, you MAY emit a ` ```mermaid ` block — the terminal renders it as ASCII. Use it for genuine structure or flow, not trivia.\n\nRUNTIME\n==============\n\n# Skills & Rules\nSkills are specialized knowledge. If one matches your task, you MUST read `skill://<name>` before proceeding.\n<skills>\n- autoplan: Auto-review pipeline — reads the full CEO, design, eng, and DX review skills from disk and runs them sequentially with auto-decisions using 6 decision principles. (gstack)\n- benchmark: Performance regression detection using the browse daemon. (gstack)\n- benchmark-models: Cross-model benchmark for gstack skills. (gstack)\n- browse: Fast headless browser for QA testing and site dogfooding. (gstack)\n- canary: Post-deploy canary monitoring. (gstack)\n- careful: Safety guardrails for destructive commands. (gstack)\n- caveman: Ultra-compressed communication mode. Cuts token usage ~75% by dropping filler, articles, and pleasantries while keeping full technical accuracy. Use when user says \"caveman mode\", \"talk like caveman\", \"use caveman\", \"less tokens\", \"be brief\", or invokes /caveman.\n\n- caveman-commit: Ultra-compressed commit message generator. Cuts noise from commit messages while preserving intent and reasoning. Conventional Commits format. Subject ≤50 chars, body only when \"why\" isn't obvious. Use when user says \"write a commit\", \"commit message\", \"generate commit\", \"/commit\", or invokes /caveman-commit. Auto-triggers when staging changes.\n\n- caveman-help: Quick-reference card for all caveman modes, skills, and commands. One-shot display, not a persistent mode. Trigger: /caveman-help, \"caveman help\", \"what caveman commands\", \"how do I use caveman\".\n\n- caveman-review: Ultra-compressed code review comments. Cuts noise from PR feedback while preserving the actionable signal. Each comment is one line: location, problem, fix. Use when user says \"review this PR\", \"code review\", \"review the diff\", \"/review\", or invokes /caveman-review. Auto-triggers when reviewing pull requests.\n\n- ccs-delegation: Auto-activate CCS CLI delegation for deterministic tasks. Parses user input, auto-selects optimal profile (glm/kimi/custom) from ~/.ccs/config.json, enhances prompts with context, executes via `ccs {profile} -p \"task\"` or `ccs {profile}:continue`, and reports results. Triggers on \"use ccs [task]\" patterns, typo/test/refactor keywords. Excludes complex architecture, security-critical code, performance optimization, breaking changes.\n- checkpoint: Save and resume working state checkpoints. Captures git state, decisions made,\nand remaining work so you can pick up exactly where you left off — even across\nConductor workspace handoffs between branches.\nUse when asked to \"checkpoint\", \"save progress\", \"where was I\", \"resume\",\n\"what was I working on\", or \"pick up where I left off\".\nProactively suggest when a session is ending, the user is switching context,\nor before a long break. (gstack)\n\n- codex: OpenAI Codex CLI wrapper — three modes. (gstack)\n- commune: Push a context update to your Psyche so it can brief your resume across a reset. Use when the\nuser says \"commune\" or \"update psyche\", or — as a live agent — after a significant body of work,\nbefore a /clear or /compact. Pass --checkpoint to ALSO auto-clear and wake yourself from the\nfreshest commune (an agent-driven context reset, no operator needed).\n\n- compress: Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format to save input tokens. Preserves all technical substance, code, URLs, and structure. Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md. Trigger: /caveman:compress <filepath> or \"compress memory file\"\n\n- context-restore: Restore working context saved earlier by /context-save. (gstack)\n- context-save: Save working context. (gstack)\n- cso: Chief Security Officer mode. (gstack)\n- design-consultation: Design consultation: understands your product, researches the landscape, proposes a complete design system (aesthetic, typography, color, layout, spacing, motion), and generates font+color preview... (gstack)\n- design-html: Design finalization: generates production-quality Pretext-native HTML/CSS. (gstack)\n- design-review: Designer's eye QA: finds visual inconsistency, spacing issues, hierarchy problems, AI slop patterns, and slow interactions — then fixes them. (gstack)\n- design-shotgun: Design shotgun: generate multiple AI design variants, open a comparison board, collect structured feedback, and iterate. (gstack)\n- devex-review: Live developer experience audit. (gstack)\n- diagnose: Disciplined diagnosis loop for hard bugs and performance regressions. Reproduce → minimise → hypothesise → instrument → fix → regression-test. Use when user says \"diagnose this\" / \"debug this\", reports a bug, says something is broken/throwing/failing, or describes a performance regression.\n- diagram: Turn an English description (or mermaid source) into a diagram triplet: the source, an editable .excalidraw file you can open (gstack)\n- document-generate: Generate missing documentation from scratch for a feature, module, or entire project. (gstack)\n- document-release: Post-ship documentation update. (gstack)\n- find-skills: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n- force-stop: Force-stop an SPT agent — a ready agent, or a live agent and its Psyche. Use when the user\nsays \"stop listening\", \"kill live agent\", \"force stop\", or \"tear down perch\". Session-aware: a\nlive target tears its Psyche down too.\n\n- freeze: Restrict file edits to a specific directory for the session. (gstack)\n- frontend-design: Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.\n- grill-me: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions \"grill me\".\n- grill-with-docs: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.\n- gsd-add-tests: Generate tests for a completed phase based on UAT criteria and implementation\n- gsd-ai-integration-phase: Generate an AI-SPEC.md design contract for phases that involve building AI systems.\n- gsd-audit-fix: Autonomous audit-to-fix pipeline — find issues, classify, fix, test, commit\n- gsd-audit-milestone: Audit milestone completion against original intent before archiving\n- gsd-audit-uat: Cross-phase audit of all outstanding UAT and verification items\n- gsd-autonomous: Run all remaining phases autonomously — discuss→plan→execute per phase\n- gsd-capture: Capture ideas, tasks, notes, and seeds to their destination\n- gsd-cleanup: Archive accumulated phase directories from completed milestones\n- gsd-code-review: Review source files changed during a phase for bugs, security issues, and code quality problems\n- gsd-complete-milestone: Archive completed milestone and prepare for next version\n- gsd-config: Configure GSD settings — workflow toggles, advanced knobs, integrations, and model profile\n- gsd-debug: Systematic debugging with persistent state across context resets\n- gsd-discuss-phase: Gather phase context through adaptive questioning before planning.\n- gsd-docs-update: Generate or update project documentation verified against the codebase\n- gsd-eval-review: Audit an executed AI phase's evaluation coverage and produce an EVAL-REVIEW.md remediation plan.\n- gsd-execute-phase: Execute all plans in a phase with wave-based parallelization\n- gsd-explore: Socratic ideation and idea routing — think through ideas before committing to plans\n- gsd-extract-learnings: Extract decisions, lessons, patterns, and surprises from completed phase artifacts\n- gsd-fast: Execute a trivial task inline — no subagents, no planning overhead\n- gsd-forensics: Post-mortem investigation for failed GSD workflows — diagnoses what went wrong.\n- gsd-graphify: Build, query, and inspect the project knowledge graph in .planning/graphs/\n- gsd-health: Diagnose planning directory health and optionally repair issues\n- gsd-help: Show available GSD commands and usage guide\n- gsd-import: Ingest external plans with conflict detection against project decisions before writing anything.\n- gsd-inbox: Triage and review open GitHub issues and PRs against project templates and contribution guidelines.\n- gsd-ingest-docs: Bootstrap or merge a .planning/ setup from existing ADRs, PRDs, SPECs, and docs in a repo.\n- gsd-manager: Interactive command center for managing multiple phases from one terminal\n- gsd-map-codebase: Analyze codebase with parallel mapper agents to produce .planning/codebase/ documents\n- gsd-milestone-summary: Generate a comprehensive project summary from milestone artifacts for team onboarding and review\n- gsd-mvp-phase: Plan a phase as a vertical MVP slice — user story, SPIDR splitting, then plan-phase\n- gsd-new-milestone: Start a new milestone cycle — update PROJECT.md and route to requirements\n- gsd-new-project: Initialize a new project with deep context gathering and PROJECT.md\n- gsd-ns-context: codebase intelligence | map graphify docs learnings\n- gsd-ns-ideate: exploration capture | explore sketch spike spec capture\n- gsd-ns-manage: config workspace | workstreams thread update ship inbox\n- gsd-ns-project: project lifecycle | milestones audits summary\n- gsd-ns-review: quality gates | code review debug audit security eval ui\n- gsd-ns-workflow: workflow | discuss plan execute verify phase progress\n- gsd-pause-work: Create context handoff when pausing work mid-phase\n- gsd-phase: CRUD for phases in ROADMAP.md — add, insert, remove, or edit phases\n- gsd-plan-phase: Create detailed phase plan (PLAN.md) with verification loop\n- gsd-plan-review-convergence: Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain.\n- gsd-pr-branch: Create a clean PR branch by filtering out .planning/ commits — ready for code review\n- gsd-profile-user: Generate developer behavioral profile and create Claude-discoverable artifacts\n- gsd-progress: Check progress, advance workflow, or dispatch freeform intent — the unified GSD situational command\n- gsd-quick: Execute a quick task with GSD guarantees (atomic commits, state tracking) but skip optional agents\n- gsd-resume-work: Resume work from previous session with full context restoration\n- gsd-review: Request cross-AI peer review of phase plans from external AI CLIs\n- gsd-review-backlog: Review and promote backlog items to active milestone\n- gsd-secure-phase: Retroactively verify threat mitigations for a completed phase\n- gsd-settings: Configure GSD workflow toggles and model profile\n- gsd-ship: Create PR, run review, and prepare for merge after verification passes\n- gsd-sketch: Sketch UI/design ideas with throwaway HTML mockups, or propose what to sketch next (frontier mode)\n- gsd-spec-phase: Clarify WHAT a phase delivers with ambiguity scoring; produces a SPEC.md before discuss-phase.\n- gsd-spike: Spike an idea through experiential exploration, or propose what to spike next (frontier mode)\n- gsd-stats: Display project statistics — phases, plans, requirements, git metrics, and timeline\n- gsd-surface: Toggle which skills are surfaced — apply a profile, list, or disable a cluster without reinstall\n- gsd-thread: Manage persistent context threads for cross-session work\n- gsd-ui-phase: Generate UI design contract (UI-SPEC.md) for frontend phases\n- gsd-ui-review: Retroactive 6-pillar visual audit of implemented frontend code\n- gsd-ultraplan-phase: [BETA] Offload plan phase to Claude Code's ultraplan cloud; review in browser and import back.\n- gsd-undo: Safe git revert. Roll back phase or plan commits using the phase manifest with dependency checks.\n- gsd-update: Update GSD to latest version with changelog display\n- gsd-validate-phase: Retroactively audit and fill Nyquist validation gaps for a completed phase\n- gsd-verify-work: Validate built features through conversational UAT\n- gsd-workspace: Manage GSD workspaces — create, list, or remove isolated workspace environments\n- gsd-workstreams: Manage parallel workstreams — list, create, switch, status, progress, complete, and resume\n- gstack: Router for the gstack skill suite. (gstack)\n- gstack-upgrade: Upgrade gstack to the latest version.\n- guard: Full safety mode: destructive command warnings + directory-scoped edits. (gstack)\n- handoff: Compact the current conversation into a handoff document for another agent to pick up.\n- health: Code quality dashboard. (gstack)\n- improve-codebase-architecture: Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.\n- investigate: Systematic debugging with root cause investigation. (gstack)\n- ios-clean: Remove the DebugBridge SPM package and all #if DEBUG wiring from an iOS app. (gstack)\n- ios-design-review: Visual design audit for iOS apps on real hardware. (gstack)\n- ios-fix: Autonomous iOS bug fixer. (gstack)\n- ios-qa: Live-device iOS QA for SwiftUI apps. (gstack)\n- ios-sync: Regenerate the iOS debug bridge against the latest upstream gstack templates. (gstack)\n- land-and-deploy: Land and deploy workflow. (gstack)\n- landing-report: Read-only queue dashboard for workspace-aware ship. (gstack)\n- learn: Manage project learnings.\n- list-agents: List SPT agents and endpoints reachable from this node. Use when the user says \"list agents\",\n\"who's live\", \"who's listening\", or wants a roster of active perches.\n\n- live: Run a live agent session. For past sessions, restores a summarized context.\n\nEXPLICIT START phrases (route to /sptc:live <id>):\n- \"live as\"\n- \"start live\"\n- \"go live\"\n- \"start a live agent\"\n\nAUTO-RESUME phrases (route to /sptc:live --auto, resumes most-recently-active live agent):\n- continue live work\n- resume live work\n- continue live agent\n- resume live agent\n- live agent continue\n- live agent resume\n- live work continue\n- live work resume\n\nDoes NOT route here (too ambiguous — require BOTH \"live\" AND (\"agent\" or \"work\")):\n- \"keep going\"\n- \"resume work\"\n- \"continue\" (bare)\n\n- make-pdf: Turn any markdown file into a publication-quality PDF. (gstack)\n- mempalace: MemPalace — mine projects and conversations into a searchable memory palace. Use when asked about mempalace, memory palace, mining memories, searching memories, or palace setup.\n- mux-send: Send keystrokes to a psmux terminal session. Use when the user says \"send to\",\n\"type in\", \"send keys to\", \"mux send\", or wants to send input to another terminal\nsession by name.\n\n- office-hours: YC Office Hours — two modes. (gstack)\n- open-gstack-browser: Launch GStack Browser — AI-controlled Chromium with the sidebar extension baked in.\n- pair-agent: Pair a remote AI agent with your browser. (gstack)\n- plan-ceo-review: CEO/founder-mode plan review. (gstack)\n- plan-design-review: Designer's eye plan review — interactive, like CEO and Eng review. (gstack)\n- plan-devex-review: Interactive developer experience plan review. (gstack)\n- plan-eng-review: Eng manager-mode plan review. (gstack)\n- plan-tune: Self-tuning question sensitivity + developer psychographic for gstack (v1: observational). (gstack)\n- propose-issue: Challenge an issue statement before fixing it. Probes scope, root cause, and assumptions\nthrough interactive questioning grounded in the codebase. Use when the user says \"propose issue\",\n\"challenge this bug\", \"question this fix\", \"is this the right problem\", or wants to pressure-test\na problem statement before jumping to implementation.\n\n- qa: Systematically QA test a web application and fix bugs found. (gstack)\n- qa-only: Report-only QA testing. (gstack)\n- ready: Make this Claude Code session reachable for inter-agent messages (register a perch and\nlisten). Use when the user says \"listen as\", \"ready as\", or wants to receive messages from\nother agents.\n\n- retro: Weekly engineering retrospective. (gstack)\n- review: Pre-landing PR review. (gstack)\n- role: Show or edit this endpoint's durable role — its statement of purpose in the mind. Use when the user\nsays \"show my role\", \"edit my role\", \"update my role\", or runs /sptc:role. Pass a directive to change\nit in words (e.g. /sptc:role make it senior); a bare /sptc:role opens an in-input-box editor\nround-trip on a live session; add --include-desc to also edit the service description.\n\n- scrape: Pull data from a web page. (gstack)\n- screenshot-capture: Capture, process, and analyze screenshots of any running application on Windows 10/11. Use this skill whenever the user asks to take a screenshot, capture what's on screen, grab an app window, extract text from a visible app, annotate a screenshot, read what's on screen, OCR a running application, document a UI state, or visually inspect any running program. Also trigger when the user says things like \"show me what [app] looks like\", \"grab that window\", \"read the text in [app]\", \"mark up a screenshot\", \"capture my desktop\", or any task involving visual inspection of running software. This skill handles full-screen captures, individual window captures by app name, OCR text extraction, and image annotation (highlights, arrows, text labels). Even if the user doesn't say \"screenshot\" explicitly, use this skill if they want to see, capture, read, or annotate anything currently displayed on their screen.\n\n- search: Instantly search files and folders on Windows via voidtools Everything 1.5a HTTP JSON API. Use when the user wants to find files by name, path, size, date, or extension across the whole system — much faster than Glob/Grep for filesystem-wide queries. Triggers on \"find file\", \"where is\", \"locate\", \"search my drive\", \"find all .ext files\", \"everything search\".\n- send: Send a message to another SPT agent. Use when the user says \"send to\", \"message\",\n\"tell <agent>\", or when you need to reach or reply to another agent yourself.\n\n- setup: Install or repair spt-core AND activate the claude-spt adapter for this Claude Code session\n(mid-session installer + post-install activation).\n\n- setup-browser-cookies: Import cookies from your real Chromium browser into the headless browse session. (gstack)\n- setup-deploy: Configure deployment settings for /land-and-deploy.\n- setup-gbrain: Set up gbrain for this coding agent: install the CLI, initialize a local PGLite or Supabase brain, register MCP, capture per-remote trust policy. (gstack)\n- ship: Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. (gstack)\n- signoff: Gracefully shut down your live session, saving a final context summary. Use when the user says\n\"sign off\" or \"graceful stop\", or when you (a live agent) are done and want to go offline cleanly.\n\n- skillify: Codify the most recent successful /scrape flow into a permanent browser-skill on disk. (gstack)\n- spec: Turn vague intent into a precise, executable spec in five phases. (gstack)\n- spt-endpoint: Hosts an adapterless SPT endpoint perch inside Oh My Pi and reliably surfaces incoming messages through harness background-job completion. Use when asked to run, host, start, or listen as an SPT endpoint in OMP, especially when an adapter-backed endpoint or a never-ending `spt ready` listener is unsuitable.\n- subnet: Manage subnet membership — create a subnet, show a pairing code to invite a machine, or join\nan existing one. Use when the user wants to pair machines, set up a subnet, or reach agents\nacross machines. Cross-machine /sptc:send and live agents depend on it.\n\n- sync-gbrain: Keep gbrain current with this repo's code and refresh agent search guidance in CLAUDE.md. Wraps the gstack-gbrain-sync orchestrator with state (gstack)\n- to-prd: Turn the current conversation context into a PRD and publish it to the project issue tracker. Use when user wants to create a PRD from the current context.\n- trace: Find old conversations in Claude Code session history. Use when the user says\n\"find the conversation where\", \"when did we discuss\", \"trace back to\", or wants\nto locate a past session by topic, command, commit, or keyword.\n\n- traceable-reqs: Install or update the traceable-reqs CLI and apply it correctly in host repos: author manifests, place tags near evidence, audit scan roots, interpret findings, and answer coverage questions through the CLI.\n- unfreeze: Clear the freeze boundary set by /freeze, allowing edits to all directories again. (gstack)\n- version: Report the spt-core-tracked adapter version (the version-of-truth, not the marketplace plugin\nversion). Use when the user asks the spt or adapter version, or runs /sptc:version.\n\n- win-marketplace-add: Add a plugin marketplace on Windows, bypassing the EBUSY rename bug. Use when the user wants to add a marketplace, runs \"/plugin marketplace add\" and gets an EBUSY error, or needs to register a marketplace that was manually cloned. Trigger on: \"add marketplace\", \"plugin marketplace add\", \"EBUSY marketplace\", \"register marketplace\", \"marketplace not found\".\n\n- write-a-skill: Create new agent skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill.\n</skills>\n# Internal URLs\nSpecial URLs for internal resources; with most FS/bash tools they auto-resolve to FS paths.\n- `skill://<name>`: skill instructions; `/<path>` = file within\n- `rule://<name>`: rule details\n- `agent://<id>`: agent output artifact; `/<path>` extracts a JSON field\n- `artifact://<id>`: artifact content\n- `local://<name>.md`: plan artifacts or shared content for subagents\n- `mcp://<uri>`: MCP resource\n- `issue://<N>` (or `issue://<owner>/<repo>/<N>`): GitHub issue, disk-cached. Bare lists recent issues; `?state=open|closed|all&limit=&author=&label=`.\n- `pr://<N>` (or `pr://<owner>/<repo>/<N>`): GitHub PR, same cache; `?comments=0` drops comments. Bare lists recent PRs; `?state=open|closed|merged|all&limit=&author=&label=`.\n- `omp://`: harness docs; AVOID unless the user asks about the harness itself.\n\n# Tool Inventory\n- Read: `read`\n- Grep: `grep`\n- Glob: `glob`\n- Web Search: `web_search`\n- Submit Result: `yield`\n- IRC: `irc`\n- mempalace:mempalace/mempalace_add_drawer: `mcp__mempalace_mempalace_mempalace_add_drawer`\n- mempalace:mempalace/mempalace_check_duplicate: `mcp__mempalace_mempalace_mempalace_check_duplicate`\n- mempalace:mempalace/mempalace_delete_drawer: `mcp__mempalace_mempalace_mempalace_delete_drawer`\n- mempalace:mempalace/mempalace_diary_read: `mcp__mempalace_mempalace_mempalace_diary_read`\n- mempalace:mempalace/mempalace_diary_write: `mcp__mempalace_mempalace_mempalace_diary_write`\n- mempalace:mempalace/mempalace_find_tunnels: `mcp__mempalace_mempalace_mempalace_find_tunnels`\n- mempalace:mempalace/mempalace_get_aaak_spec: `mcp__mempalace_mempalace_mempalace_get_aaak_spec`\n- mempalace:mempalace/mempalace_get_taxonomy: `mcp__mempalace_mempalace_mempalace_get_taxonomy`\n- mempalace:mempalace/mempalace_graph_stats: `mcp__mempalace_mempalace_mempalace_graph_stats`\n- mempalace:mempalace/mempalace_kg_add: `mcp__mempalace_mempalace_mempalace_kg_add`\n- mempalace:mempalace/mempalace_kg_invalidate: `mcp__mempalace_mempalace_mempalace_kg_invalidate`\n- mempalace:mempalace/mempalace_kg_query: `mcp__mempalace_mempalace_mempalace_kg_query`\n- mempalace:mempalace/mempalace_kg_stats: `mcp__mempalace_mempalace_mempalace_kg_stats`\n- mempalace:mempalace/mempalace_kg_timeline: `mcp__mempalace_mempalace_mempalace_kg_timeline`\n- mempalace:mempalace/mempalace_list_rooms: `mcp__mempalace_mempalace_mempalace_list_rooms`\n- mempalace:mempalace/mempalace_list_wings: `mcp__mempalace_mempalace_mempalace_list_wings`\n- mempalace:mempalace/mempalace_search: `mcp__mempalace_mempalace_mempalace_search`\n- mempalace:mempalace/mempalace_status: `mcp__mempalace_mempalace_mempalace_status`\n- mempalace:mempalace/mempalace_traverse: `mcp__mempalace_mempalace_mempalace_traverse`\n- GenerateImage: `generate_image`\n\nTOOL POLICY\n==============\n\n# General\nUse tools whenever they improve correctness, completeness, or grounding.\n- You MUST complete the task using available tools.\n- SHOULD resolve prerequisites before acting.\n- NEVER stop at the first plausible answer if another call would cut uncertainty.\n- Empty, partial, or suspiciously narrow lookup? Retry with a different strategy.\n- SHOULD parallelize independent calls.\n# Tool I/O\n- Prefer relative paths for `path`-like fields.\n- Most tools take `i`: a concise intent, present participle, 2–6 words, no period, capitalized.\n# Specialized Tools\nYou MUST use the specialized tool over its shell equivalent:\n- File or directory reads → `read` (a directory path lists entries).\n- Regex search → `grep`, not `grep`, `rg`, or `awk`.\n- Globbing → `glob`, not `ls **/*.ext` or `fd`.\n# Exploration\nYou NEVER open a file hoping. Hope is not a strategy.\n- You MUST load only what's necessary; AVOID reading files or sections you don't need.\n- Use `grep` to locate targets.\n- Use `glob` to map structure.\n- Use `read` with offset/limit instead of whole-file reads.\n# Delegation\n\nEXECUTION WORKFLOW\n==============\n\n# 1. Scope\n- Read relevant skills first.\n- For multi-file work, plan before touching files; research existing code and conventions first.\n\n# 2. Research Before Editing\n- Read sections, not snippets. You MUST reuse existing patterns; a second convention beside an existing one is PROHIBITED.\n\n- Re-read before acting if a tool fails or a file changed since you read it.\n\n# 3. Decompose\n- Update todos as you go; skip them for trivial requests. Marking a todo done is a transition: start the next in the same turn.\n- NEVER abandon phases under scope pressure—delegate, don't shrink.\n\n- Plan only what makes the request work. Cleanup—changelog, tests, docs—is NOT planned up front; it belongs to the final phase below.\n\n# 4. Implement\n- Fix problems at the source. Remove obsolete code—no leftover comments, aliases, or re-exports.\n- Prefer updating existing files over creating new ones.\n- Review changes from the user's perspective.\n- Grep instead of guessing.\n- Don't run destructive git commands or delete code you didn't write.\n\n# 5. Verify\n- NEVER yield non-trivial work without proof: tests, E2E, browsing, or QA.\n- Every test MUST defend an observable contract and fail on a plausible bug.\n- Test behavior, boundaries, invariants, transitions, precedence, and real errors—not plumbing, source text, or incidental defaults.\n- Match existing conventions; keep tests deterministic, isolated, and full-suite safe.\n- Run only touched tests; small/no-test changes still REQUIRE a focused behavioral smoke test.\n\n# 6. Cleanup\nChangelog, tests, docs, and removing scaffolding are the LAST phase—NEVER skipped, but gated on the request demonstrably working.\n\n- NEVER start, pre-plan, or pre-allocate todos for cleanup before you've made the request work and smoke-tested it. Until then, every edit serves correctness; housekeeping NEVER steers the design.\n- Once your smoke test confirms “it works,” do the cleanup in full before yielding.\n\nDELIVERY CONTRACT\n==============\n\n<contract>\nInviolable.\n- NEVER yield unless the deliverable is complete. A phase boundary, todo flip, or sub-step is NEVER a yield point—continue in the same turn.\n- NEVER fabricate outputs. Claims about code, tools, tests, docs, or sources MUST be grounded.\n- NEVER substitute an easier or more familiar problem:\n  - Don't infer extra scope—retries, validation, telemetry, abstraction “while you're at it”—because it changes the contract.\n  - Don't solve the symptom—suppress a warning or exception, special-case an input—unless asked. Do the real ask.\n- NEVER ask for what tools, repo context, or files can provide.\n- NEVER punt half-solved work back.\n- Default to clean cutover: migrate every caller; leave no shims, aliases, or deprecated paths.\n</contract>\n\n<completeness>\n- “Done” means the deliverable behaves as specified end to end—not that a scaffold compiles or a narrowed test passes.\n- A named plan, phase list, checklist, or spec MUST satisfy every acceptance criterion. A plausible subset is failure, not partial success.\n- NEVER silently shrink scope. Reduce scope only with explicit user approval in this conversation; otherwise do the full work—exhaust every tool and angle.\n- NEVER ship stubs, placeholders, mocks, no-ops, fake fallbacks, or `TODO: implement` as delivered work. If real implementation needs unavailable information, state the missing prerequisite and implement everything else.\n- NEVER relabel unfinished work—“scaffold,” “MVP,” “v1,” “foundation,” “follow-up”—to imply completion. Not done? Say so.\n</completeness>\n\n<evidence-and-output>\n- Output format MUST match the ask.\n- Every claim about code, tools, tests, docs, or sources MUST be grounded.\n- Mark any claim not directly observed or established as `[INFERENCE]`.\n- Verification claims MUST match what was exercised, preferably smoke tested.\n- No required tool lookup may be skipped when it would cut uncertainty.\n- Be brief in prose, not in evidence, verification, or blocking details.\n</evidence-and-output>\n\n<yielding>\nBefore yielding, verify:\n- All requested deliverables are complete; no partial implementation is presented as complete.\n- All affected artifacts—callsites, tests, docs—are updated or intentionally left unchanged.\n- The output and evidence requirements above are satisfied.\n\nBefore declaring blocked:\n- Be sure the information is unreachable through tools, context, or anything in reach. One failing check does not mean blocked—finish all remaining work first.\n- Still stuck? State exactly what's missing and what you tried.\n</yielding>\n<critical>\n- NEVER narrate or consider session limits, token or tool budgets, effort estimates, or how much you can finish. Not your concern—start as if unbounded; execute or delegate.\n- NEVER re-audit an applied edit; NEVER run git subcommands as routine validation. Tool results are THE verification.\n</critical>\n\nROLE\n===================================\n\nInvestigate the codebase rapidly. Return structured findings another agent can use without re-reading everything.\n\n<directives>\n- You MUST use tools for broad pattern matching / code search as much as possible.\n- You SHOULD invoke tools in parallel—this is a short investigation, and you are supposed to finish in a few seconds.\n- If a search returns empty results, you MUST try at least one alternate strategy (different pattern, broader path, or AST search) before concluding the target doesn't exist.\n</directives>\n\n<thoroughness>\nYou MUST infer the thoroughness from the task; default to medium:\n- **Quick**: Targeted lookups, key files only\n- **Medium**: Follow imports, read critical sections\n- **Thorough**: Trace all dependencies, check tests/types.\n</thoroughness>\n\n<procedure>\n1. Locate relevant code using tools.\n2. Read key sections. NEVER read full files unless they're tiny.\n3. Identify types/interfaces/key functions.\n4. Note dependencies between files.\n</procedure>\n\n<critical>\nYou MUST operate as read-only. You NEVER write, edit, or modify files, nor execute any state-changing commands, via git, build system, package manager, etc.\nYou MUST keep going until complete.\n</critical>\n\nYou are specializing as: **Release Regression 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\nIdentify why deployed spt v0.36 broker PID 72488 and brain PID 58272 sustain CPU and extreme I/O with 4-5 hosted sessions.\n# Constraints\nRead-only investigation. Do not edit. Skip formatters, linters, and tests. Evidence: broker 532-541 threads but only 4 CPU-active over 5s; brain 18-24 threads with 5 active. 20s means: brain 15.5% one-core CPU and 63.91 MiB/s reads; broker 11.4% one-core CPU and 3.74 MiB/s writes. daemon log grows only ~563 B/s. Current release is v0.36.0 after REDISPATCH fixes.\n# Contract\nReturn exact source paths/lines, loop semantics, and falsifiable probes that distinguish normal activity from leaks/spins.\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 `RedispatchCpuRca`. Currently visible peers:\n- `Main` — main (main, running)\n- `RefreshLifecycle` — Daemon Lifecycle Investigator (sub, parked)\n- `PtyControl` — PTY Transport Investigator (sub, parked)\n- `ReproHarness` — Integration Test Investigator (sub, parked)\n- `HyphenMessage` — CLI Parsing Investigator (sub, parked)\n- `ElevatedRc` — Remote Control Investigator (sub, parked)\n- `ElevatedMessaging` — Messaging Lifecycle Investigator (sub, parked)\n- `RcSplitBrain` — Registry Session Investigator (sub, parked)\n- `ElevatedHosted` — Windows Token Investigator (sub, parked)\n- `ShortformDifferential` — Delivery Differential Investigator (sub, parked)\n- `AdapterFreshSemantics` — Claude Adapter Investigator (sub, parked)\n- `PerriDurableState` — State Forensics Investigator (sub, parked)\n- `CoreFreshSemantics` — Core Lifecycle Investigator (sub, parked)\n- `RestStateRca` — Endpoint Lifecycle Investigator (sub, parked)\n- `CreateNoopRca` — CLI Semantics Investigator (sub, parked)\n- `ControlledStampRca` — Broker Control-State Investigator (sub, parked)\n- `EmphRestartRca` — Daemon Restart Investigator (sub, parked)\n- `HarnessExitRca` — Harness Lifecycle Investigator (sub, parked)\n- `V034DiffRca` — Regression Archaeologist (sub, parked)\n- `V034ReleaseRca` — Release Contract Investigator (sub, parked)\n- `LiveLogRca` — Runtime Telemetry Investigator (sub, parked)\n- `PtyPathRca` — PTY Concurrency Investigator (sub, parked)\n- `BrokerCpuRca` — Broker Concurrency Investigator (sub, running)\n- `BrainCpuRca` — Brain Performance Investigator (sub, running)\nIdle/parked peers are not gone: messaging them wakes (or revives) them.\n\nUse `irc` only for quick coordination, never long-form content. Address peers by id or use `\"all\"` to broadcast.\n- Discovery: the roster above shows each peer's role and what it is doing now; `irc` op:\"list\" refreshes it.\n- Coordination: before you edit a file or start work a sibling may already own, message that peer first — overlapping edits collide.\n- Follow-up: answer a peer's question with a short reply (set `replyTo`); use `await` only when you genuinely cannot proceed without the answer.\n\nCOMPLETION\n===================================\n\nNo TODO tracking, no progress updates. Execute; report results with `yield`.\n\nWhile work remains, you MUST continue with another tool call — investigate, edit, run, verify. Save narrative for a terminal `yield` unless you intentionally record an incremental section.\n\nYield protocol:\n- Omit `type` for the normal single terminal structured result in `result.data`.\n- Use non-empty `type: string[]` for incremental, non-terminal sections; calls accumulate by section.\n- Use `type: string` for a terminal result; if data is omitted, your last assistant turn becomes the raw final result.\n\nThis is your only way to return a final result. For structured results, you NEVER put JSON in plain text or substitute a text summary for `result.data`.\n\nYour terminal `yield` MUST use exactly this shape — the schema fields go inside `result.data`, NEVER at the top level and NEVER as a stringified summary:\n```ts\nresult: {\n  data: {\n    summary: string;\n    files: { path: string; description: string; }[];\n    architecture: string;\n  };\n}\n```\n\nGiving up is a last resort. If truly blocked, you MUST terminal-yield `result.error` describing what you tried and the exact blocker.\nYou NEVER give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.\n\nYou MUST keep going until this ticket is closed. This matters.\n\nPROJECT\n===================================\n\n<workstation>\n- OS: win32 10.0.26200\n- Distro: Windows_NT\n- Kernel: Windows 11 Pro\n- Arch: x64\n- GPU: Parsec Virtual Display Adapter\n- Terminal: Windows Terminal\n- Model: openai-codex/gpt-5.6-sol\n</workstation>\n\n<context>\nYou MUST follow the context files below for all tasks:\n<file path=\"C:\\Users\\decid\\.claude\\CLAUDE.md\">\n## Reference Documents\n\nThe `~/.claude/reference_docs/` directory contains reference guides available across all projects.\n\n- **[claude-code-hooks.md](reference_docs/claude-code-hooks.md)** -- Complete Claude Code hooks reference: all 27 event types, input/output JSON schemas, matcher patterns, exit code behavior, environment variables, and caveats. Consult this when building or debugging hooks.\n</file>\n</context>\nThe context files above are loaded automatically. You NEVER `grep`/`glob` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise.\nToday is 2026-07-16, and the current working directory is 'C:/Users/decid/Documents/projects/spt-core'.\n\n<critical>\n- Each response MUST advance the task. There is no stopping condition other than completion.\n- You MUST default to informed action; do not ask for confirmation when tools or repo context can answer.\n- You MUST verify the effect of significant behavioral changes before yielding: run the specific test, command, or scenario that covers your change.\n</critical>","task":"Complete the assignment below, thoroughly:\n\n# Target\nv0.36 REDISPATCH changes and tests/history available in repository. Non-goal: unrelated lifecycle code.\n# Change\nAssess whether stream retirement/reconstruction fixes can cause retained broker workers, replay loops, or connection churn after update/refresh. Compare intended O(active) behavior with thread/process evidence.\n# Acceptance\nGive a source-grounded causal assessment and targeted regression-test seam.","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":"1028a395","parentId":"988a4914","timestamp":"2026-07-17T06:57:30.378Z","message":{"role":"user","content":[{"type":"text","text":"Complete the assignment below, thoroughly:\n\n# Target\nv0.36 REDISPATCH changes and tests/history available in repository. Non-goal: unrelated lifecycle code.\n# Change\nAssess whether stream retirement/reconstruction fixes can cause retained broker workers, replay loops, or connection churn after update/refresh. Compare intended O(active) behavior with thread/process evidence.\n# Acceptance\nGive a source-grounded causal assessment and targeted regression-test seam."}],"attribution":"agent","timestamp":1784271450368}}
{"type":"message","id":"a3acc7ff","parentId":"1028a395","timestamp":"2026-07-17T06:57:39.999Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning skill-based code exploration**"},{"type":"toolCall","id":"call_XofIOQgfbnjf1jpWOKiGm6lN|fc_089095e18118ee7d016a59d263d7d8819098cf18cd651f58de","name":"read","arguments":{"i":"Reading diagnosis workflow","path":"skill://diagnose","selector":null}},{"type":"toolCall","id":"call_6XBdXxIbQr3bv1AXlXe1UrDz|fc_089095e18118ee7d016a59d263d7ec8190a302c6b29278fcfb","name":"grep","arguments":{"i":"Locating redispatch changes","pattern":"REDISPATCH|redispatch|re-dispatch|redispatching","path":".","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_W3Jo7xmZcMSbQlpPweo236pi|fc_089095e18118ee7d016a59d263d7f48190935a83a8f4854fd4","name":"grep","arguments":{"i":"Locating stream retirement","pattern":"retir|reconstruct|replay|worker|stream","path":"src; tests; test","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_ZOYIZTOB3PEBB0KNHd41VDRZ|fc_089095e18118ee7d016a59d263d7fc8190b6fada2b8a6bd7f4","name":"glob","arguments":{"i":"Mapping broker sources","path":"**/*{broker,stream,dispatch,worker}*","hidden":true,"gitignore":true,"limit":300}},{"type":"toolCall","id":"call_WXnpzlRo7cVpP9E1Aidf8IgC|fc_089095e18118ee7d016a59d263d8048190984d078de4bdc00f","name":"glob","arguments":{"i":"Mapping repository history","path":".git/{logs,refs}/**/*","hidden":true,"gitignore":false,"limit":300}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":14895,"output":345,"cacheRead":0,"cacheWrite":0,"totalTokens":15240,"reasoningTokens":101,"cost":{"input":0.074475,"output":0.01035,"cacheRead":0,"cacheWrite":0,"total":0.084825}},"stopReason":"toolUse","timestamp":1784271450427,"responseId":"resp_089095e18118ee7d016a59d25b707c819094afa6da88052e69","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d25d28408190a49d9a1f943bcf0a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdJjLUeyhyEVT_r_DNwvFQLsCFLVjQBt1ZqFMTD15AbkJHgWcCquSB09cuO1MQLmlfLNXAf53UJrX_kBygqi2bOhrH6HUJ9l28nzGcpsiJePvEL1UmG3-JAJa-UHc3I9JJq5vWBHesCwkgL68XOf2zBAlj41USxelA54NsWjpgySisjBsgiIoApQOoaeat-fNZ_CPHtyD6_cAaz8D3ITYYV0YntH9rawNGPLAVTvPBfLW8XjKMQQB8WCUl4x8pF9fsn6mj3gJHy6uop4E6JCbkYLla1jIE7FJ47yqM4-tCTGDE_7_x7dxurZS4Zm6wUQE7xj-mIE3m_lBtNdrVpTUIHvMKVvxePAGPccld_UImWQKFc6AXYBbVQwyxeSZV7sU7pQB-qSmkeRXM6Vu56kFjQI3BQju6krPHGtvtZj03ptIld0yFIQgHonaGkX9xSX0RmvD59ke1jGoXK2T_TCLntEIDVMCqvTkmoQIbG87YlS77OKUd-FvDZOPHaJgCE6bdup_2xxCiv_w5WXj3tDqN-qOmTF8-DofQTxEI9KxdTFYp8t4sip8XfMzl3-Odr4pc566Bb606S0rC1q7YNrvyuRKqnXCyJzWFINybtcC8sfCFG2FJw7yYe-b3WySv4KpEpskftgg5rtE6LGExIA3eLgoTWj1d45GLFsIw8TiTMQWV7cT9GZvbx_NC8s_hSxR9PB9cSeCbH-i6255n9kEKl7TMPoiQUILAHdwysuG7vW7vVGxLfn57FYG4CSrkzYNz2KRCBpVIFmSMT77E5lLejXa09BmXiceA2vhN9ImfsCiYxzrgupfY3kvyD8cTq2_xb7EWQnBZdiX6AI7sWFYEkna3pIXT7VF30zFVou7UHAbtJFGPYUzgmh0JvmV2TN7108n4Fv7-KhfU1KGvWu-G48iZDwG_3508iEIiRzakk1M4ViETgPwQRiImq5KtehbMlamgpgkYA_FThEXxvV6CCuvBUhhGvR98zhDnkk28eSP9_WMN6LpeeqxzGFdvYQYNRzXO9CUK69hW0N6Dvi1mqAXXJzznAre614vD7o4pLqgG2BFVSm60MHwh79zMfar1PtVE90nmbqmJCk6sC5FF_9kVByX5oBXoSWodwg_iVOZOF35X5BjupRld7GmrfRfC3d7KNkllB46xTWhka2aWx66tmbrKHRYrH50FNMogziPSSj45fhrqdy8ySS3nTfzX8yra_4I42wEkXvYsufo4ghiPmIpbw7uU43h5OIy5RnyXC3x7ga05pj4ItaUmiqbTwtPuMZw29sZGx2uSc4X7Di73beg9WQ87axFt4cLYV8ZpPpQVcc8_NwFxth6Zus-uGt-ahHxdChOP0W0izUL9ShOU8ivsc-d54b8iUkUwDCLiInMEhst6izyJLASgfqN3kpIz98ZWoMkSGNYzRclnYAZIb5A7bdb8B5oNA3OhRbyNQOPnCAr4atP3QA2jsT3wfCHAlMYPqVJNuL4YOpxtYg3YIt8Agbq4YVq884BefpFJg4oMbVCp6CmN7Xcm9MyCUnlpMxThFtkZamq28FqemSEw==","summary":[{"type":"summary_text","text":"**Planning skill-based code exploration**"}]},{"id":"fc_089095e18118ee7d016a59d263d7d8819098cf18cd651f58de","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading diagnosis workflow\",\"path\":\"skill://diagnose\",\"selector\":null}","call_id":"call_XofIOQgfbnjf1jpWOKiGm6lN","name":"read"},{"id":"fc_089095e18118ee7d016a59d263d7ec8190a302c6b29278fcfb","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating redispatch changes\",\"pattern\":\"REDISPATCH|redispatch|re-dispatch|redispatching\",\"path\":\".\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_6XBdXxIbQr3bv1AXlXe1UrDz","name":"grep"},{"id":"fc_089095e18118ee7d016a59d263d7f48190935a83a8f4854fd4","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating stream retirement\",\"pattern\":\"retir|reconstruct|replay|worker|stream\",\"path\":\"src; tests; test\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_W3Jo7xmZcMSbQlpPweo236pi","name":"grep"},{"id":"fc_089095e18118ee7d016a59d263d7fc8190b6fada2b8a6bd7f4","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping broker sources\",\"path\":\"**/*{broker,stream,dispatch,worker}*\",\"hidden\":true,\"gitignore\":true,\"limit\":300}","call_id":"call_ZOYIZTOB3PEBB0KNHd41VDRZ","name":"glob"},{"id":"fc_089095e18118ee7d016a59d263d8048190984d078de4bdc00f","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping repository history\",\"path\":\".git/{logs,refs}/**/*\",\"hidden\":true,\"gitignore\":false,\"limit\":300}","call_id":"call_WXnpzlRo7cVpP9E1Aidf8IgC","name":"glob"}]},"duration":9569.73860000074,"ttft":2742.6312000006437,"contextSnapshot":{"promptTokens":14895,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_XofIOQgfbnjf1jpWOKiGm6lN|fc_089095e18118ee7d016a59d263d7d8819098cf18cd651f58de","toolName":"read","startedAt":"2026-07-17T06:57:39.999Z","args":{"path":"skill://diagnose"},"intent":"Reading diagnosis workflow"},"id":"6926ed33","parentId":"a3acc7ff","timestamp":"2026-07-17T06:57:39.999Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6XBdXxIbQr3bv1AXlXe1UrDz|fc_089095e18118ee7d016a59d263d7ec8190a302c6b29278fcfb","toolName":"grep","startedAt":"2026-07-17T06:57:39.999Z","args":{"path":"."},"intent":"Locating redispatch changes"},"id":"96b5e5cd","parentId":"6926ed33","timestamp":"2026-07-17T06:57:39.999Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_W3Jo7xmZcMSbQlpPweo236pi|fc_089095e18118ee7d016a59d263d7f48190935a83a8f4854fd4","toolName":"grep","startedAt":"2026-07-17T06:57:40.000Z","args":{"path":"src; tests; test"},"intent":"Locating stream retirement"},"id":"de914355","parentId":"96b5e5cd","timestamp":"2026-07-17T06:57:40.000Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ZOYIZTOB3PEBB0KNHd41VDRZ|fc_089095e18118ee7d016a59d263d7fc8190b6fada2b8a6bd7f4","toolName":"glob","startedAt":"2026-07-17T06:57:40.000Z","args":{"path":"**/*{broker,stream,dispatch,worker}*"},"intent":"Mapping broker sources"},"id":"687dc172","parentId":"de914355","timestamp":"2026-07-17T06:57:40.000Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_WXnpzlRo7cVpP9E1Aidf8IgC|fc_089095e18118ee7d016a59d263d8048190984d078de4bdc00f","toolName":"glob","startedAt":"2026-07-17T06:57:40.000Z","args":{"path":".git/{logs,refs}/**/*"},"intent":"Mapping repository history"},"id":"000d9f8f","parentId":"687dc172","timestamp":"2026-07-17T06:57:40.000Z"}
{"type":"message","id":"8d36700b","parentId":"000d9f8f","timestamp":"2026-07-17T06:57:40.004Z","message":{"role":"toolResult","toolCallId":"call_XofIOQgfbnjf1jpWOKiGm6lN|fc_089095e18118ee7d016a59d263d7d8819098cf18cd651f58de","toolName":"read","content":[{"type":"text","text":"---\nname: diagnose\ndescription: 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---\n\n# Diagnose\n\nA discipline for hard bugs. Skip phases only when explicitly justified.\n\nWhen exploring the codebase, use the project's domain glossary to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.\n\n## Phase 1 — Build a feedback loop\n\n**This is the skill.** Everything else is mechanical. If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause — bisection, hypothesis-testing, and instrumentation all just consume that signal. If you don't have one, no amount of staring at code will save you.\n\nSpend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**\n\n### Ways to construct one — try them in roughly this order\n\n1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e.\n2. **Curl / HTTP script** against a running dev server.\n3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.\n4. **Headless browser script** (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.\n5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.\n6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.\n7. **Property / fuzz loop.** If the bug is \"sometimes wrong output\", run 1000 random inputs and look for the failure mode.\n8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate \"boot at state X, check, repeat\" so you can `git bisect run` it.\n9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.\n10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.\n\nBuild the right feedback loop, and the bug is 90% fixed.\n\n### Iterate on the loop itself\n\nTreat the loop as a product. Once you have _a_ loop, ask:\n\n- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)\n- Can I make the signal sharper? (Assert on the specific symptom, not \"didn't crash\".)\n- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)\n\nA 30-second flaky loop is barely better than no loop. A 2-second deterministic loop is a debugging superpower.\n\n### Non-deterministic bugs\n\nThe goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.\n\n### When you genuinely cannot build a loop\n\nStop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.\n\nDo not proceed to Phase 2 until you have a loop you believe in.\n\n## Phase 2 — Reproduce\n\nRun the loop. Watch the bug appear.\n\nConfirm:\n\n- [ ] The loop produces the failure mode the **user** described — not a different failure that happens to be nearby. Wrong bug = wrong fix.\n- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).\n- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.\n\nDo not proceed until you reproduce the bug.\n\n## Phase 3 — Hypothesise\n\nGenerate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.\n\nEach hypothesis must be **falsifiable**: state the prediction it makes.\n\n> Format: \"If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse.\"\n\nIf you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.\n\n**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly (\"we just deployed a change to #3\"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.\n\n## Phase 4 — Instrument\n\nEach probe must map to a specific prediction from Phase 3. **Change one variable at a time.**\n\nTool preference:\n\n1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.\n2. **Targeted logs** at the boundaries that distinguish hypotheses.\n3. Never \"log everything and grep\".\n\n**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.\n\n**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.\n\n## Phase 5 — Fix + regression test\n\nWrite the regression test **before the fix** — but only if there is a **correct seam** for it.\n\nA correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.\n\n**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.\n\nIf a correct seam exists:\n\n1. Turn the minimised repro into a failing test at that seam.\n2. Watch it fail.\n3. Apply the fix.\n4. Watch it pass.\n5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.\n\n## Phase 6 — Cleanup + post-mortem\n\nRequired before declaring done:\n\n- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)\n- [ ] Regression test passes (or absence of seam is documented)\n- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)\n- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)\n- [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns\n\n**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started.\n"}],"details":{"resolvedPath":"C:\\Users\\decid\\.claude\\skills\\diagnose\\SKILL.md","contentType":"text/markdown","displayContent":{"text":"---\r\nname: diagnose\r\ndescription: 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.\r\n---\r\n\r\n# Diagnose\r\n\r\nA discipline for hard bugs. Skip phases only when explicitly justified.\r\n\r\nWhen exploring the codebase, use the project's domain glossary to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.\r\n\r\n## Phase 1 — Build a feedback loop\r\n\r\n**This is the skill.** Everything else is mechanical. If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause — bisection, hypothesis-testing, and instrumentation all just consume that signal. If you don't have one, no amount of staring at code will save you.\r\n\r\nSpend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**\r\n\r\n### Ways to construct one — try them in roughly this order\r\n\r\n1. **Failing test** at whatever seam reaches the bug — unit, integration, e2e.\r\n2. **Curl / HTTP script** against a running dev server.\r\n3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.\r\n4. **Headless browser script** (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.\r\n5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.\r\n6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.\r\n7. **Property / fuzz loop.** If the bug is \"sometimes wrong output\", run 1000 random inputs and look for the failure mode.\r\n8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate \"boot at state X, check, repeat\" so you can `git bisect run` it.\r\n9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.\r\n10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.\r\n\r\nBuild the right feedback loop, and the bug is 90% fixed.\r\n\r\n### Iterate on the loop itself\r\n\r\nTreat the loop as a product. Once you have _a_ loop, ask:\r\n\r\n- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)\r\n- Can I make the signal sharper? (Assert on the specific symptom, not \"didn't crash\".)\r\n- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)\r\n\r\nA 30-second flaky loop is barely better than no loop. A 2-second deterministic loop is a debugging superpower.\r\n\r\n### Non-deterministic bugs\r\n\r\nThe goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.\r\n\r\n### When you genuinely cannot build a loop\r\n\r\nStop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.\r\n\r\nDo not proceed to Phase 2 until you have a loop you believe in.\r\n\r\n## Phase 2 — Reproduce\r\n\r\nRun the loop. Watch the bug appear.\r\n\r\nConfirm:\r\n\r\n- [ ] The loop produces the failure mode the **user** described — not a different failure that happens to be nearby. Wrong bug = wrong fix.\r\n- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).\r\n- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.\r\n\r\nDo not proceed until you reproduce the bug.\r\n\r\n## Phase 3 — Hypothesise\r\n\r\nGenerate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.\r\n\r\nEach hypothesis must be **falsifiable**: state the prediction it makes.\r\n\r\n> Format: \"If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse.\"\r\n\r\nIf you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.\r\n\r\n**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly (\"we just deployed a change to #3\"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.\r\n\r\n## Phase 4 — Instrument\r\n\r\nEach probe must map to a specific prediction from Phase 3. **Change one variable at a time.**\r\n\r\nTool preference:\r\n\r\n1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.\r\n2. **Targeted logs** at the boundaries that distinguish hypotheses.\r\n3. Never \"log everything and grep\".\r\n\r\n**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.\r\n\r\n**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.\r\n\r\n## Phase 5 — Fix + regression test\r\n\r\nWrite the regression test **before the fix** — but only if there is a **correct seam** for it.\r\n\r\nA correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.\r\n\r\n**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.\r\n\r\nIf a correct seam exists:\r\n\r\n1. Turn the minimised repro into a failing test at that seam.\r\n2. Watch it fail.\r\n3. Apply the fix.\r\n4. Watch it pass.\r\n5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.\r\n\r\n## Phase 6 — Cleanup + post-mortem\r\n\r\nRequired before declaring done:\r\n\r\n- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)\r\n- [ ] Regression test passes (or absence of seam is documented)\r\n- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)\r\n- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)\r\n- [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns\r\n\r\n**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started.\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118]},"meta":{"source":{"type":"internal","value":"skill://diagnose"}}},"isError":false,"timestamp":1784271460004}}
{"type":"message","id":"f6822e22","parentId":"8d36700b","timestamp":"2026-07-17T06:57:40.014Z","message":{"role":"toolResult","toolCallId":"call_ZOYIZTOB3PEBB0KNHd41VDRZ|fc_089095e18118ee7d016a59d263d7fc8190b6fada2b8a6bd7f4","toolName":"glob","content":[{"type":"text","text":"# crates/\n## spt-daemon/\n### src/\ndispatch.rs\nbroker.rs\n### tests/\nredispatch_stall.rs\nbroker.rs\nredispatch.rs\nnet_worker_starve.rs\nnetstream.rs\nnetbroker.rs\ndispatch.rs\n#### fixtures/\ndispatch_fixture.rs\n## spt/\n### src/api/\nworker.rs\n### tests/\nworker_visibility_e2e.rs\nworker_lifecycle_e2e.rs\n## spt-store/src/\nworker_reap.rs\nworker_seq.rs\n## spt-term/\n### tests/\nstream.rs\n### src/\nstream.rs\n# docs/\n## adr/\n0038-broker-stream-lifecycle-truth.md\n0018-broker-brain-process-isolation-restoration.md\n0004-single-daemon-broker-brain-split-and-self-update.md\n## spikes/\nSPIKE-01-broker-handoff.md"}],"details":{"scopePath":".","fileCount":21,"files":["crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/tests/redispatch_stall.rs","crates/spt-daemon/tests/broker.rs","docs/adr/0038-broker-stream-lifecycle-truth.md","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","crates/spt-daemon/tests/net_worker_starve.rs","crates/spt/src/api/worker.rs","crates/spt/tests/worker_visibility_e2e.rs","crates/spt/tests/worker_lifecycle_e2e.rs","crates/spt-store/src/worker_reap.rs","crates/spt-store/src/worker_seq.rs","crates/spt-daemon/tests/netstream.rs","crates/spt-daemon/tests/netbroker.rs","crates/spt-daemon/tests/dispatch.rs","crates/spt-term/tests/stream.rs","crates/spt-term/src/stream.rs","docs/adr/0018-broker-brain-process-isolation-restoration.md","docs/adr/0004-single-daemon-broker-brain-split-and-self-update.md","docs/spikes/SPIKE-01-broker-handoff.md"],"truncated":false,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"},"isError":false,"timestamp":1784271460014}}
{"type":"message","id":"65f7f34f","parentId":"f6822e22","timestamp":"2026-07-17T06:57:40.023Z","message":{"role":"toolResult","toolCallId":"call_W3Jo7xmZcMSbQlpPweo236pi|fc_089095e18118ee7d016a59d263d7f48190935a83a8f4854fd4","toolName":"grep","content":[{"type":"text","text":"No matches found\nSkipped missing paths: test"}],"details":{"scopePath":"src, tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":0,"fileCount":0,"files":[],"truncated":false,"missingPaths":["test"]},"isError":false,"useless":true,"timestamp":1784271460023}}
{"type":"message","id":"0f86f86b","parentId":"65f7f34f","timestamp":"2026-07-17T06:57:40.059Z","message":{"role":"toolResult","toolCallId":"call_WXnpzlRo7cVpP9E1Aidf8IgC|fc_089095e18118ee7d016a59d263d8048190984d078de4bdc00f","toolName":"glob","content":[{"type":"text","text":"# .git/\n## logs/\nHEAD\n### refs/\n#### remotes/\n##### origin/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\ngateway-liveness\npump-w2-design\nlifecycle-truth\n###### docs/\nv0360-notes-plain-language\nreadme-install-channel\nreorg-plans-process\n###### fix/\nredispatch-stall-w1-replay-order\nchangelog-0330-heading\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n###### release/\nv0.36.0\nv0.35.0\nv0.34.0\nv0.33.0\nv0.32.0\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.2\nv0.30.1\n0.29.1\n###### build/\nredispatch-stall-w1\nmesh-recovery-w1\nredispatch-truth-w1\nproject-index-w3-reader-cutover\nproject-index-w2-daemon-writer\nproject-index-w1-identity-store\n###### triage/\nupdate-wedge-rca-trace\nmusl-tier\n###### test/\nuwedge-r4-stale-guard-swap\n###### release-merge/\nuwedge-r4\n###### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n###### spec/\nbrain-handoff-pilot\n###### rca/\npump-w1-fastfail\n##### mirror/\nmain\n#### heads/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\npump-w2-design\npump-w1-fix\nmusl-w2\nmusl-w1\ngate-inject-rearm\n##### docs/\nv0360-notes-plain-language\nreorg-plans-process\n##### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n##### release/\nv0.36.0\nv0.35.0\nv0.34.0\nv0.33.0\nv0.32.0\nv0.30.4\n0.29.1\n##### triage/\nupdate-wedge-rca-trace\nupdate-wedge-rca-capture\nmusl-tier\n##### test/\nuwedge-r4-stale-guard-swap\n##### release-merge/\nuwedge-r4\n##### spec/\nbrain-handoff-pilot\n##### rca/\npump-w1-fastfail\n##### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n## refs/\n### remotes/\n#### origin/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\ngateway-liveness\npump-w2-design\n##### docs/\nv0360-notes-plain-language\nreadme-install-channel\nreorg-plans-process\n##### fix/\nredispatch-stall-w1-replay-order\nchangelog-0330-heading\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n##### release/\nv0.36.0\nv0.35.0\nv0.34.0\nv0.33.0\nv0.32.0\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.2\nv0.30.1\n0.29.1\n##### build/\nredispatch-stall-w1\nmesh-recovery-w1\nredispatch-truth-w1\nproject-index-w3-reader-cutover\nproject-index-w2-daemon-writer\nproject-index-w1-identity-store\n##### triage/\nupdate-wedge-rca-trace\nmusl-tier\n##### test/\nuwedge-r4-stale-guard-swap\n##### release-merge/\nuwedge-r4\n##### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n##### spec/\nbrain-handoff-pilot\n##### rca/\npump-w1-fastfail\n#### mirror/\nmain\n### heads/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\npump-w2-design\npump-w1-fix\nmusl-w2\nmusl-w1\ngate-inject-rearm\n#### docs/\nv0360-notes-plain-language\nreorg-plans-process\n#### fix/\nupdate-wedge-bounded-conn-write\ninject-settle-rearm\n#### release/\nv0.36.0\nv0.35.0\nv0.34.0\nv0.33.0\nv0.32.0\nv0.30.4\n0.29.1\n#### triage/\nupdate-wedge-rca-trace\nupdate-wedge-rca-capture\nmusl-tier\n#### test/\nuwedge-r4-stale-guard-swap\n#### release-merge/\nuwedge-r4\n#### spec/\nbrain-handoff-pilot\n#### rca/\npump-w1-fastfail\n#### feat/\nmusl-w3-release-artifact\nmusl-w1-platform-registry\n### tags/\nv0.36.0\nv0.35.0\nv0.34.0\nv0.33.0\nv0.32.0\nv0.31.0\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.3\nv0.30.2\nv0.30.1\nv0.30.0\nv0.29.1\n\n[200 results limit reached. Use limit=400 for more]"}],"details":{"scopePath":".git","fileCount":200,"files":[".git/logs/refs/remotes/origin/main",".git/refs/remotes/origin/",".git/refs/remotes/origin/main",".git/logs/refs/remotes/origin/docs/",".git/logs/refs/remotes/origin/docs/v0360-notes-plain-language",".git/refs/remotes/origin/docs/",".git/refs/remotes/origin/docs/v0360-notes-plain-language",".git/logs/HEAD",".git/logs/refs/heads/docs/v0360-notes-plain-language",".git/refs/heads/docs/",".git/refs/heads/docs/v0360-notes-plain-language",".git/logs/refs/heads/docs/",".git/refs/tags/",".git/refs/tags/v0.36.0",".git/logs/refs/heads/fix/",".git/refs/heads/fix/",".git/logs/refs/heads/main",".git/refs/heads/",".git/refs/heads/main",".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order",".git/refs/remotes/origin/fix/",".git/refs/remotes/origin/fix/redispatch-stall-w1-replay-order",".git/logs/refs/remotes/origin/fix/",".git/logs/refs/remotes/origin/release/v0.36.0",".git/refs/remotes/origin/release/",".git/logs/refs/remotes/origin/release/",".git/refs/remotes/origin/release/v0.36.0",".git/refs/heads/release/",".git/logs/refs/heads/release/v0.36.0",".git/refs/heads/release/v0.36.0",".git/logs/refs/heads/release/",".git/logs/refs/heads/",".git/logs/refs/remotes/origin/build/redispatch-stall-w1",".git/refs/remotes/origin/build/",".git/refs/remotes/origin/build/redispatch-stall-w1",".git/logs/refs/remotes/origin/build/",".git/refs/tags/v0.35.0",".git/logs/refs/remotes/origin/release/v0.35.0",".git/refs/remotes/origin/release/v0.35.0",".git/logs/refs/heads/release/v0.35.0",".git/refs/heads/release/v0.35.0",".git/refs/remotes/mirror/",".git/logs/refs/remotes/mirror/main",".git/refs/remotes/mirror/main",".git/logs/refs/remotes/origin/build/mesh-recovery-w1",".git/refs/remotes/origin/build/mesh-recovery-w1",".git/logs/refs/remotes/origin/fix/changelog-0330-heading",".git/refs/remotes/origin/fix/changelog-0330-heading",".git/refs/tags/v0.34.0",".git/logs/refs/remotes/origin/release/v0.34.0",".git/refs/remotes/origin/release/v0.34.0",".git/logs/refs/heads/release/v0.34.0",".git/refs/heads/release/v0.34.0",".git/logs/refs/remotes/origin/build/redispatch-truth-w1",".git/refs/remotes/origin/build/redispatch-truth-w1",".git/logs/refs/",".git/refs/tags/v0.33.0",".git/logs/refs/remotes/origin/release/v0.33.0",".git/refs/remotes/origin/release/v0.33.0",".git/logs/refs/heads/release/v0.33.0",".git/refs/heads/release/v0.33.0",".git/logs/refs/remotes/origin/build/project-index-w3-reader-cutover",".git/refs/remotes/origin/build/project-index-w3-reader-cutover",".git/logs/refs/remotes/origin/",".git/logs/refs/remotes/origin/docs/readme-install-channel",".git/refs/remotes/origin/docs/readme-install-channel",".git/logs/refs/remotes/origin/build/project-index-w2-daemon-writer",".git/refs/remotes/origin/build/project-index-w2-daemon-writer",".git/logs/refs/remotes/origin/build/project-index-w1-identity-store",".git/refs/remotes/origin/build/project-index-w1-identity-store",".git/refs/tags/v0.32.0",".git/logs/refs/remotes/origin/release/v0.32.0",".git/refs/remotes/origin/release/v0.32.0",".git/logs/refs/heads/release/v0.32.0",".git/refs/heads/release/v0.32.0",".git/logs/refs/remotes/",".git/logs/refs/remotes/mirror/",".git/refs/remotes/",".git/refs/tags/v0.31.0",".git/logs/refs/remotes/origin/triage/",".git/refs/remotes/origin/triage/",".git/refs/heads/triage/",".git/logs/refs/heads/triage/",".git/logs/refs/remotes/origin/triage/update-wedge-rca-trace",".git/refs/remotes/origin/triage/update-wedge-rca-trace",".git/logs/refs/heads/triage/update-wedge-rca-trace",".git/refs/heads/triage/update-wedge-rca-trace",".git/refs/tags/v0.30.6",".git/logs/refs/remotes/origin/release/v0.30.6",".git/refs/remotes/origin/release/v0.30.6",".git/logs/refs/remotes/origin/test/uwedge-r4-stale-guard-swap",".git/refs/remotes/origin/test/",".git/refs/remotes/origin/test/uwedge-r4-stale-guard-swap",".git/refs/heads/test/",".git/logs/refs/heads/test/uwedge-r4-stale-guard-swap",".git/refs/heads/test/uwedge-r4-stale-guard-swap",".git/logs/refs/remotes/origin/test/",".git/logs/refs/heads/test/",".git/logs/refs/remotes/origin/release-merge/uwedge-r4",".git/refs/remotes/origin/release-merge/",".git/logs/refs/remotes/origin/release-merge/",".git/refs/remotes/origin/release-merge/uwedge-r4",".git/logs/refs/heads/release-merge/uwedge-r4",".git/refs/heads/release-merge/",".git/refs/heads/release-merge/uwedge-r4",".git/logs/refs/heads/release-merge/",".git/logs/refs/remotes/origin/fix/update-wedge-bounded-conn-write",".git/refs/remotes/origin/fix/update-wedge-bounded-conn-write",".git/logs/refs/heads/fix/update-wedge-bounded-conn-write",".git/refs/heads/fix/update-wedge-bounded-conn-write",".git/logs/refs/heads/triage/update-wedge-rca-capture",".git/refs/heads/triage/update-wedge-rca-capture",".git/refs/tags/v0.30.5",".git/logs/refs/remotes/origin/release/v0.30.5",".git/refs/remotes/origin/release/v0.30.5",".git/logs/refs/heads/uwedge2-resume-viewer",".git/refs/heads/uwedge2-resume-viewer",".git/refs/tags/v0.30.4",".git/logs/refs/remotes/origin/release/v0.30.4",".git/refs/remotes/origin/release/v0.30.4",".git/logs/refs/heads/release/v0.30.4",".git/refs/heads/release/v0.30.4",".git/logs/refs/remotes/origin/uwedge2-resume-viewer",".git/refs/remotes/origin/uwedge2-resume-viewer",".git/refs/tags/v0.30.3",".git/logs/refs/remotes/origin/update-wedge-drain-drive",".git/refs/remotes/origin/update-wedge-drain-drive",".git/logs/refs/heads/update-wedge-drain-drive",".git/refs/heads/update-wedge-drain-drive",".git/logs/refs/remotes/origin/f-035-window-drain-honor",".git/refs/remotes/origin/f-035-window-drain-honor",".git/logs/refs/heads/f-035-window-drain-honor",".git/refs/heads/f-035-window-drain-honor",".git/logs/refs/remotes/origin/gateway-liveness",".git/refs/remotes/origin/gateway-liveness",".git/refs/tags/v0.30.2",".git/logs/refs/remotes/origin/feat/",".git/refs/remotes/origin/feat/",".git/logs/refs/remotes/origin/release/v0.30.2",".git/refs/remotes/origin/release/v0.30.2",".git/logs/refs/remotes/origin/pump-w2-design",".git/refs/remotes/origin/pump-w2-design",".git/logs/refs/heads/pump-w2-design",".git/refs/heads/pump-w2-design",".git/logs/refs/remotes/origin/spec/brain-handoff-pilot",".git/refs/remotes/origin/spec/",".git/refs/remotes/origin/spec/brain-handoff-pilot",".git/refs/heads/spec/",".git/logs/refs/heads/spec/brain-handoff-pilot",".git/refs/heads/spec/brain-handoff-pilot",".git/refs/tags/v0.30.1",".git/logs/refs/remotes/origin/release/v0.30.1",".git/refs/remotes/origin/release/v0.30.1",".git/logs/refs/heads/pump-w1-fix",".git/refs/heads/pump-w1-fix",".git/logs/refs/remotes/origin/spec/",".git/logs/refs/heads/spec/",".git/logs/refs/remotes/origin/docs/reorg-plans-process",".git/refs/remotes/origin/docs/reorg-plans-process",".git/logs/refs/heads/docs/reorg-plans-process",".git/refs/heads/docs/reorg-plans-process",".git/logs/refs/heads/rca/pump-w1-fastfail",".git/refs/heads/rca/",".git/refs/heads/rca/pump-w1-fastfail",".git/refs/remotes/origin/rca/",".git/logs/refs/remotes/origin/rca/pump-w1-fastfail",".git/refs/remotes/origin/rca/pump-w1-fastfail",".git/refs/tags/v0.30.0",".git/logs/refs/remotes/origin/feat/musl-w3-release-artifact",".git/refs/remotes/origin/feat/musl-w3-release-artifact",".git/logs/refs/heads/feat/musl-w3-release-artifact",".git/refs/heads/feat/",".git/refs/heads/feat/musl-w3-release-artifact",".git/logs/refs/remotes/origin/rca/",".git/logs/refs/heads/rca/",".git/logs/refs/heads/feat/",".git/logs/refs/heads/musl-w2",".git/refs/heads/musl-w2",".git/logs/refs/heads/musl-w1",".git/refs/heads/musl-w1",".git/logs/refs/remotes/origin/feat/musl-w1-platform-registry",".git/refs/remotes/origin/feat/musl-w1-platform-registry",".git/logs/refs/heads/feat/musl-w1-platform-registry",".git/refs/heads/feat/musl-w1-platform-registry",".git/logs/refs/remotes/origin/triage/musl-tier",".git/refs/remotes/origin/triage/musl-tier",".git/logs/refs/heads/triage/musl-tier",".git/refs/heads/triage/musl-tier",".git/refs/tags/v0.29.1",".git/logs/refs/remotes/origin/release/0.29.1",".git/refs/remotes/origin/release/0.29.1",".git/logs/refs/heads/release/0.29.1",".git/refs/heads/release/0.29.1",".git/logs/refs/remotes/origin/fix/inject-settle-rearm",".git/refs/remotes/origin/fix/inject-settle-rearm",".git/logs/refs/heads/gate-inject-rearm",".git/refs/heads/gate-inject-rearm",".git/logs/refs/heads/fix/inject-settle-rearm",".git/refs/heads/fix/inject-settle-rearm",".git/logs/refs/remotes/origin/lifecycle-truth"],"truncated":true,"resultLimitReached":200,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","meta":{"limits":{"resultLimit":{"reached":200,"suggestion":400}}}},"isError":false,"timestamp":1784271460059}}
{"type":"message","id":"7cb61481","parentId":"0f86f86b","timestamp":"2026-07-17T06:58:10.015Z","message":{"role":"toolResult","toolCallId":"call_6XBdXxIbQr3bv1AXlXe1UrDz|fc_089095e18118ee7d016a59d263d7ec8190a302c6b29278fcfb","toolName":"grep","content":[{"type":"text","text":"Grep timed out after 30s; narrow paths or pattern, or scope with `glob` first"}],"details":{},"isError":true,"timestamp":1784271490015}}
{"type":"message","id":"c3814920","parentId":"7cb61481","timestamp":"2026-07-17T06:58:20.956Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning detailed daemon inspection**"},{"type":"toolCall","id":"call_LZZswLUgVgx24T9HewOMfz1W|fc_089095e18118ee7d016a59d28cd5308190a0cab7b7bfc36f22","name":"grep","arguments":{"i":"Tracing retirement semantics","pattern":"retir|reconstruct|replay|redispatch|spawn|thread|worker|connection","path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_BSFyupW3BpVVHtUTsUdkSdEV|fc_089095e18118ee7d016a59d28cd53c81909f9a1dee3901dd5c","name":"read","arguments":{"i":"Reading stream lifecycle ADR","path":"docs/adr/0038-broker-stream-lifecycle-truth.md","selector":null}},{"type":"toolCall","id":"call_v5mBTEaZZ0WxMcveQ3n7spsm|fc_089095e18118ee7d016a59d28cd54881908a58861f1864cc54","name":"read","arguments":{"i":"Mapping broker declarations","path":"crates/spt-daemon/src/broker.rs","selector":null}},{"type":"toolCall","id":"call_ZfpfhyXwKv7WO3hIzmBs7gDn|fc_089095e18118ee7d016a59d28cd56481908d2da9f5bce1160c","name":"read","arguments":{"i":"Mapping dispatch declarations","path":"crates/spt-daemon/src/dispatch.rs","selector":null}},{"type":"toolCall","id":"call_fAJLKBPskQLT3UZd2fkIAxUd|fc_089095e18118ee7d016a59d28cd56881909311324cad0323e9","name":"read","arguments":{"i":"Mapping redispatch tests","path":"crates/spt-daemon/tests/redispatch.rs","selector":null}},{"type":"toolCall","id":"call_UkQg8h5L66zfR05cLeoYmQpB|fc_089095e18118ee7d016a59d28cd5708190971e8a12982cd790","name":"read","arguments":{"i":"Mapping stall regression tests","path":"crates/spt-daemon/tests/redispatch_stall.rs","selector":null}},{"type":"toolCall","id":"call_PQKYS8rh8BMdFAz5lygGEtHL|fc_089095e18118ee7d016a59d28cd5748190b59c5bb5daab7dcd","name":"read","arguments":{"i":"Reading redispatch branch history","path":".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order","selector":"raw"}},{"type":"toolCall","id":"call_6ExS8eHG46gFRvsQilUlVqWe|fc_089095e18118ee7d016a59d28cd578819084e64f9f42c54c38","name":"read","arguments":{"i":"Reading redispatch build history","path":".git/logs/refs/remotes/origin/build/redispatch-truth-w1","selector":"raw"}},{"type":"toolCall","id":"call_6wDFAWEmhvDVM9meNNNn67BF|fc_089095e18118ee7d016a59d28cd57c819091007edc88cb2a24","name":"read","arguments":{"i":"Reading v036 release history","path":".git/logs/refs/remotes/origin/release/v0.36.0","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4528,"output":471,"cacheRead":13824,"cacheWrite":0,"totalTokens":18823,"reasoningTokens":46,"cost":{"input":0.02264,"output":0.01413,"cacheRead":0.006912,"cacheWrite":0,"total":0.043682}},"stopReason":"toolUse","timestamp":1784271490038,"responseId":"resp_089095e18118ee7d016a59d282465881908a6a0f63ff4e6d12","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d28459948190b4a53f301a2ff849","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdKMECLoiDr_HXXV7P8ltPVWyVBgO-yvvtTEaaRYF1dbifc8BZYU_24k-3ga3NC_DaYgfLKh9ubu8qDHIGyATs-j8f23Qc1CibQkH33K36oP3CtvIiL3wEU6BinPFKRNbEt9Dj-RJra-zZyEVYlIsM2x1nKgj8KiuoeHrGniNsZ0k83Lp6ANdD-yAsHmUXppUgfoc4setU4znZKD-pg1eNiykd1rTbYKwUYGCjFeFHmaxH7cf7oL6oZup60gWyASUZgw3iBptQINHcWrcFf-oqAbHEhNu6cvD7518JJEqX8m8v13QVmOT3mRDnxe7NWnltNCnOEp9gju22iYKtja1NrdrqJQCjfI7zVoof_r_GwaKL8S_8FZ_ivA8-3d_5OpHV1YnfHb4wSGYQhxijmTVMs0QgQDa8U57Dp_bc_btf2G33IGUNIkfmu0tocFwiTkU6OKVBAnTvdE2IoPfV0HujUJLV7BG8hsM7KpZhfsYZNs3aqpno5eZyhQk5XJmCIfwXoEN_-iz68vww_GYiPydNU2Y1jDQTxpszPXnmCpduTEb5aCC9OMso7svP5aA5DdaN4Ey5cHcWhImVB8aw8hVotJFktEhq9kU7OIhapLFn2HnKfZmFvDIvdWRNhVNDIvILy1_5-NB58I5tRfMK0AHM53pdrfTy-4YBXsRMNLut_K-P4lO06TH2opcoQYvUsR4D-J8c7SKe04hs7yOWLulz0uEEEJi2tWymKw7Du-Pcj145MaDJXibYx2rJT-EP_nZHOIab3P21OUpQSv3kmlODNF4u_uznouy-2yvf4DegFJb_COvvtr27_5kIAS7V_wXrNvKI_5pGChckT96W00SqivOlUgqcVV29h551DC81SFFcJv9qO1QVQYaqEqTnWgvyiO2FaxiAD2zAuKFR7hNFn6FGyVwuwZD2AtGXYSdbdWm_mS-GQPFlo2aWprp_kSF_E3eB0mUB-ocU1zgXMQ34rxHYRqrpnPyzzsZH5vTwhzSzCic952u9AyIHaBi5x6HyK4cCZfAUhWr5bIdrHhzeUaxxQFgAafX7v7lokdm1XMRajMasWY1kQQ6PV7dLmzEHS3SCuThYr3LS6Xstg2C7A5qko9S32xA6Bp_pjTzILA348FfiMKPZJ7kiMKMGd0LWTn_85p-RuuSg-qGh8L1Cyk7hJ-Bihc941jUhDsO_0OGizelLcgowtbys4K7zaHgPYL3c9X1ii-3HDLURp0uPLP3g==","summary":[{"type":"summary_text","text":"**Planning detailed daemon inspection**"}]},{"id":"fc_089095e18118ee7d016a59d28cd5308190a0cab7b7bfc36f22","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing retirement semantics\",\"pattern\":\"retir|reconstruct|replay|redispatch|spawn|thread|worker|connection\",\"path\":\"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_LZZswLUgVgx24T9HewOMfz1W","name":"grep"},{"id":"fc_089095e18118ee7d016a59d28cd53c81909f9a1dee3901dd5c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream lifecycle ADR\",\"path\":\"docs/adr/0038-broker-stream-lifecycle-truth.md\",\"selector\":null}","call_id":"call_BSFyupW3BpVVHtUTsUdkSdEV","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd54881908a58861f1864cc54","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping broker declarations\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":null}","call_id":"call_v5mBTEaZZ0WxMcveQ3n7spsm","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd56481908d2da9f5bce1160c","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping dispatch declarations\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":null}","call_id":"call_ZfpfhyXwKv7WO3hIzmBs7gDn","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd56881909311324cad0323e9","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping redispatch tests\",\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"selector\":null}","call_id":"call_fAJLKBPskQLT3UZd2fkIAxUd","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd5708190971e8a12982cd790","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping stall regression tests\",\"path\":\"crates/spt-daemon/tests/redispatch_stall.rs\",\"selector\":null}","call_id":"call_UkQg8h5L66zfR05cLeoYmQpB","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd5748190b59c5bb5daab7dcd","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch branch history\",\"path\":\".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order\",\"selector\":\"raw\"}","call_id":"call_PQKYS8rh8BMdFAz5lygGEtHL","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd578819084e64f9f42c54c38","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch build history\",\"path\":\".git/logs/refs/remotes/origin/build/redispatch-truth-w1\",\"selector\":\"raw\"}","call_id":"call_6ExS8eHG46gFRvsQilUlVqWe","name":"read"},{"id":"fc_089095e18118ee7d016a59d28cd57c819091007edc88cb2a24","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading v036 release history\",\"path\":\".git/logs/refs/remotes/origin/release/v0.36.0\",\"selector\":\"raw\"}","call_id":"call_6wDFAWEmhvDVM9meNNNn67BF","name":"read"}]},"duration":10899.548999994993,"ttft":2321.9900999963284,"contextSnapshot":{"promptTokens":18352,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LZZswLUgVgx24T9HewOMfz1W|fc_089095e18118ee7d016a59d28cd5308190a0cab7b7bfc36f22","toolName":"grep","startedAt":"2026-07-17T06:58:20.957Z","args":{"path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs"},"intent":"Tracing retirement semantics"},"id":"eb577a38","parentId":"c3814920","timestamp":"2026-07-17T06:58:20.957Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BSFyupW3BpVVHtUTsUdkSdEV|fc_089095e18118ee7d016a59d28cd53c81909f9a1dee3901dd5c","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md"},"intent":"Reading stream lifecycle ADR"},"id":"004066ed","parentId":"eb577a38","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_v5mBTEaZZ0WxMcveQ3n7spsm|fc_089095e18118ee7d016a59d28cd54881908a58861f1864cc54","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Mapping broker declarations"},"id":"9d3e7527","parentId":"004066ed","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ZfpfhyXwKv7WO3hIzmBs7gDn|fc_089095e18118ee7d016a59d28cd56481908d2da9f5bce1160c","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Mapping dispatch declarations"},"id":"80844375","parentId":"9d3e7527","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fAJLKBPskQLT3UZd2fkIAxUd|fc_089095e18118ee7d016a59d28cd56881909311324cad0323e9","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs"},"intent":"Mapping redispatch tests"},"id":"ab605c05","parentId":"80844375","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UkQg8h5L66zfR05cLeoYmQpB|fc_089095e18118ee7d016a59d28cd5708190971e8a12982cd790","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":"crates/spt-daemon/tests/redispatch_stall.rs"},"intent":"Mapping stall regression tests"},"id":"13a2be55","parentId":"ab605c05","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_PQKYS8rh8BMdFAz5lygGEtHL|fc_089095e18118ee7d016a59d28cd5748190b59c5bb5daab7dcd","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order"},"intent":"Reading redispatch branch history"},"id":"65c175e5","parentId":"13a2be55","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6ExS8eHG46gFRvsQilUlVqWe|fc_089095e18118ee7d016a59d28cd578819084e64f9f42c54c38","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":".git/logs/refs/remotes/origin/build/redispatch-truth-w1"},"intent":"Reading redispatch build history"},"id":"167898fb","parentId":"65c175e5","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6wDFAWEmhvDVM9meNNNn67BF|fc_089095e18118ee7d016a59d28cd57c819091007edc88cb2a24","toolName":"read","startedAt":"2026-07-17T06:58:20.958Z","args":{"path":".git/logs/refs/remotes/origin/release/v0.36.0"},"intent":"Reading v036 release history"},"id":"d21fd3f7","parentId":"167898fb","timestamp":"2026-07-17T06:58:20.958Z"}
{"type":"message","id":"bfc4b00f","parentId":"d21fd3f7","timestamp":"2026-07-17T06:58:20.961Z","message":{"role":"toolResult","toolCallId":"call_BSFyupW3BpVVHtUTsUdkSdEV|fc_089095e18118ee7d016a59d28cd53c81909f9a1dee3901dd5c","toolName":"read","content":[{"type":"text","text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\n\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL — see Amendment below)**\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n\n## Context\n\nThe broker's `NetShared.streams` table is append-only in practice: rows are\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\nno removal or retirement path exists. Stream *classification* is derived at\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\nbounded (4096 transport chunks) and evicts the protocol opener under\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\nperformed before worker spawn, never cleared or retried.\n\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\nbroker-held peer stream, including finished historical attach streams. A\nreplayed historical Attach silently steals the live controller\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\nclassifies Unknown/Failed and is permanently abandoned (claim burned\npre-spawn).\n\n## Decision\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\n   is retired from redispatch eligibility (removed from the enumeration the\n   dispatcher claims from, or excluded by lifecycle state). Only the\n   post-EOF state genuinely needed by other readers (presence, log reads)\n   survives. A fresh dispatcher never re-serves a terminal Attach.\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\n   eligibility-visibility only, never transport teardown. Removing the row\n   races the worker's fire-and-forget reply send and resets the\n   never-finished send half (requester sees torn-EOF instead of its reply);\n   physical teardown belongs to the connection-close sweep alone.*\n2. **Classification identity is restart-durable and ring-independent.**\n   Each broker-held inbound stream pins an immutable bounded opener/\n   classification fact (the complete first NDJSON record or its derived\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\n   close. Recovery classifies from that metadata, never from ring seq 0.\n3. **Claims are retryable, outcomes are classified.** The dispatcher\n   distinguishes active/retryable vs served vs finished. A transient\n   worker-setup failure releases/requeues the claim; terminal outcomes do\n   not hot-loop.\n4. **Live reconstruction semantics are unchanged.** The legitimate\n   same-operator successor re-take after a brain restart (controller\n   identity keyed on `by`) still silently re-takes; the discriminator for\n   replay suppression is stream lifecycle, never origin identity.\n\n## Consequences\n\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\n  historical streams can neither take nor clear a live controller, and\n  active streams survive opener eviction — verified by production-path\n  regressions (the prior e2e manually re-served a stream and bypassed\n  rediscovery entirely).\n- The stream table gains a retirement duty (bounded growth instead of\n  forever-discoverable rows) and a small per-stream metadata cost (one\n  pinned opener record per stream, ring untouched).\n- Rejected alternatives: clearing the whole stream table on brain restart\n  (destroys live streams' reconstruction facts along with the stale ones);\n  enlarging the data ring to preserve openers (unbounded memory for a\n  correctness fact); filtering replay by origin identity (breaks the\n  legitimate successor re-take, the exact silent-steal seam); a\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\n  rows eligible forever).\n\n## Amendment — REDISPATCH-STALL (2026-07-16)\n<!-- [doc->REQ-DISPATCH-FALLBACK-CIRCUIT] -->\n<!-- [doc->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] -->\n<!-- [doc->REQ-SERVE-OWNERSHIP-GENERATION] -->\n<!-- [doc->REQ-DISPATCH-HYGIENE-TELEMETRY] -->\n\n- **Status:** accepted (hertz v0.34 field-stall RCA + doyle seam-verify, same\n  day, both hours apart); RCA summary\n  `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; hazard\n  KNOWN-HAZARDS 7.43; build shape `docs/REDISPATCH-STALL-TRIAGE.md`.\n\nThe W1 machinery this ADR shipped is field-correct on its own terms (D1/D1b\nhold: finished rows never serve) but two of its legs COMPOSE into a stall\nengine on live v0.34: Decision 3's retryable claims re-drive Decision 2's N-1\nring-peek fallback, whose guard arm is a broad `Err(_)`\n(`dispatch.rs:414` — intended old-broker-only, catches transport\ntimeout/EOF/poison too), each pass installing a throwaway peek subscriber\nwhose `StreamLog::attach` replays the whole retained ring UNDER the per-stream\nmutex with discarded write errors — one wedged subscriber conn = serial 15s\nbounded-write poison windows (33 observed, all 15,000–15,154 ms) composing\ninto 17–62s PTY/RC stalls.\n\nAmended decisions:\n\n1. **(amends Decision 2)** The ring-peek fallback fires ONLY on the explicit\n   unsupported-verb/old-broker answer. Every other opener-query error\n   (transport timeout, EOF, poison) is `Failed` → the bounded claim requeue —\n   never a second replay subscriber.\n2. **(new — subscriber write discipline)** Replay and live fan-out stop at\n   the FIRST failed subscriber write, and the failed subscriber is REMOVED\n   (attach replay, append, finish — all three sites). Subscriber I/O moves\n   OFF the `StreamLog` mutex: a bounded per-subscriber writer queue,\n   enqueue-under-lock / I/O-outside; queue overflow = detach that subscriber\n   + resume-from-cursor (the existing gapless-resume contract), never a\n   producer stall.\n3. **(new — bounded forwarding)** Attach worker completion and forwarding are\n   BOUNDED and cancelable: a poisoned brain subscriber cancels its paired\n   serve/wire worker (no orphan forwarding legs). Coordinate at build with\n   the DAEMON-LIFECYCLE C2 stream-lease work — same neighborhood, build once.\n4. **(amends Decision 3)** Retries are CLASSIFIED: a pre-setup transient may\n   retry; a deadline-poisoned replay is CIRCUIT-BROKEN (global backoff), and\n   a replacement subscriber is never installed until the prior subscriber is\n   fully gone. This is an upgrade of the bounded backoff, NOT a revert to the\n   v0.33 burn-the-claim abandonment (explicitly rejected).\n5. **(new — serve-side validation)** Terminal-exclusion is enforced pre-serve,\n   and attach/detach validate ownership/generation — a stale worker can never\n   detach a replacement controller. This also covers the UNFINISHED-stale-row\n   steal shape (raw-close no-FIN viewports) that Decision 1's finished-row\n   retirement cannot see.\n6. **(new — dispatcher hygiene + telemetry)** The redispatch worker pool is\n   bounded with batched cold enumeration and NO claim/sessions locks held\n   across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12\n   kin); dispatch telemetry is keyed per\n   gen/stream/family/endpoint/attempt/conn with\n   replay/poison/cancel/worker-outcome events and gauges.\n\nRejected here: reverting retryable claims (restores permanent abandonment);\nwidening any write deadline (hides the composition instead of removing it);\ntearing down all subscribers on any poison (punishes healthy streams for a\nwedged conn).\n"}],"details":{"displayContent":{"text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\r\n\r\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL — see Amendment below)**\r\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\r\n\r\n## Context\r\n\r\nThe broker's `NetShared.streams` table is append-only in practice: rows are\r\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\r\nno removal or retirement path exists. Stream *classification* is derived at\r\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\r\nbounded (4096 transport chunks) and evicts the protocol opener under\r\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\r\nperformed before worker spawn, never cleared or retried.\r\n\r\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\r\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\r\nbroker-held peer stream, including finished historical attach streams. A\r\nreplayed historical Attach silently steals the live controller\r\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\r\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\r\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\r\nclassifies Unknown/Failed and is permanently abandoned (claim burned\r\npre-spawn).\r\n\r\n## Decision\r\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\r\n\r\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\r\n\r\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\r\n   is retired from redispatch eligibility (removed from the enumeration the\r\n   dispatcher claims from, or excluded by lifecycle state). Only the\r\n   post-EOF state genuinely needed by other readers (presence, log reads)\r\n   survives. A fresh dispatcher never re-serves a terminal Attach.\r\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\r\n   eligibility-visibility only, never transport teardown. Removing the row\r\n   races the worker's fire-and-forget reply send and resets the\r\n   never-finished send half (requester sees torn-EOF instead of its reply);\r\n   physical teardown belongs to the connection-close sweep alone.*\r\n2. **Classification identity is restart-durable and ring-independent.**\r\n   Each broker-held inbound stream pins an immutable bounded opener/\r\n   classification fact (the complete first NDJSON record or its derived\r\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\r\n   close. Recovery classifies from that metadata, never from ring seq 0.\r\n3. **Claims are retryable, outcomes are classified.** The dispatcher\r\n   distinguishes active/retryable vs served vs finished. A transient\r\n   worker-setup failure releases/requeues the claim; terminal outcomes do\r\n   not hot-loop.\r\n4. **Live reconstruction semantics are unchanged.** The legitimate\r\n   same-operator successor re-take after a brain restart (controller\r\n   identity keyed on `by`) still silently re-takes; the discriminator for\r\n   replay suppression is stream lifecycle, never origin identity.\r\n\r\n## Consequences\r\n\r\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\r\n  historical streams can neither take nor clear a live controller, and\r\n  active streams survive opener eviction — verified by production-path\r\n  regressions (the prior e2e manually re-served a stream and bypassed\r\n  rediscovery entirely).\r\n- The stream table gains a retirement duty (bounded growth instead of\r\n  forever-discoverable rows) and a small per-stream metadata cost (one\r\n  pinned opener record per stream, ring untouched).\r\n- Rejected alternatives: clearing the whole stream table on brain restart\r\n  (destroys live streams' reconstruction facts along with the stale ones);\r\n  enlarging the data ring to preserve openers (unbounded memory for a\r\n  correctness fact); filtering replay by origin identity (breaks the\r\n  legitimate successor re-take, the exact silent-steal seam); a\r\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\r\n  rows eligible forever).\r\n\r\n## Amendment — REDISPATCH-STALL (2026-07-16)\r\n<!-- [doc->REQ-DISPATCH-FALLBACK-CIRCUIT] -->\r\n<!-- [doc->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] -->\r\n<!-- [doc->REQ-SERVE-OWNERSHIP-GENERATION] -->\r\n<!-- [doc->REQ-DISPATCH-HYGIENE-TELEMETRY] -->\r\n\r\n- **Status:** accepted (hertz v0.34 field-stall RCA + doyle seam-verify, same\r\n  day, both hours apart); RCA summary\r\n  `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; hazard\r\n  KNOWN-HAZARDS 7.43; build shape `docs/REDISPATCH-STALL-TRIAGE.md`.\r\n\r\nThe W1 machinery this ADR shipped is field-correct on its own terms (D1/D1b\r\nhold: finished rows never serve) but two of its legs COMPOSE into a stall\r\nengine on live v0.34: Decision 3's retryable claims re-drive Decision 2's N-1\r\nring-peek fallback, whose guard arm is a broad `Err(_)`\r\n(`dispatch.rs:414` — intended old-broker-only, catches transport\r\ntimeout/EOF/poison too), each pass installing a throwaway peek subscriber\r\nwhose `StreamLog::attach` replays the whole retained ring UNDER the per-stream\r\nmutex with discarded write errors — one wedged subscriber conn = serial 15s\r\nbounded-write poison windows (33 observed, all 15,000–15,154 ms) composing\r\ninto 17–62s PTY/RC stalls.\r\n\r\nAmended decisions:\r\n\r\n1. **(amends Decision 2)** The ring-peek fallback fires ONLY on the explicit\r\n   unsupported-verb/old-broker answer. Every other opener-query error\r\n   (transport timeout, EOF, poison) is `Failed` → the bounded claim requeue —\r\n   never a second replay subscriber.\r\n2. **(new — subscriber write discipline)** Replay and live fan-out stop at\r\n   the FIRST failed subscriber write, and the failed subscriber is REMOVED\r\n   (attach replay, append, finish — all three sites). Subscriber I/O moves\r\n   OFF the `StreamLog` mutex: a bounded per-subscriber writer queue,\r\n   enqueue-under-lock / I/O-outside; queue overflow = detach that subscriber\r\n   + resume-from-cursor (the existing gapless-resume contract), never a\r\n   producer stall.\r\n3. **(new — bounded forwarding)** Attach worker completion and forwarding are\r\n   BOUNDED and cancelable: a poisoned brain subscriber cancels its paired\r\n   serve/wire worker (no orphan forwarding legs). Coordinate at build with\r\n   the DAEMON-LIFECYCLE C2 stream-lease work — same neighborhood, build once.\r\n4. **(amends Decision 3)** Retries are CLASSIFIED: a pre-setup transient may\r\n   retry; a deadline-poisoned replay is CIRCUIT-BROKEN (global backoff), and\r\n   a replacement subscriber is never installed until the prior subscriber is\r\n   fully gone. This is an upgrade of the bounded backoff, NOT a revert to the\r\n   v0.33 burn-the-claim abandonment (explicitly rejected).\r\n5. **(new — serve-side validation)** Terminal-exclusion is enforced pre-serve,\r\n   and attach/detach validate ownership/generation — a stale worker can never\r\n   detach a replacement controller. This also covers the UNFINISHED-stale-row\r\n   steal shape (raw-close no-FIN viewports) that Decision 1's finished-row\r\n   retirement cannot see.\r\n6. **(new — dispatcher hygiene + telemetry)** The redispatch worker pool is\r\n   bounded with batched cold enumeration and NO claim/sessions locks held\r\n   across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12\r\n   kin); dispatch telemetry is keyed per\r\n   gen/stream/family/endpoint/attempt/conn with\r\n   replay/poison/cancel/worker-outcome events and gauges.\r\n\r\nRejected here: reverting retryable claims (restores permanent abandonment);\r\nwidening any write deadline (hides the composition instead of removing it);\r\ntearing down all subscribers on any poison (punishes healthy streams for a\r\nwedged conn).\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\adr\\0038-broker-stream-lifecycle-truth.md"}}},"isError":false,"timestamp":1784271500961}}
{"type":"message","id":"c4eb3541","parentId":"bfc4b00f","timestamp":"2026-07-17T06:58:20.966Z","message":{"role":"toolResult","toolCallId":"call_fAJLKBPskQLT3UZd2fkIAxUd|fc_089095e18118ee7d016a59d28cd56881909311324cad0323e9","toolName":"read","content":[{"type":"text","text":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n//! a dispatcher GENERATION whose death drops its workers' broker conns\n//! exactly like a production brain death). NO manual re-serve anywhere: the\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\n//! classification seam that was broken in the field.\n//!\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n//!   endpoint/origin; restart the dispatcher generation only → the\n//!   historical stream neither takes nor clears the current controller\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\n//!   the retired row left the broker's enumeration).\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n//!   restart the dispatcher → durable classification (the pinned opener)\n//!   resumes the SAME operator stream.\n//! - D3: ONE injected transient worker-start failure → the claim requeues\n//!   and the retry serves the stream (no permanent abandonment, no\n//!   duplicate controller).\n//!\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\n//! hermetic QUIC brokers per test.\n\nuse std::process::{Child, Command, Stdio};\nuse std::sync::atomic::{AtomicU32, Ordering};\nuse std::sync::{Arc, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::attach::{request_attach, send_attach_input};\nuse spt_daemon::brain::{Brain, BrokerEvent};\nuse spt_daemon::effect::{Minter, MintedOp};\nuse spt_daemon::msg::SpawnReq;\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::Broker;\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_term::ScreenGrid;\n\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\n\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\n    #[cfg(unix)]\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n    #[cfg(windows)]\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n    SpawnReq {\n        program,\n        args,\n        rows: 24,\n        cols: 80,\n        endpoint: endpoint.to_string(),\n        cwd: None,\n        env: Default::default(),\n        translation_binary: None,\n        adapter: String::new(),\n        install_dir: None,\n    }\n}\n\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\nstruct Generation {\n    child: Child,\n    log: std::path::PathBuf,\n}\n\nimpl Generation {\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n        cmd.arg(broker_name)\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n            .stdout(Stdio::null())\n            .stderr(Stdio::from(f));\n        for (k, v) in extra_env {\n            cmd.env(k, v);\n        }\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n        Generation { child, log }\n    }\n\n    fn stderr_log(&self) -> String {\n        std::fs::read_to_string(&self.log).unwrap_or_default()\n    }\n\n    fn kill(mut self) {\n        let _ = self.child.kill();\n        let _ = self.child.wait();\n    }\n}\n\n/// The A-side (target broker) peer-stream ids, via a probe brain.\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\n    probe\n        .net_streams()\n        .expect(\"net-streams\")\n        .streams\n        .iter()\n        .filter(|s| !s.initiated_locally)\n        .map(|s| s.stream_id)\n        .collect()\n}\n\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n/// Returns `true` if an EOF for the stream arrived while rendering (the\n/// steal-clears-controller symptom D1 must NOT see).\nfn render_until(\n    operator: &mut Brain,\n    stream_id: u64,\n    decoder: &mut AttachDecoder,\n    cursor: &mut u64,\n    buf: &mut Vec<u8>,\n    needle: &[u8],\n    deadline: Duration,\n) -> bool {\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\n    let end = Instant::now() + deadline;\n    let mut saw_eof = false;\n    while !contains(buf, needle) {\n        assert!(\n            Instant::now() < end,\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\n            String::from_utf8_lossy(needle),\n            String::from_utf8_lossy(buf)\n        );\n        match operator.read_event().expect(\"operator read event\") {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\n                        if seq < *cursor {\n                            continue; // replay duplicate — dedup at the render cursor\n                        }\n                        *cursor = seq + 1;\n                        buf.extend_from_slice(\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\n                        );\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                saw_eof = true;\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\nfn screen_of(viewport: &[u8]) -> String {\n    let mut screen = ScreenGrid::new(24, 80);\n    screen.advance(viewport);\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\n}\n\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n// through PRODUCTION rediscovery: a finished historical Attach and a current\n// active Attach on the same endpoint/origin; only the dispatcher generation\n// restarts. The historical stream must neither take nor clear the current\n// controller: post-restart input still renders (the frozen-PTY symptom is\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n// clear), the fresh generation logs NO replay of the historical stream id,\n// and the retired row is GONE from the broker's enumeration\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n#[test]\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\n\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n    let end = Instant::now() + Duration::from_secs(20);\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n        thread::sleep(Duration::from_millis(50));\n    }\n\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\n\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\n    // rediscovery re-serves what the broker still enumerates.\n    gen1.kill();\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n\n    // THE regression: input typed AFTER the restart still renders — the\n    // reconstruction re-took the controller (successor re-take parity), and\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n    assert!(\n        !eof_post,\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\n         historical detach cleared it (the 7.41 steal)\"\n    );\n    let painted = screen_of(&view2);\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\n\n    // The fresh generation never touched the historical stream id — the\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\n    // absent for s1, while s2's re-serve is expected work, not noise.\n    let gen2_log = gen2.stderr_log();\n    assert!(\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\n    );\n    let _ = s2_a;\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n[Showing lines 1-300 of 621. Use :301 to continue]"}],"details":{"truncation":{"content":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\r\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\r\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\r\n//! a dispatcher GENERATION whose death drops its workers' broker conns\r\n//! exactly like a production brain death). NO manual re-serve anywhere: the\r\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\r\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\r\n//! classification seam that was broken in the field.\r\n//!\r\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\r\n//!   endpoint/origin; restart the dispatcher generation only → the\r\n//!   historical stream neither takes nor clears the current controller\r\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\r\n//!   the retired row left the broker's enumeration).\r\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\r\n//!   restart the dispatcher → durable classification (the pinned opener)\r\n//!   resumes the SAME operator stream.\r\n//! - D3: ONE injected transient worker-start failure → the claim requeues\r\n//!   and the retry serves the stream (no permanent abandonment, no\r\n//!   duplicate controller).\r\n//!\r\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\r\n//! hermetic QUIC brokers per test.\r\n\r\nuse std::process::{Child, Command, Stdio};\r\nuse std::sync::atomic::{AtomicU32, Ordering};\r\nuse std::sync::{Arc, OnceLock};\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nuse spt_daemon::attach::{request_attach, send_attach_input};\r\nuse spt_daemon::brain::{Brain, BrokerEvent};\r\nuse spt_daemon::effect::{Minter, MintedOp};\r\nuse spt_daemon::msg::SpawnReq;\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::Broker;\r\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_term::ScreenGrid;\r\n\r\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\r\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\r\n\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\r\n    #[cfg(unix)]\r\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\r\n    #[cfg(windows)]\r\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\r\n    SpawnReq {\r\n        program,\r\n        args,\r\n        rows: 24,\r\n        cols: 80,\r\n        endpoint: endpoint.to_string(),\r\n        cwd: None,\r\n        env: Default::default(),\r\n        translation_binary: None,\r\n        adapter: String::new(),\r\n        install_dir: None,\r\n    }\r\n}\r\n\r\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\r\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\r\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\r\nstruct Generation {\r\n    child: Child,\r\n    log: std::path::PathBuf,\r\n}\r\n\r\nimpl Generation {\r\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\r\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\r\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\r\n        cmd.arg(broker_name)\r\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\r\n            .stdout(Stdio::null())\r\n            .stderr(Stdio::from(f));\r\n        for (k, v) in extra_env {\r\n            cmd.env(k, v);\r\n        }\r\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\r\n        Generation { child, log }\r\n    }\r\n\r\n    fn stderr_log(&self) -> String {\r\n        std::fs::read_to_string(&self.log).unwrap_or_default()\r\n    }\r\n\r\n    fn kill(mut self) {\r\n        let _ = self.child.kill();\r\n        let _ = self.child.wait();\r\n    }\r\n}\r\n\r\n/// The A-side (target broker) peer-stream ids, via a probe brain.\r\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\r\n    probe\r\n        .net_streams()\r\n        .expect(\"net-streams\")\r\n        .streams\r\n        .iter()\r\n        .filter(|s| !s.initiated_locally)\r\n        .map(|s| s.stream_id)\r\n        .collect()\r\n}\r\n\r\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\r\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\r\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\r\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\r\n/// Returns `true` if an EOF for the stream arrived while rendering (the\r\n/// steal-clears-controller symptom D1 must NOT see).\r\nfn render_until(\r\n    operator: &mut Brain,\r\n    stream_id: u64,\r\n    decoder: &mut AttachDecoder,\r\n    cursor: &mut u64,\r\n    buf: &mut Vec<u8>,\r\n    needle: &[u8],\r\n    deadline: Duration,\r\n) -> bool {\r\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\r\n    let end = Instant::now() + deadline;\r\n    let mut saw_eof = false;\r\n    while !contains(buf, needle) {\r\n        assert!(\r\n            Instant::now() < end,\r\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\r\n            String::from_utf8_lossy(needle),\r\n            String::from_utf8_lossy(buf)\r\n        );\r\n        match operator.read_event().expect(\"operator read event\") {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\r\n                        if seq < *cursor {\r\n                            continue; // replay duplicate — dedup at the render cursor\r\n                        }\r\n                        *cursor = seq + 1;\r\n                        buf.extend_from_slice(\r\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                saw_eof = true;\r\n            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r\n}\r\n\r\nfn screen_of(viewport: &[u8]) -> String {\r\n    let mut screen = ScreenGrid::new(24, 80);\r\n    screen.advance(viewport);\r\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\r\n// through PRODUCTION rediscovery: a finished historical Attach and a current\r\n// active Attach on the same endpoint/origin; only the dispatcher generation\r\n// restarts. The historical stream must neither take nor clear the current\r\n// controller: post-restart input still renders (the frozen-PTY symptom is\r\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\r\n// clear), the fresh generation logs NO replay of the historical stream id,\r\n// and the retired row is GONE from the broker's enumeration\r\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\r\n#[test]\r\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\r\n\r\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\r\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\r\n    let end = Instant::now() + Duration::from_secs(20);\r\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n\r\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\r\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\r\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\r\n\r\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\r\n    // rediscovery re-serves what the broker still enumerates.\r\n    gen1.kill();\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n\r\n    // THE regression: input typed AFTER the restart still renders — the\r\n    // reconstruction re-took the controller (successor re-take parity), and\r\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\r\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\r\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\r\n    assert!(\r\n        !eof_post,\r\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\r\n         historical detach cleared it (the 7.41 steal)\"\r\n    );\r\n    let painted = screen_of(&view2);\r\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\r\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\r\n\r\n    // The fresh generation never touched the historical stream id — the\r\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\r\n    // absent for s1, while s2's re-serve is expected work, not noise.\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\r\n    );\r\n    let _ = s2_a;\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r","truncated":true,"truncatedBy":"lines","totalLines":621,"totalBytes":12658,"outputLines":300,"outputBytes":12658,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\r\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\r\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\r\n//! a dispatcher GENERATION whose death drops its workers' broker conns\r\n//! exactly like a production brain death). NO manual re-serve anywhere: the\r\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\r\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\r\n//! classification seam that was broken in the field.\r\n//!\r\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\r\n//!   endpoint/origin; restart the dispatcher generation only → the\r\n//!   historical stream neither takes nor clears the current controller\r\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\r\n//!   the retired row left the broker's enumeration).\r\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\r\n//!   restart the dispatcher → durable classification (the pinned opener)\r\n//!   resumes the SAME operator stream.\r\n//! - D3: ONE injected transient worker-start failure → the claim requeues\r\n//!   and the retry serves the stream (no permanent abandonment, no\r\n//!   duplicate controller).\r\n//!\r\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\r\n//! hermetic QUIC brokers per test.\r\n\r\nuse std::process::{Child, Command, Stdio};\r\nuse std::sync::atomic::{AtomicU32, Ordering};\r\nuse std::sync::{Arc, OnceLock};\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nuse spt_daemon::attach::{request_attach, send_attach_input};\r\nuse spt_daemon::brain::{Brain, BrokerEvent};\r\nuse spt_daemon::effect::{Minter, MintedOp};\r\nuse spt_daemon::msg::SpawnReq;\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::Broker;\r\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_term::ScreenGrid;\r\n\r\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\r\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\r\n\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\r\n    #[cfg(unix)]\r\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\r\n    #[cfg(windows)]\r\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\r\n    SpawnReq {\r\n        program,\r\n        args,\r\n        rows: 24,\r\n        cols: 80,\r\n        endpoint: endpoint.to_string(),\r\n        cwd: None,\r\n        env: Default::default(),\r\n        translation_binary: None,\r\n        adapter: String::new(),\r\n        install_dir: None,\r\n    }\r\n}\r\n\r\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\r\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\r\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\r\nstruct Generation {\r\n    child: Child,\r\n    log: std::path::PathBuf,\r\n}\r\n\r\nimpl Generation {\r\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\r\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\r\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\r\n        cmd.arg(broker_name)\r\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\r\n            .stdout(Stdio::null())\r\n            .stderr(Stdio::from(f));\r\n        for (k, v) in extra_env {\r\n            cmd.env(k, v);\r\n        }\r\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\r\n        Generation { child, log }\r\n    }\r\n\r\n    fn stderr_log(&self) -> String {\r\n        std::fs::read_to_string(&self.log).unwrap_or_default()\r\n    }\r\n\r\n    fn kill(mut self) {\r\n        let _ = self.child.kill();\r\n        let _ = self.child.wait();\r\n    }\r\n}\r\n\r\n/// The A-side (target broker) peer-stream ids, via a probe brain.\r\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\r\n    probe\r\n        .net_streams()\r\n        .expect(\"net-streams\")\r\n        .streams\r\n        .iter()\r\n        .filter(|s| !s.initiated_locally)\r\n        .map(|s| s.stream_id)\r\n        .collect()\r\n}\r\n\r\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\r\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\r\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\r\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\r\n/// Returns `true` if an EOF for the stream arrived while rendering (the\r\n/// steal-clears-controller symptom D1 must NOT see).\r\nfn render_until(\r\n    operator: &mut Brain,\r\n    stream_id: u64,\r\n    decoder: &mut AttachDecoder,\r\n    cursor: &mut u64,\r\n    buf: &mut Vec<u8>,\r\n    needle: &[u8],\r\n    deadline: Duration,\r\n) -> bool {\r\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\r\n    let end = Instant::now() + deadline;\r\n    let mut saw_eof = false;\r\n    while !contains(buf, needle) {\r\n        assert!(\r\n            Instant::now() < end,\r\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\r\n            String::from_utf8_lossy(needle),\r\n            String::from_utf8_lossy(buf)\r\n        );\r\n        match operator.read_event().expect(\"operator read event\") {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\r\n                        if seq < *cursor {\r\n                            continue; // replay duplicate — dedup at the render cursor\r\n                        }\r\n                        *cursor = seq + 1;\r\n                        buf.extend_from_slice(\r\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                saw_eof = true;\r\n            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r\n}\r\n\r\nfn screen_of(viewport: &[u8]) -> String {\r\n    let mut screen = ScreenGrid::new(24, 80);\r\n    screen.advance(viewport);\r\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\r\n// through PRODUCTION rediscovery: a finished historical Attach and a current\r\n// active Attach on the same endpoint/origin; only the dispatcher generation\r\n// restarts. The historical stream must neither take nor clear the current\r\n// controller: post-restart input still renders (the frozen-PTY symptom is\r\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\r\n// clear), the fresh generation logs NO replay of the historical stream id,\r\n// and the retired row is GONE from the broker's enumeration\r\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\r\n#[test]\r\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\r\n\r\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\r\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\r\n    let end = Instant::now() + Duration::from_secs(20);\r\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n\r\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\r\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\r\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\r\n\r\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\r\n    // rediscovery re-serves what the broker still enumerates.\r\n    gen1.kill();\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n\r\n    // THE regression: input typed AFTER the restart still renders — the\r\n    // reconstruction re-took the controller (successor re-take parity), and\r\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\r\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\r\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\r\n    assert!(\r\n        !eof_post,\r\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\r\n         historical detach cleared it (the 7.41 steal)\"\r\n    );\r\n    let painted = screen_of(&view2);\r\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\r\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\r\n\r\n    // The fresh generation never touched the historical stream id — the\r\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\r\n    // absent for s1, while s2's re-serve is expected work, not noise.\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\r\n    );\r\n    let _ = s2_a;\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}","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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":621,"totalBytes":12658,"outputLines":300,"outputBytes":12658,"shownRange":{"start":1,"end":300},"nextOffset":301}}},"isError":false,"timestamp":1784271500966}}
{"type":"message","id":"27589372","parentId":"c4eb3541","timestamp":"2026-07-17T06:58:20.991Z","message":{"role":"toolResult","toolCallId":"call_UkQg8h5L66zfR05cLeoYmQpB|fc_089095e18118ee7d016a59d28cd5708190971e8a12982cd790","toolName":"read","content":[{"type":"text","text":"//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n//! windows (33 observed, all 15,000-15,154ms).\n//!\n//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns\n//! through the production broker subscribe path — gate ruling 1):\n//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n//!     the first failed write; the producer stays FLAT (never a serial poison\n//!     composition); recovery replays gapless from the cursor.\n//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n//!     subscribed + replayed against the conn).\n//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n//!     stream's serve lease — the paired forwarding leg terminates at its\n//!     next send; a new subscriber generation renews and serves again.\n//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n//!     recovers after the window (the not-abandonment discriminator).\n//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n//!     see) replayed by a fresh generation neither takes nor clears the live\n//!     replacement controller (ownership/generation validation).\n//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n//!     the ring-peek fallback fires on the old broker's explicit\n//!     unknown-verb answer and serves correctly.\n//!\n//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n//! binary alongside — the retire/steal regressions are substrate).\n//!\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n//! `--test-threads=1` — tests set process-wide env knobs\n//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n\nuse std::process::{Child, Command, Stdio};\nuse std::sync::atomic::{AtomicU32, Ordering};\nuse std::sync::{Arc, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::attach::{request_attach, send_attach_input};\nuse spt_daemon::brain::{Brain, BrokerEvent};\nuse spt_daemon::effect::{MintedOp, Minter};\nuse spt_daemon::msg::SpawnReq;\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::Broker;\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_term::ScreenGrid;\n\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-rdstall-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\n    #[cfg(unix)]\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n    #[cfg(windows)]\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n    SpawnReq {\n        program,\n        args,\n        rows: 24,\n        cols: 80,\n        endpoint: endpoint.to_string(),\n        cwd: None,\n        env: Default::default(),\n        translation_binary: None,\n        adapter: String::new(),\n        install_dir: None,\n    }\n}\n\n/// One REAL dispatcher generation (the redispatch.rs harness): the\n/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n/// brain death. Stderr lands in `log`.\nstruct Generation {\n    child: Child,\n    log: std::path::PathBuf,\n}\n\nimpl Generation {\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n        cmd.arg(broker_name)\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n            .stdout(Stdio::null())\n            .stderr(Stdio::from(f));\n        for (k, v) in extra_env {\n            cmd.env(k, v);\n        }\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n        Generation { child, log }\n    }\n\n    fn stderr_log(&self) -> String {\n        std::fs::read_to_string(&self.log).unwrap_or_default()\n    }\n\n    fn kill(mut self) {\n        let _ = self.child.kill();\n        let _ = self.child.wait();\n    }\n}\n\n/// The A-side peer-stream ids, via a probe brain.\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\n    probe\n        .net_streams()\n        .expect(\"net-streams\")\n        .streams\n        .iter()\n        .filter(|s| !s.initiated_locally)\n        .map(|s| s.stream_id)\n        .collect()\n}\n\n/// Render the operator's viewport until `needle` appears (the redispatch.rs\n/// helper, verbatim). Returns whether an EOF for the stream arrived.\nfn render_until(\n    operator: &mut Brain,\n    stream_id: u64,\n    decoder: &mut AttachDecoder,\n    cursor: &mut u64,\n    buf: &mut Vec<u8>,\n    needle: &[u8],\n    deadline: Duration,\n) -> bool {\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\n    let end = Instant::now() + deadline;\n    let mut saw_eof = false;\n    while !contains(buf, needle) {\n        assert!(\n            Instant::now() < end,\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\n            String::from_utf8_lossy(needle),\n            String::from_utf8_lossy(buf)\n        );\n        match operator.read_event().expect(\"operator read event\") {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\n                        if seq < *cursor {\n                            continue;\n                        }\n                        *cursor = seq + 1;\n                        buf.extend_from_slice(\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\n                        );\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                saw_eof = true;\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\nfn screen_of(viewport: &[u8]) -> String {\n    let mut screen = ScreenGrid::new(24, 80);\n    screen.advance(viewport);\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\n}\n\n/// A raw B→A traffic stream: dial A from B, open a stream, write an NDJSON-ish\n/// opener line. Returns (operator brain, B-side stream id, A-side stream id).\nfn open_traffic_stream(\n    _name_a: &str,\n    name_b: &str,\n    probe: &mut Brain,\n    opener: &[u8],\n) -> (Brain, u64, u64) {\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n    let mut operator = connect_retry(name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let opened = operator\n        .net_open_stream(conn.conn_id, Some(MintedOp::new(Minter::Cli, 2)))\n        .expect(\"open stream\");\n    operator\n        .net_stream_send(opened.stream_id, opener, Some(MintedOp::new(Minter::Cli, 3)), false)\n        .expect(\"send opener\");\n    // The A-side row appears once the pump appends the opener bytes.\n    let end = Instant::now() + Duration::from_secs(20);\n    let sid_a = loop {\n        if let Some(id) = peer_stream_ids(probe).first().copied() {\n            break id;\n        }\n        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n        thread::sleep(Duration::from_millis(25));\n    };\n    (operator, opened.stream_id, sid_a)\n}\n\n/// Subscribe a fresh brain to `sid_a` from seq 0, retrying through the\n/// no-reinstall drain window (\"subscriber busy\" surfaces as an Error event),\n/// then accumulate the replay until `want_bytes` bytes arrived. Asserts DENSE\n/// seq contiguity from 0 (gapless) and returns the bytes.\nfn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n    let end = Instant::now() + deadline;\n    'attach: loop {\n        assert!(Instant::now() < end, \"recovery subscriber never attached + drained\");\n        let mut f = connect_retry(name_a);\n        f.net_stream_subscribe(sid_a, 0).expect(\"subscribe send\");\n        let mut got: Vec<u8> = Vec::new();\n        let mut next = 0u64;\n        loop {\n            if Instant::now() >= end {\n                panic!(\n                    \"recovery drain incomplete: {}/{want_bytes} bytes at seq {next}\",\n                    got.len()\n                );\n            }\n            match f.read_event() {\n                Ok(BrokerEvent::NetStreamData {\n                    stream_id, seq, bytes,\n                }) if stream_id == sid_a => {\n                    assert_eq!(\n                        seq, next,\n                        \"the recovery replay must be DENSE from 0 — a gap means the \\\n                         overflow/poison lost retained bytes\"\n                    );\n                    next += 1;\n                    got.extend_from_slice(&bytes);\n                    if got.len() >= want_bytes {\n                        assert_eq!(got.len(), want_bytes, \"byte-exact, never over\");\n                        return got;\n                    }\n                }\n                Ok(BrokerEvent::Error { message }) => {\n                    // The prior poisoned seat may still be draining — the\n                    // no-reinstall gate refuses busy; retry on a fresh conn.\n                    assert!(\n                        message.contains(\"subscriber busy\"),\n                        \"unexpected error while re-attaching: {message}\"\n                    );\n                    thread::sleep(Duration::from_millis(200));\n                    continue 'attach;\n                }\n                Ok(_) => {}\n                Err(e) => panic!(\"recovery subscriber read failed: {e}\"),\n            }\n        }\n    }\n}\n\n[Showing lines 1-300 of 799. Use :301 to continue]"}],"details":{"truncation":{"content":"//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n//! windows (33 observed, all 15,000-15,154ms).\n//!\n//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns\n//! through the production broker subscribe path — gate ruling 1):\n//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n//!     the first failed write; the producer stays FLAT (never a serial poison\n//!     composition); recovery replays gapless from the cursor.\n//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n//!     subscribed + replayed against the conn).\n//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n//!     stream's serve lease — the paired forwarding leg terminates at its\n//!     next send; a new subscriber generation renews and serves again.\n//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n//!     recovers after the window (the not-abandonment discriminator).\n//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n//!     see) replayed by a fresh generation neither takes nor clears the live\n//!     replacement controller (ownership/generation validation).\n//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n//!     the ring-peek fallback fires on the old broker's explicit\n//!     unknown-verb answer and serves correctly.\n//!\n//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n//! binary alongside — the retire/steal regressions are substrate).\n//!\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n//! `--test-threads=1` — tests set process-wide env knobs\n//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n\nuse std::process::{Child, Command, Stdio};\nuse std::sync::atomic::{AtomicU32, Ordering};\nuse std::sync::{Arc, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::attach::{request_attach, send_attach_input};\nuse spt_daemon::brain::{Brain, BrokerEvent};\nuse spt_daemon::effect::{MintedOp, Minter};\nuse spt_daemon::msg::SpawnReq;\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::Broker;\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_term::ScreenGrid;\n\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-rdstall-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\n    #[cfg(unix)]\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n    #[cfg(windows)]\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n    SpawnReq {\n        program,\n        args,\n        rows: 24,\n        cols: 80,\n        endpoint: endpoint.to_string(),\n        cwd: None,\n        env: Default::default(),\n        translation_binary: None,\n        adapter: String::new(),\n        install_dir: None,\n    }\n}\n\n/// One REAL dispatcher generation (the redispatch.rs harness): the\n/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n/// brain death. Stderr lands in `log`.\nstruct Generation {\n    child: Child,\n    log: std::path::PathBuf,\n}\n\nimpl Generation {\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n        cmd.arg(broker_name)\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n            .stdout(Stdio::null())\n            .stderr(Stdio::from(f));\n        for (k, v) in extra_env {\n            cmd.env(k, v);\n        }\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n        Generation { child, log }\n    }\n\n    fn stderr_log(&self) -> String {\n        std::fs::read_to_string(&self.log).unwrap_or_default()\n    }\n\n    fn kill(mut self) {\n        let _ = self.child.kill();\n        let _ = self.child.wait();\n    }\n}\n\n/// The A-side peer-stream ids, via a probe brain.\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\n    probe\n        .net_streams()\n        .expect(\"net-streams\")\n        .streams\n        .iter()\n        .filter(|s| !s.initiated_locally)\n        .map(|s| s.stream_id)\n        .collect()\n}\n\n/// Render the operator's viewport until `needle` appears (the redispatch.rs\n/// helper, verbatim). Returns whether an EOF for the stream arrived.\nfn render_until(\n    operator: &mut Brain,\n    stream_id: u64,\n    decoder: &mut AttachDecoder,\n    cursor: &mut u64,\n    buf: &mut Vec<u8>,\n    needle: &[u8],\n    deadline: Duration,\n) -> bool {\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\n    let end = Instant::now() + deadline;\n    let mut saw_eof = false;\n    while !contains(buf, needle) {\n        assert!(\n            Instant::now() < end,\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\n            String::from_utf8_lossy(needle),\n            String::from_utf8_lossy(buf)\n        );\n        match operator.read_event().expect(\"operator read event\") {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\n                        if seq < *cursor {\n                            continue;\n                        }\n                        *cursor = seq + 1;\n                        buf.extend_from_slice(\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\n                        );\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                saw_eof = true;\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\nfn screen_of(viewport: &[u8]) -> String {\n    let mut screen = ScreenGrid::new(24, 80);\n    screen.advance(viewport);\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\n}\n\n/// A raw B→A traffic stream: dial A from B, open a stream, write an NDJSON-ish\n/// opener line. Returns (operator brain, B-side stream id, A-side stream id).\nfn open_traffic_stream(\n    _name_a: &str,\n    name_b: &str,\n    probe: &mut Brain,\n    opener: &[u8],\n) -> (Brain, u64, u64) {\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n    let mut operator = connect_retry(name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let opened = operator\n        .net_open_stream(conn.conn_id, Some(MintedOp::new(Minter::Cli, 2)))\n        .expect(\"open stream\");\n    operator\n        .net_stream_send(opened.stream_id, opener, Some(MintedOp::new(Minter::Cli, 3)), false)\n        .expect(\"send opener\");\n    // The A-side row appears once the pump appends the opener bytes.\n    let end = Instant::now() + Duration::from_secs(20);\n    let sid_a = loop {\n        if let Some(id) = peer_stream_ids(probe).first().copied() {\n            break id;\n        }\n        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n        thread::sleep(Duration::from_millis(25));\n    };\n    (operator, opened.stream_id, sid_a)\n}\n\n/// Subscribe a fresh brain to `sid_a` from seq 0, retrying through the\n/// no-reinstall drain window (\"subscriber busy\" surfaces as an Error event),\n/// then accumulate the replay until `want_bytes` bytes arrived. Asserts DENSE\n/// seq contiguity from 0 (gapless) and returns the bytes.\nfn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n    let end = Instant::now() + deadline;\n    'attach: loop {\n        assert!(Instant::now() < end, \"recovery subscriber never attached + drained\");\n        let mut f = connect_retry(name_a);\n        f.net_stream_subscribe(sid_a, 0).expect(\"subscribe send\");\n        let mut got: Vec<u8> = Vec::new();\n        let mut next = 0u64;\n        loop {\n            if Instant::now() >= end {\n                panic!(\n                    \"recovery drain incomplete: {}/{want_bytes} bytes at seq {next}\",\n                    got.len()\n                );\n            }\n            match f.read_event() {\n                Ok(BrokerEvent::NetStreamData {\n                    stream_id, seq, bytes,\n                }) if stream_id == sid_a => {\n                    assert_eq!(\n                        seq, next,\n                        \"the recovery replay must be DENSE from 0 — a gap means the \\\n                         overflow/poison lost retained bytes\"\n                    );\n                    next += 1;\n                    got.extend_from_slice(&bytes);\n                    if got.len() >= want_bytes {\n                        assert_eq!(got.len(), want_bytes, \"byte-exact, never over\");\n                        return got;\n                    }\n                }\n                Ok(BrokerEvent::Error { message }) => {\n                    // The prior poisoned seat may still be draining — the\n                    // no-reinstall gate refuses busy; retry on a fresh conn.\n                    assert!(\n                        message.contains(\"subscriber busy\"),\n                        \"unexpected error while re-attaching: {message}\"\n                    );\n                    thread::sleep(Duration::from_millis(200));\n                    continue 'attach;\n                }\n                Ok(_) => {}\n                Err(e) => panic!(\"recovery subscriber read failed: {e}\"),\n            }","truncated":true,"truncatedBy":"lines","totalLines":799,"totalBytes":11687,"outputLines":300,"outputBytes":11687,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n//! windows (33 observed, all 15,000-15,154ms).\n//!\n//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns\n//! through the production broker subscribe path — gate ruling 1):\n//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n//!     the first failed write; the producer stays FLAT (never a serial poison\n//!     composition); recovery replays gapless from the cursor.\n//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n//!     subscribed + replayed against the conn).\n//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n//!     stream's serve lease — the paired forwarding leg terminates at its\n//!     next send; a new subscriber generation renews and serves again.\n//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n//!     recovers after the window (the not-abandonment discriminator).\n//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n//!     see) replayed by a fresh generation neither takes nor clears the live\n//!     replacement controller (ownership/generation validation).\n//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n//!     the ring-peek fallback fires on the old broker's explicit\n//!     unknown-verb answer and serves correctly.\n//!\n//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n//! binary alongside — the retire/steal regressions are substrate).\n//!\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n//! `--test-threads=1` — tests set process-wide env knobs\n//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n\nuse std::process::{Child, Command, Stdio};\nuse std::sync::atomic::{AtomicU32, Ordering};\nuse std::sync::{Arc, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::attach::{request_attach, send_attach_input};\nuse spt_daemon::brain::{Brain, BrokerEvent};\nuse spt_daemon::effect::{MintedOp, Minter};\nuse spt_daemon::msg::SpawnReq;\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::Broker;\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_term::ScreenGrid;\n\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-rdstall-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\n    #[cfg(unix)]\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n    #[cfg(windows)]\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n    SpawnReq {\n        program,\n        args,\n        rows: 24,\n        cols: 80,\n        endpoint: endpoint.to_string(),\n        cwd: None,\n        env: Default::default(),\n        translation_binary: None,\n        adapter: String::new(),\n        install_dir: None,\n    }\n}\n\n/// One REAL dispatcher generation (the redispatch.rs harness): the\n/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n/// brain death. Stderr lands in `log`.\nstruct Generation {\n    child: Child,\n    log: std::path::PathBuf,\n}\n\nimpl Generation {\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n        cmd.arg(broker_name)\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n            .stdout(Stdio::null())\n            .stderr(Stdio::from(f));\n        for (k, v) in extra_env {\n            cmd.env(k, v);\n        }\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n        Generation { child, log }\n    }\n\n    fn stderr_log(&self) -> String {\n        std::fs::read_to_string(&self.log).unwrap_or_default()\n    }\n\n    fn kill(mut self) {\n        let _ = self.child.kill();\n        let _ = self.child.wait();\n    }\n}\n\n/// The A-side peer-stream ids, via a probe brain.\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\n    probe\n        .net_streams()\n        .expect(\"net-streams\")\n        .streams\n        .iter()\n        .filter(|s| !s.initiated_locally)\n        .map(|s| s.stream_id)\n        .collect()\n}\n\n/// Render the operator's viewport until `needle` appears (the redispatch.rs\n/// helper, verbatim). Returns whether an EOF for the stream arrived.\nfn render_until(\n    operator: &mut Brain,\n    stream_id: u64,\n    decoder: &mut AttachDecoder,\n    cursor: &mut u64,\n    buf: &mut Vec<u8>,\n    needle: &[u8],\n    deadline: Duration,\n) -> bool {\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\n    let end = Instant::now() + deadline;\n    let mut saw_eof = false;\n    while !contains(buf, needle) {\n        assert!(\n            Instant::now() < end,\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\n            String::from_utf8_lossy(needle),\n            String::from_utf8_lossy(buf)\n        );\n        match operator.read_event().expect(\"operator read event\") {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\n                        if seq < *cursor {\n                            continue;\n                        }\n                        *cursor = seq + 1;\n                        buf.extend_from_slice(\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\n                        );\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                saw_eof = true;\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\nfn screen_of(viewport: &[u8]) -> String {\n    let mut screen = ScreenGrid::new(24, 80);\n    screen.advance(viewport);\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\n}\n\n/// A raw B→A traffic stream: dial A from B, open a stream, write an NDJSON-ish\n/// opener line. Returns (operator brain, B-side stream id, A-side stream id).\nfn open_traffic_stream(\n    _name_a: &str,\n    name_b: &str,\n    probe: &mut Brain,\n    opener: &[u8],\n) -> (Brain, u64, u64) {\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n    let mut operator = connect_retry(name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let opened = operator\n        .net_open_stream(conn.conn_id, Some(MintedOp::new(Minter::Cli, 2)))\n        .expect(\"open stream\");\n    operator\n        .net_stream_send(opened.stream_id, opener, Some(MintedOp::new(Minter::Cli, 3)), false)\n        .expect(\"send opener\");\n    // The A-side row appears once the pump appends the opener bytes.\n    let end = Instant::now() + Duration::from_secs(20);\n    let sid_a = loop {\n        if let Some(id) = peer_stream_ids(probe).first().copied() {\n            break id;\n        }\n        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n        thread::sleep(Duration::from_millis(25));\n    };\n    (operator, opened.stream_id, sid_a)\n}\n\n/// Subscribe a fresh brain to `sid_a` from seq 0, retrying through the\n/// no-reinstall drain window (\"subscriber busy\" surfaces as an Error event),\n/// then accumulate the replay until `want_bytes` bytes arrived. Asserts DENSE\n/// seq contiguity from 0 (gapless) and returns the bytes.\nfn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n    let end = Instant::now() + deadline;\n    'attach: loop {\n        assert!(Instant::now() < end, \"recovery subscriber never attached + drained\");\n        let mut f = connect_retry(name_a);\n        f.net_stream_subscribe(sid_a, 0).expect(\"subscribe send\");\n        let mut got: Vec<u8> = Vec::new();\n        let mut next = 0u64;\n        loop {\n            if Instant::now() >= end {\n                panic!(\n                    \"recovery drain incomplete: {}/{want_bytes} bytes at seq {next}\",\n                    got.len()\n                );\n            }\n            match f.read_event() {\n                Ok(BrokerEvent::NetStreamData {\n                    stream_id, seq, bytes,\n                }) if stream_id == sid_a => {\n                    assert_eq!(\n                        seq, next,\n                        \"the recovery replay must be DENSE from 0 — a gap means the \\\n                         overflow/poison lost retained bytes\"\n                    );\n                    next += 1;\n                    got.extend_from_slice(&bytes);\n                    if got.len() >= want_bytes {\n                        assert_eq!(got.len(), want_bytes, \"byte-exact, never over\");\n                        return got;\n                    }\n                }\n                Ok(BrokerEvent::Error { message }) => {\n                    // The prior poisoned seat may still be draining — the\n                    // no-reinstall gate refuses busy; retry on a fresh conn.\n                    assert!(\n                        message.contains(\"subscriber busy\"),\n                        \"unexpected error while re-attaching: {message}\"\n                    );\n                    thread::sleep(Duration::from_millis(200));\n                    continue 'attach;\n                }\n                Ok(_) => {}\n                Err(e) => panic!(\"recovery subscriber read failed: {e}\"),\n            }\n        }\n    }\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch_stall.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":799,"totalBytes":11687,"outputLines":300,"outputBytes":11687,"shownRange":{"start":1,"end":300},"nextOffset":301}}},"isError":false,"timestamp":1784271500991}}
{"type":"message","id":"d4f216e9","parentId":"27589372","timestamp":"2026-07-17T06:58:21.002Z","message":{"role":"toolResult","toolCallId":"call_ZfpfhyXwKv7WO3hIzmBs7gDn|fc_089095e18118ee7d016a59d28cd56481908d2da9f5bce1160c","toolName":"read","content":[{"type":"text","text":"//! Inbound net-stream dispatch (D9-1) — the daemon-side acceptor that turns\n//! the hand-wired serve loops of D5–D8 into production behavior.\n//!\n//! Every E2E test so far drove the serve half manually (a probe brain calling\n//! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against\n//! a known stream). In production nothing did — a peer could dial and write\n//! and the bytes sat in the broker's stream log unread. This module is the\n//! missing consumer: a daemon-hosted loop that watches the broker's stream\n//! table for **peer-initiated** streams, classifies each by its first NDJSON\n//! record, and hands it to the right serve loop on its own worker thread.\n//!\n//! ## Direction (the gotcha-#9 fact, now first-class)\n//!\n//! The stream table holds locally-opened outbound streams too. The dispatcher\n//! claims only rows with `initiated_locally == false` — serving our own\n//! requester stream would deadlock the requester against ourselves. The flag\n//! is broker-recorded at registration (`open_stream` vs the accept loop),\n//! never inferred.\n//!\n//! ## Classification (first-record demux)\n//!\n//! Streams are single-purpose; the opener's first record names the protocol.\n//! The kind-tagged families collide on variant names (`request` is both\n//! sync's and attach's opener; `chunk`/`done`/`err` recur everywhere), so the\n//! demux keys on the first line's **shape**, not its kind alone:\n//!\n//! | first line | family |\n//! |---|---|\n//! | `kind=request` + `sync_id` | sync pull ([`crate::sync::serve_sync`]) |\n//! | `kind=request` + `session_id` | remote-drive attach (D9-1-3) |\n//! | `kind=request` + `endpoint`+`event` | remote rest op ([`crate::resthost::serve_rest`]) |\n//! | `kind=request` + `owner`+`shell_ref` | cross-node shell link ([`crate::linkhost::serve_shell_link`]) |\n//! | `kind=query` | update pull ([`crate::propagate::serve_update`]) |\n//! | `kind=row` | notif feed ([`crate::notifsync::apply_notif_feed`]) |\n//! | `kind=fetch` / `kind=push` | file transfer (D9-1-3) |\n//! | untagged + `target`+`op_id` | WAN message feed ([`crate::wan::receive_wan`]) |\n//! | untagged + `subnet`+`instance` | registry feed (D9-1-2) |\n//! | anything else | unknown — dropped, loud |\n//!\n//! A family whose production wiring lands in a later slice is still\n//! classified here (the table is complete) but routed to a typed\n//! [`Unwired`](DispatchOutcome::Unwired) drop — loud, never silent.\n//!\n//! ## Worker model\n//!\n//! Each claimed stream gets its own worker thread with its **own** brain IPC\n//! connection (the broker is multi-client by design; the probe-brain pattern\n//! every E2E test uses). The peek and the serve use **separate** connections:\n//! the peek brain subscribes from 0 to read the first line and is dropped;\n//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n//! stream's event feed alone (no double-subscription interleave). Origin is\n//! the table's `remote_id_hex` — handshake-proven, never payload\n//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n// [impl->REQ-NET-1]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n\nuse std::collections::HashMap;\nuse std::io;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_net::net::notif::NotifDecoder;\nuse spt_net::net::replicate::{\n    FeedDecoder as RegistryDecoder, NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate,\n};\nuse spt_net::net::wanmsg::{WanDecoder, WanReply};\nuse spt_store::contextstore::ContextStore;\nuse spt_store::notif::NotifStore;\nuse spt_store::perch;\nuse spt_store::roster::RosterStore;\n\nuse crate::attach::serve_attach;\nuse crate::brain::{Brain, BrokerEvent};\nuse crate::notifsync::{apply_notif_feed, NotifPolicy};\nuse crate::propagate::serve_update;\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\nuse crate::relcache::ReleaseCache;\nuse crate::sync::{serve_sync, SyncPolicy};\nuse crate::wan::receive_wan;\nuse crate::xfer::serve_xfer;\n\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n/// workers block on their stream's event feed).\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StreamFamily {\n    /// A context sync pull (`SyncRecord::Request`).\n    Sync,\n    /// An update pull (`UpdRecord::Query`).\n    Update,\n    /// A notif replication feed (`NotifRecord::Row`).\n    Notif,\n    /// A WAN message feed (untagged `WanMessage` lines).\n    WanMsg,\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\n    Registry,\n    /// A remote-drive attach (`AttachRecord::Request`).\n    Attach,\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\n    Xfer,\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\n    /// node, M5-D5b).\n    Rest,\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\n    /// against an owner's shell on this node, M5-D8c).\n    ShellLink,\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\n    ServeProbe,\n    /// Unclassifiable — dropped.\n    Unknown,\n}\n\n/// Classify one decoded first line by shape (module-docs demux table).\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\n            StreamFamily::Rest\n        }\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\n            StreamFamily::ShellLink\n        }\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\n        Some(\"query\") => StreamFamily::Update,\n        // The status-only convergence query rides the update family (M8\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\n        Some(\"status_query\") => StreamFamily::Update,\n        Some(\"row\") => StreamFamily::Notif,\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\n        Some(_) => StreamFamily::Unknown,\n        None => {\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\n                StreamFamily::WanMsg\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\n                StreamFamily::Registry\n            } else if line.get(\"subnet\").is_some()\n                && line.get(\"node\").is_some()\n                && line.get(\"label\").is_some()\n            {\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\n                // sends label records first (no instance rows), so the feed's\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\n                StreamFamily::Registry\n            } else {\n                StreamFamily::Unknown\n            }\n        }\n    }\n}\n\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\n#[derive(Debug)]\npub enum DispatchOutcome {\n    /// The family's serve loop ran to its own outcome (rendered for the log).\n    Served(String),\n    /// The first line never arrived / didn't parse — dropped.\n    Unknown,\n    /// The worker failed (IO against broker or store).\n    Failed(String),\n}\n\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n/// insert performed BEFORE worker spawn and never cleared — one transient\n/// worker-setup failure permanently abandoned the stream. Claims now\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\n/// requeues bounded (backoff-shaped, never a hot loop).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ClaimState {\n    /// A worker owns the stream right now (`attempts` = prior failures).\n    InFlight { attempts: u32 },\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n…\n}\n\n[Showing lines 1-300 of 1558. Use :301 to continue]"}],"details":{"truncation":{"content":"//! Inbound net-stream dispatch (D9-1) — the daemon-side acceptor that turns\n//! the hand-wired serve loops of D5–D8 into production behavior.\n//!\n//! Every E2E test so far drove the serve half manually (a probe brain calling\n//! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against\n//! a known stream). In production nothing did — a peer could dial and write\n//! and the bytes sat in the broker's stream log unread. This module is the\n//! missing consumer: a daemon-hosted loop that watches the broker's stream\n//! table for **peer-initiated** streams, classifies each by its first NDJSON\n//! record, and hands it to the right serve loop on its own worker thread.\n//!\n//! ## Direction (the gotcha-#9 fact, now first-class)\n//!\n//! The stream table holds locally-opened outbound streams too. The dispatcher\n//! claims only rows with `initiated_locally == false` — serving our own\n//! requester stream would deadlock the requester against ourselves. The flag\n//! is broker-recorded at registration (`open_stream` vs the accept loop),\n//! never inferred.\n//!\n//! ## Classification (first-record demux)\n//!\n//! Streams are single-purpose; the opener's first record names the protocol.\n//! The kind-tagged families collide on variant names (`request` is both\n//! sync's and attach's opener; `chunk`/`done`/`err` recur everywhere), so the\n//! demux keys on the first line's **shape**, not its kind alone:\n//!\n//! | first line | family |\n//! |---|---|\n//! | `kind=request` + `sync_id` | sync pull ([`crate::sync::serve_sync`]) |\n//! | `kind=request` + `session_id` | remote-drive attach (D9-1-3) |\n//! | `kind=request` + `endpoint`+`event` | remote rest op ([`crate::resthost::serve_rest`]) |\n//! | `kind=request` + `owner`+`shell_ref` | cross-node shell link ([`crate::linkhost::serve_shell_link`]) |\n//! | `kind=query` | update pull ([`crate::propagate::serve_update`]) |\n//! | `kind=row` | notif feed ([`crate::notifsync::apply_notif_feed`]) |\n//! | `kind=fetch` / `kind=push` | file transfer (D9-1-3) |\n//! | untagged + `target`+`op_id` | WAN message feed ([`crate::wan::receive_wan`]) |\n//! | untagged + `subnet`+`instance` | registry feed (D9-1-2) |\n//! | anything else | unknown — dropped, loud |\n//!\n//! A family whose production wiring lands in a later slice is still\n//! classified here (the table is complete) but routed to a typed\n//! [`Unwired`](DispatchOutcome::Unwired) drop — loud, never silent.\n//!\n//! ## Worker model\n//!\n//! Each claimed stream gets its own worker thread with its **own** brain IPC\n//! connection (the broker is multi-client by design; the probe-brain pattern\n//! every E2E test uses). The peek and the serve use **separate** connections:\n//! the peek brain subscribes from 0 to read the first line and is dropped;\n//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n//! stream's event feed alone (no double-subscription interleave). Origin is\n//! the table's `remote_id_hex` — handshake-proven, never payload\n//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n// [impl->REQ-NET-1]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n\nuse std::collections::HashMap;\nuse std::io;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_net::net::notif::NotifDecoder;\nuse spt_net::net::replicate::{\n    FeedDecoder as RegistryDecoder, NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate,\n};\nuse spt_net::net::wanmsg::{WanDecoder, WanReply};\nuse spt_store::contextstore::ContextStore;\nuse spt_store::notif::NotifStore;\nuse spt_store::perch;\nuse spt_store::roster::RosterStore;\n\nuse crate::attach::serve_attach;\nuse crate::brain::{Brain, BrokerEvent};\nuse crate::notifsync::{apply_notif_feed, NotifPolicy};\nuse crate::propagate::serve_update;\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\nuse crate::relcache::ReleaseCache;\nuse crate::sync::{serve_sync, SyncPolicy};\nuse crate::wan::receive_wan;\nuse crate::xfer::serve_xfer;\n\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n/// workers block on their stream's event feed).\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StreamFamily {\n    /// A context sync pull (`SyncRecord::Request`).\n    Sync,\n    /// An update pull (`UpdRecord::Query`).\n    Update,\n    /// A notif replication feed (`NotifRecord::Row`).\n    Notif,\n    /// A WAN message feed (untagged `WanMessage` lines).\n    WanMsg,\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\n    Registry,\n    /// A remote-drive attach (`AttachRecord::Request`).\n    Attach,\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\n    Xfer,\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\n    /// node, M5-D5b).\n    Rest,\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\n    /// against an owner's shell on this node, M5-D8c).\n    ShellLink,\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\n    ServeProbe,\n    /// Unclassifiable — dropped.\n    Unknown,\n}\n\n/// Classify one decoded first line by shape (module-docs demux table).\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\n            StreamFamily::Rest\n        }\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\n            StreamFamily::ShellLink\n        }\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\n        Some(\"query\") => StreamFamily::Update,\n        // The status-only convergence query rides the update family (M8\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\n        Some(\"status_query\") => StreamFamily::Update,\n        Some(\"row\") => StreamFamily::Notif,\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\n        Some(_) => StreamFamily::Unknown,\n        None => {\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\n                StreamFamily::WanMsg\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\n                StreamFamily::Registry\n            } else if line.get(\"subnet\").is_some()\n                && line.get(\"node\").is_some()\n                && line.get(\"label\").is_some()\n            {\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\n                // sends label records first (no instance rows), so the feed's\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\n                StreamFamily::Registry\n            } else {\n                StreamFamily::Unknown\n            }\n        }\n    }\n}\n\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\n#[derive(Debug)]\npub enum DispatchOutcome {\n    /// The family's serve loop ran to its own outcome (rendered for the log).\n    Served(String),\n    /// The first line never arrived / didn't parse — dropped.\n    Unknown,\n    /// The worker failed (IO against broker or store).\n    Failed(String),\n}\n\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n/// insert performed BEFORE worker spawn and never cleared — one transient\n/// worker-setup failure permanently abandoned the stream. Claims now\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\n/// requeues bounded (backoff-shaped, never a hot loop).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ClaimState {\n    /// A worker owns the stream right now (`attempts` = prior failures).\n    InFlight { attempts: u32 },\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {","truncated":true,"truncatedBy":"lines","totalLines":1558,"totalBytes":13307,"outputLines":300,"outputBytes":13307,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! Inbound net-stream dispatch (D9-1) — the daemon-side acceptor that turns\n//! the hand-wired serve loops of D5–D8 into production behavior.\n//!\n//! Every E2E test so far drove the serve half manually (a probe brain calling\n//! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against\n//! a known stream). In production nothing did — a peer could dial and write\n//! and the bytes sat in the broker's stream log unread. This module is the\n//! missing consumer: a daemon-hosted loop that watches the broker's stream\n//! table for **peer-initiated** streams, classifies each by its first NDJSON\n//! record, and hands it to the right serve loop on its own worker thread.\n//!\n//! ## Direction (the gotcha-#9 fact, now first-class)\n//!\n//! The stream table holds locally-opened outbound streams too. The dispatcher\n//! claims only rows with `initiated_locally == false` — serving our own\n//! requester stream would deadlock the requester against ourselves. The flag\n//! is broker-recorded at registration (`open_stream` vs the accept loop),\n//! never inferred.\n//!\n//! ## Classification (first-record demux)\n//!\n//! Streams are single-purpose; the opener's first record names the protocol.\n//! The kind-tagged families collide on variant names (`request` is both\n//! sync's and attach's opener; `chunk`/`done`/`err` recur everywhere), so the\n//! demux keys on the first line's **shape**, not its kind alone:\n//!\n//! | first line | family |\n//! |---|---|\n//! | `kind=request` + `sync_id` | sync pull ([`crate::sync::serve_sync`]) |\n//! | `kind=request` + `session_id` | remote-drive attach (D9-1-3) |\n//! | `kind=request` + `endpoint`+`event` | remote rest op ([`crate::resthost::serve_rest`]) |\n//! | `kind=request` + `owner`+`shell_ref` | cross-node shell link ([`crate::linkhost::serve_shell_link`]) |\n//! | `kind=query` | update pull ([`crate::propagate::serve_update`]) |\n//! | `kind=row` | notif feed ([`crate::notifsync::apply_notif_feed`]) |\n//! | `kind=fetch` / `kind=push` | file transfer (D9-1-3) |\n//! | untagged + `target`+`op_id` | WAN message feed ([`crate::wan::receive_wan`]) |\n//! | untagged + `subnet`+`instance` | registry feed (D9-1-2) |\n//! | anything else | unknown — dropped, loud |\n//!\n//! A family whose production wiring lands in a later slice is still\n//! classified here (the table is complete) but routed to a typed\n//! [`Unwired`](DispatchOutcome::Unwired) drop — loud, never silent.\n//!\n//! ## Worker model\n//!\n//! Each claimed stream gets its own worker thread with its **own** brain IPC\n//! connection (the broker is multi-client by design; the probe-brain pattern\n//! every E2E test uses). The peek and the serve use **separate** connections:\n//! the peek brain subscribes from 0 to read the first line and is dropped;\n//! the serve loop then subscribes from 0 on a fresh connection so it owns its\n//! stream's event feed alone (no double-subscription interleave). Origin is\n//! the table's `remote_id_hex` — handshake-proven, never payload\n//! (REQ-HAZARD-WAN-ORIGIN-AUTH); every family's own gate then runs unchanged.\n// [impl->REQ-NET-1]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n\nuse std::collections::HashMap;\nuse std::io;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_net::net::notif::NotifDecoder;\nuse spt_net::net::replicate::{\n    FeedDecoder as RegistryDecoder, NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate,\n};\nuse spt_net::net::wanmsg::{WanDecoder, WanReply};\nuse spt_store::contextstore::ContextStore;\nuse spt_store::notif::NotifStore;\nuse spt_store::perch;\nuse spt_store::roster::RosterStore;\n\nuse crate::attach::serve_attach;\nuse crate::brain::{Brain, BrokerEvent};\nuse crate::notifsync::{apply_notif_feed, NotifPolicy};\nuse crate::propagate::serve_update;\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\nuse crate::relcache::ReleaseCache;\nuse crate::sync::{serve_sync, SyncPolicy};\nuse crate::wan::receive_wan;\nuse crate::xfer::serve_xfer;\n\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n/// workers block on their stream's event feed).\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StreamFamily {\n    /// A context sync pull (`SyncRecord::Request`).\n    Sync,\n    /// An update pull (`UpdRecord::Query`).\n    Update,\n    /// A notif replication feed (`NotifRecord::Row`).\n    Notif,\n    /// A WAN message feed (untagged `WanMessage` lines).\n    WanMsg,\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\n    Registry,\n    /// A remote-drive attach (`AttachRecord::Request`).\n    Attach,\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\n    Xfer,\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\n    /// node, M5-D5b).\n    Rest,\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\n    /// against an owner's shell on this node, M5-D8c).\n    ShellLink,\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\n    ServeProbe,\n    /// Unclassifiable — dropped.\n    Unknown,\n}\n\n/// Classify one decoded first line by shape (module-docs demux table).\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\n            StreamFamily::Rest\n        }\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\n            StreamFamily::ShellLink\n        }\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\n        Some(\"query\") => StreamFamily::Update,\n        // The status-only convergence query rides the update family (M8\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\n        Some(\"status_query\") => StreamFamily::Update,\n        Some(\"row\") => StreamFamily::Notif,\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\n        Some(_) => StreamFamily::Unknown,\n        None => {\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\n                StreamFamily::WanMsg\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\n                StreamFamily::Registry\n            } else if line.get(\"subnet\").is_some()\n                && line.get(\"node\").is_some()\n                && line.get(\"label\").is_some()\n            {\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\n                // sends label records first (no instance rows), so the feed's\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\n                StreamFamily::Registry\n            } else {\n                StreamFamily::Unknown\n            }\n        }\n    }\n}\n\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\n#[derive(Debug)]\npub enum DispatchOutcome {\n    /// The family's serve loop ran to its own outcome (rendered for the log).\n    Served(String),\n    /// The first line never arrived / didn't parse — dropped.\n    Unknown,\n    /// The worker failed (IO against broker or store).\n    Failed(String),\n}\n\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n/// insert performed BEFORE worker spawn and never cleared — one transient\n/// worker-setup failure permanently abandoned the stream. Claims now\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\n/// requeues bounded (backoff-shaped, never a hot loop).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ClaimState {\n    /// A worker owns the stream right now (`attempts` = prior failures).\n    InFlight { attempts: u32 },\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n…\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,null,306]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":13307,"outputLines":300,"outputBytes":13307,"shownRange":{"start":1,"end":300},"nextOffset":301}}},"isError":false,"timestamp":1784271501002}}
{"type":"message","id":"c0f285c4","parentId":"d4f216e9","timestamp":"2026-07-17T06:58:21.010Z","message":{"role":"toolResult","toolCallId":"call_LZZswLUgVgx24T9HewOMfz1W|fc_089095e18118ee7d016a59d28cd5308190a0cab7b7bfc36f22","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## src/\n### broker.rs\n 4|//! would lose continuity on if the logic restarted: the PTY master+writer and\n*5|//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n 6|//! client / listening sockets. It runs **no logic** — no routing, registry,\n*7|//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n 8|//! forward its output to the brain, inject the brain's input, report exit.\n 9|//!\n 10|//! ## Gapless handoff (B2 — the load-bearing invariant)\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...\n 38|use std::sync::{Arc, Mutex};\n*39|use std::thread::{self, JoinHandle};\n 40|use std::time::{Duration, Instant};\n 41|\n 42|use interprocess::local_socket::{prelude::*, Stream};\n...\n 58|    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n*59|    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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...\n 66|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n*67|    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n*68|    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n*69|    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n 70|};\n 71|use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n 72|use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n...\n 74|\n*75|/// The shared, serialized send side of one brain connection. Output frames,\n*76|/// replay frames, command acks, and exit/error events all write through this.\n 77|/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\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...\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 87|/// disconnected session from buffering without limit.\n 88|const DEFAULT_LOG_CHUNKS: usize = 4096;\n 89|\n...\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*95|/// owns the replay batch), so a viewer attaching to a busy session is not\n 96|/// spuriously evicted.\n 97|const VIEWER_CHANNEL_DEPTH: usize = 256;\n 98|\n 99|/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n*100|/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\n 101|const MAX_VIEWERS: usize = 32;\n 102|\n 103|/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n 104|/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\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...\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### dispatch.rs\n 9|//! table for **peer-initiated** streams, classifies each by its first NDJSON\n*10|//! record, and hands it to the right serve loop on its own worker thread.\n 11|//!\n 12|//! ## Direction (the gotcha-#9 fact, now first-class)\n 13|//!\n...\n 43|//!\n*44|//! ## Worker model\n 45|//!\n*46|//! Each claimed stream gets its own worker thread with its **own** brain IPC\n*47|//! connection (the broker is multi-client by design; the probe-brain pattern\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 61|use std::sync::{Arc, Mutex, OnceLock};\n*62|use std::thread;\n 63|use std::time::{Duration, Instant};\n 64|\n 65|use spt_net::net::notif::NotifDecoder;\n...\n 85|/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n*86|/// workers block on their stream's event feed).\n 87|pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n 88|\n 89|/// What protocol a peer-initiated stream speaks, by its first record's shape.\n...\n 157|\n*158|/// Why a dispatched stream's worker ended — loud + typed, never silent.\n 159|#[derive(Debug)]\n 160|pub enum DispatchOutcome {\n 161|    /// The family's serve loop ran to its own outcome (rendered for the log).\n...\n 164|    Unknown,\n*165|    /// The worker failed (IO against broker or store).\n 166|    Failed(String),\n 167|}\n 168|\n...\n 170|/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n*171|/// insert performed BEFORE worker spawn and never cleared — one transient\n*172|/// worker-setup failure permanently abandoned the stream. Claims now\n 173|/// distinguish in-flight vs terminal vs retryable, and a transient failure\n 174|/// requeues bounded (backoff-shaped, never a hot loop).\n 175|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176|enum ClaimState {\n*177|    /// A worker owns the stream right now (`attempts` = prior failures).\n 178|    InFlight { attempts: u32 },\n 179|    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n 180|    /// retries exhausted. Terminal outcomes never re-enter the queue.\n...\n 197|\n*198|/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n 199|/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n 200|/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n*201|/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n 202|/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n 203|/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n 204|/// bounded-write deadline), and three fast retries would burn to Terminal\n...\n 210|    /// The stream's subscriber seat is poisoned or still draining — retrying\n*211|    /// immediately would manufacture another replay victim.\n 212|    SeatBlocked,\n 213|}\n 214|\n*215|/// Classify a worker failure message (the seat-blocked shapes are the two\n 216|/// strings the nethost subscriber discipline emits).\n 217|// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 218|fn classify_failure(msg: &str) -> FailClass {\n 219|    if msg.contains(\"subscriber busy\")\n*220|        || msg.contains(\"replay halted\")\n 221|        || msg.contains(\"lease canceled\")\n 222|    {\n 223|        FailClass::SeatBlocked\n...\n 233|/// wire fact. Everything else (transport timeout, EOF, poison) is a\n*234|/// transient — Failed → bounded requeue, never a second replay subscriber.\n 235|// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 236|fn is_unsupported_verb(e: &io::Error) -> bool {\n 237|    e.to_string().contains(\"unknown command kind\")\n...\n 242|/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n*243|/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n 244|/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n 245|// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 246|#[derive(Debug, Default)]\n\n## tests/\n### redispatch.rs\n*1|//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n 2|//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n 3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n*4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n 5|//! exactly like a production brain death). NO manual re-serve anywhere: the\n 6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n 7|//! hand-stepped a known stream and bypassed exactly the rediscovery/\n...\n 12|//!   historical stream neither takes nor clears the current controller\n*13|//!   (post-restart input still renders; no EOF; no replay-storm log line;\n*14|//!   the retired row left the broker's enumeration).\n 15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n 16|//!   restart the dispatcher → durable classification (the pinned opener)\n 17|//!   resumes the SAME operator stream.\n*18|//! - D3: ONE injected transient worker-start failure → the claim requeues\n 19|//!   and the retry serves the stream (no permanent abandonment, no\n 20|//!   duplicate controller).\n 21|//!\n*22|//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\n 23|//! hermetic QUIC brokers per test.\n 24|\n 25|use std::process::{Child, Command, Stdio};\n...\n 27|use std::sync::{Arc, OnceLock};\n*28|use std::thread;\n 29|use std::time::{Duration, Instant};\n 30|\n 31|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 33|use spt_daemon::effect::{Minter, MintedOp};\n*34|use spt_daemon::msg::SpawnReq;\n 35|use spt_daemon::nethost::{NetConfig, NetHost};\n 36|use spt_daemon::Broker;\n 37|use spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\n...\n 73|    let serve = Arc::clone(&broker);\n*74|    thread::spawn(move || {\n 75|        let _ = serve.serve();\n 76|    });\n 77|    broker\n...\n 84|        }\n*85|        thread::sleep(Duration::from_millis(10));\n 86|    }\n 87|    panic!(\"brain could not connect\");\n 88|}\n 89|\n*90|fn echo_spawn_req(endpoint: &str) -> SpawnReq {\n 91|    #[cfg(unix)]\n 92|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 93|    #[cfg(windows)]\n 94|    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n*95|    SpawnReq {\n 96|        program,\n 97|        args,\n 98|        rows: 24,\n...\n 110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n*111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112|struct Generation {\n 113|    child: Child,\n 114|    log: std::path::PathBuf,\n...\n 117|impl Generation {\n*118|    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119|        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120|        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121|        cmd.arg(broker_name)\n...\n 127|        }\n*128|        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n 129|        Generation { child, log }\n 130|    }\n 131|\n...\n 153|\n*154|/// Render the operator's viewport (dedup `Output` by seq — replays after a\n 155|/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156|/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157|/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n...\n 187|                        if seq < *cursor {\n*188|                            continue; // replay duplicate — dedup at the render cursor\n 189|                        }\n 190|                        *cursor = seq + 1;\n 191|                        buf.extend_from_slice(\n...\n 211|\n*212|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214|// active Attach on the same endpoint/origin; only the dispatcher generation\n 215|// restarts. The historical stream must neither take nor clear the current\n 216|// controller: post-restart input still renders (the frozen-PTY symptom is\n*217|// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218|// clear), the fresh generation logs NO replay of the historical stream id,\n### redispatch_stall.rs\n*1|//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n*2|//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n 3|//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n 4|//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n*5|//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n 6|//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n 7|//! windows (33 observed, all 15,000-15,154ms).\n 8|//!\n...\n 10|//! through the production broker subscribe path — gate ruling 1):\n*11|//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n 12|//!     the first failed write; the producer stays FLAT (never a serial poison\n*13|//!     composition); recovery replays gapless from the cursor.\n 14|//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n 15|//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n*16|//!     subscribed + replayed against the conn).\n 17|//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n 18|//!     stream's serve lease — the paired forwarding leg terminates at its\n 19|//!     next send; a new subscriber generation renews and serves again.\n*20|//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n 21|//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n 22|//!     recovers after the window (the not-abandonment discriminator).\n 23|//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n*24|//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n*25|//!     see) replayed by a fresh generation neither takes nor clears the live\n 26|//!     replacement controller (ownership/generation validation).\n 27|//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n 28|//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n...\n 31|//!\n*32|//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n*33|//! binary alongside — the retire/steal regressions are substrate).\n 34|//!\n 35|//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n*36|//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n 37|//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n*38|//! `--test-threads=1` — tests set process-wide env knobs\n 39|//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n 40|\n 41|use std::process::{Child, Command, Stdio};\n...\n 43|use std::sync::{Arc, OnceLock};\n*44|use std::thread;\n 45|use std::time::{Duration, Instant};\n 46|\n 47|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 49|use spt_daemon::effect::{MintedOp, Minter};\n*50|use spt_daemon::msg::SpawnReq;\n 51|use spt_daemon::nethost::{NetConfig, NetHost};\n 52|use spt_daemon::Broker;\n 53|use spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\n...\n 87|    let serve = Arc::clone(&broker);\n*88|    thread::spawn(move || {\n 89|        let _ = serve.serve();\n 90|    });\n 91|    broker\n...\n 98|        }\n*99|        thread::sleep(Duration::from_millis(10));\n 100|    }\n 101|    panic!(\"brain could not connect\");\n 102|}\n 103|\n*104|fn echo_spawn_req(endpoint: &str) -> SpawnReq {\n 105|    #[cfg(unix)]\n 106|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 107|    #[cfg(windows)]\n 108|    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n*109|    SpawnReq {\n 110|        program,\n 111|        args,\n 112|        rows: 24,\n...\n 122|\n*123|/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124|/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125|/// brain death. Stderr lands in `log`.\n 126|struct Generation {"}],"details":{"scopePath":"crates/spt-daemon/src/broker.rs, crates/spt-daemon/src/dispatch.rs, crates/spt-daemon/tests/redispatch.rs, crates/spt-daemon/tests/redispatch_stall.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":80,"fileCount":4,"files":["crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/src/dispatch.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/\n\n## src/\n### broker.rs\n   4│//! would lose continuity on if the logic restarted: the PTY master+writer and\n  *5│//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n   6│//! client / listening sockets. It runs **no logic** — no routing, registry,\n  *7│//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n   8│//! forward its output to the brain, inject the brain's input, report exit.\n   9│//!\n  10│//! ## Gapless handoff (B2 — the load-bearing invariant)\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    │...\n  38│use std::sync::{Arc, Mutex};\n *39│use std::thread::{self, JoinHandle};\n  40│use std::time::{Duration, Instant};\n  41│\n  42│use interprocess::local_socket::{prelude::*, Stream};\n    │...\n  58│    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n *59│    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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    │...\n  66│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n *67│    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n *68│    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n *69│    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n  70│};\n  71│use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n  72│use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n    │...\n  74│\n *75│/// The shared, serialized send side of one brain connection. Output frames,\n *76│/// replay frames, command acks, and exit/error events all write through this.\n  77│/// `pub(crate)`: the net host's stream logs (D4b) live-send through the same\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    │...\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  87│/// disconnected session from buffering without limit.\n  88│const DEFAULT_LOG_CHUNKS: usize = 4096;\n  89│\n    │...\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 *95│/// owns the replay batch), so a viewer attaching to a busy session is not\n  96│/// spuriously evicted.\n  97│const VIEWER_CHANNEL_DEPTH: usize = 256;\n  98│\n  99│/// Soft cap on concurrent viewers per session (REQ-HAZARD-VIEWER-ISOLATION:\n*100│/// bounds the writer-thread count). A `Viewer` attach beyond the cap is refused.\n 101│const MAX_VIEWERS: usize = 32;\n 102│\n 103│/// Whether a viewer's bounded-queue `try_send` result demands EVICTION\n 104│/// (REQ-HAZARD-VIEWER-ISOLATION): a `Full` queue (the viewer can't keep up with\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    │...\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### dispatch.rs\n   9│//! table for **peer-initiated** streams, classifies each by its first NDJSON\n *10│//! record, and hands it to the right serve loop on its own worker thread.\n  11│//!\n  12│//! ## Direction (the gotcha-#9 fact, now first-class)\n  13│//!\n    │...\n  43│//!\n *44│//! ## Worker model\n  45│//!\n *46│//! Each claimed stream gets its own worker thread with its **own** brain IPC\n *47│//! connection (the broker is multi-client by design; the probe-brain pattern\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  61│use std::sync::{Arc, Mutex, OnceLock};\n *62│use std::thread;\n  63│use std::time::{Duration, Instant};\n  64│\n  65│use spt_net::net::notif::NotifDecoder;\n    │...\n  85│/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n *86│/// workers block on their stream's event feed).\n  87│pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n  88│\n  89│/// What protocol a peer-initiated stream speaks, by its first record's shape.\n    │...\n 157│\n*158│/// Why a dispatched stream's worker ended — loud + typed, never silent.\n 159│#[derive(Debug)]\n 160│pub enum DispatchOutcome {\n 161│    /// The family's serve loop ran to its own outcome (rendered for the log).\n    │...\n 164│    Unknown,\n*165│    /// The worker failed (IO against broker or store).\n 166│    Failed(String),\n 167│}\n 168│\n    │...\n 170│/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n*171│/// insert performed BEFORE worker spawn and never cleared — one transient\n*172│/// worker-setup failure permanently abandoned the stream. Claims now\n 173│/// distinguish in-flight vs terminal vs retryable, and a transient failure\n 174│/// requeues bounded (backoff-shaped, never a hot loop).\n 175│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176│enum ClaimState {\n*177│    /// A worker owns the stream right now (`attempts` = prior failures).\n 178│    InFlight { attempts: u32 },\n 179│    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n 180│    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    │...\n 197│\n*198│/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n 199│/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n 200│/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n*201│/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n 202│/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n 203│/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n 204│/// bounded-write deadline), and three fast retries would burn to Terminal\n    │...\n 210│    /// The stream's subscriber seat is poisoned or still draining — retrying\n*211│    /// immediately would manufacture another replay victim.\n 212│    SeatBlocked,\n 213│}\n 214│\n*215│/// Classify a worker failure message (the seat-blocked shapes are the two\n 216│/// strings the nethost subscriber discipline emits).\n 217│// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 218│fn classify_failure(msg: &str) -> FailClass {\n 219│    if msg.contains(\"subscriber busy\")\n*220│        || msg.contains(\"replay halted\")\n 221│        || msg.contains(\"lease canceled\")\n 222│    {\n 223│        FailClass::SeatBlocked\n    │...\n 233│/// wire fact. Everything else (transport timeout, EOF, poison) is a\n*234│/// transient — Failed → bounded requeue, never a second replay subscriber.\n 235│// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 236│fn is_unsupported_verb(e: &io::Error) -> bool {\n 237│    e.to_string().contains(\"unknown command kind\")\n    │...\n 242│/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n*243│/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n 244│/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n 245│// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n 246│#[derive(Debug, Default)]\n\n## tests/\n### redispatch.rs\n  *1│//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n   2│//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n   3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n  *4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n   5│//! exactly like a production brain death). NO manual re-serve anywhere: the\n   6│//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n   7│//! hand-stepped a known stream and bypassed exactly the rediscovery/\n    │...\n  12│//!   historical stream neither takes nor clears the current controller\n *13│//!   (post-restart input still renders; no EOF; no replay-storm log line;\n *14│//!   the retired row left the broker's enumeration).\n  15│//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n  16│//!   restart the dispatcher → durable classification (the pinned opener)\n  17│//!   resumes the SAME operator stream.\n *18│//! - D3: ONE injected transient worker-start failure → the claim requeues\n  19│//!   and the retry serves the stream (no permanent abandonment, no\n  20│//!   duplicate controller).\n  21│//!\n *22│//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\n  23│//! hermetic QUIC brokers per test.\n  24│\n  25│use std::process::{Child, Command, Stdio};\n    │...\n  27│use std::sync::{Arc, OnceLock};\n *28│use std::thread;\n  29│use std::time::{Duration, Instant};\n  30│\n  31│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  33│use spt_daemon::effect::{Minter, MintedOp};\n *34│use spt_daemon::msg::SpawnReq;\n  35│use spt_daemon::nethost::{NetConfig, NetHost};\n  36│use spt_daemon::Broker;\n  37│use spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\n    │...\n  73│    let serve = Arc::clone(&broker);\n *74│    thread::spawn(move || {\n  75│        let _ = serve.serve();\n  76│    });\n  77│    broker\n    │...\n  84│        }\n *85│        thread::sleep(Duration::from_millis(10));\n  86│    }\n  87│    panic!(\"brain could not connect\");\n  88│}\n  89│\n *90│fn echo_spawn_req(endpoint: &str) -> SpawnReq {\n  91│    #[cfg(unix)]\n  92│    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n  93│    #[cfg(windows)]\n  94│    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n *95│    SpawnReq {\n  96│        program,\n  97│        args,\n  98│        rows: 24,\n    │...\n 110│/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n*111│/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112│struct Generation {\n 113│    child: Child,\n 114│    log: std::path::PathBuf,\n    │...\n 117│impl Generation {\n*118│    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119│        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120│        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121│        cmd.arg(broker_name)\n    │...\n 127│        }\n*128│        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n 129│        Generation { child, log }\n 130│    }\n 131│\n    │...\n 153│\n*154│/// Render the operator's viewport (dedup `Output` by seq — replays after a\n 155│/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156│/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157│/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n    │...\n 187│                        if seq < *cursor {\n*188│                            continue; // replay duplicate — dedup at the render cursor\n 189│                        }\n 190│                        *cursor = seq + 1;\n 191│                        buf.extend_from_slice(\n    │...\n 211│\n*212│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214│// active Attach on the same endpoint/origin; only the dispatcher generation\n 215│// restarts. The historical stream must neither take nor clear the current\n 216│// controller: post-restart input still renders (the frozen-PTY symptom is\n*217│// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218│// clear), the fresh generation logs NO replay of the historical stream id,\n### redispatch_stall.rs\n  *1│//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n  *2│//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n   3│//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n   4│//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n  *5│//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n   6│//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n   7│//! windows (33 observed, all 15,000-15,154ms).\n   8│//!\n    │...\n  10│//! through the production broker subscribe path — gate ruling 1):\n *11│//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n  12│//!     the first failed write; the producer stays FLAT (never a serial poison\n *13│//!     composition); recovery replays gapless from the cursor.\n  14│//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n  15│//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n *16│//!     subscribed + replayed against the conn).\n  17│//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n  18│//!     stream's serve lease — the paired forwarding leg terminates at its\n  19│//!     next send; a new subscriber generation renews and serves again.\n *20│//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n  21│//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n  22│//!     recovers after the window (the not-abandonment discriminator).\n  23│//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n *24│//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n *25│//!     see) replayed by a fresh generation neither takes nor clears the live\n  26│//!     replacement controller (ownership/generation validation).\n  27│//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n  28│//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n    │...\n  31│//!\n *32│//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n *33│//! binary alongside — the retire/steal regressions are substrate).\n  34│//!\n  35│//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n *36│//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n  37│//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n *38│//! `--test-threads=1` — tests set process-wide env knobs\n  39│//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n  40│\n  41│use std::process::{Child, Command, Stdio};\n    │...\n  43│use std::sync::{Arc, OnceLock};\n *44│use std::thread;\n  45│use std::time::{Duration, Instant};\n  46│\n  47│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  49│use spt_daemon::effect::{MintedOp, Minter};\n *50│use spt_daemon::msg::SpawnReq;\n  51│use spt_daemon::nethost::{NetConfig, NetHost};\n  52│use spt_daemon::Broker;\n  53│use spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\n    │...\n  87│    let serve = Arc::clone(&broker);\n *88│    thread::spawn(move || {\n  89│        let _ = serve.serve();\n  90│    });\n  91│    broker\n    │...\n  98│        }\n *99│        thread::sleep(Duration::from_millis(10));\n 100│    }\n 101│    panic!(\"brain could not connect\");\n 102│}\n 103│\n*104│fn echo_spawn_req(endpoint: &str) -> SpawnReq {\n 105│    #[cfg(unix)]\n 106│    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 107│    #[cfg(windows)]\n 108│    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n*109│    SpawnReq {\n 110│        program,\n 111│        args,\n 112│        rows: 24,\n    │...\n 122│\n*123│/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124│/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125│/// brain death. Stderr lands in `log`.\n 126│struct Generation {"},"isError":false,"timestamp":1784271501010}}
{"type":"message","id":"f7f065a6","parentId":"c0f285c4","timestamp":"2026-07-17T06:58:21.011Z","message":{"role":"toolResult","toolCallId":"call_PQKYS8rh8BMdFAz5lygGEtHL|fc_089095e18118ee7d016a59d28cd5748190b59c5bb5daab7dcd","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 fc2579964647cfbf26d26bf045b20c162cb44b54 Reavo End <decidel@gmail.com> 1784261859 -0700\tupdate by push\nfc2579964647cfbf26d26bf045b20c162cb44b54 f4727cdd3da014fcd8097cbba3c63203fb6b38b2 Reavo End <decidel@gmail.com> 1784262306 -0700\tupdate by push\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 fc2579964647cfbf26d26bf045b20c162cb44b54 Reavo End <decidel@gmail.com> 1784261859 -0700\tupdate by push\nfc2579964647cfbf26d26bf045b20c162cb44b54 f4727cdd3da014fcd8097cbba3c63203fb6b38b2 Reavo End <decidel@gmail.com> 1784262306 -0700\tupdate by push\n","startLine":1,"lineNumbers":[1,2,3]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\remotes\\origin\\fix\\redispatch-stall-w1-replay-order"}}},"isError":false,"timestamp":1784271501011}}
{"type":"message","id":"27bd59e0","parentId":"f7f065a6","timestamp":"2026-07-17T06:58:21.011Z","message":{"role":"toolResult","toolCallId":"call_6wDFAWEmhvDVM9meNNNn67BF|fc_089095e18118ee7d016a59d28cd57c819091007edc88cb2a24","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784258640 -0700\tupdate by push\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784258640 -0700\tupdate by push\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\remotes\\origin\\release\\v0.36.0"}}},"isError":false,"timestamp":1784271501011}}
{"type":"message","id":"e8451cfe","parentId":"27bd59e0","timestamp":"2026-07-17T06:58:21.011Z","message":{"role":"toolResult","toolCallId":"call_6ExS8eHG46gFRvsQilUlVqWe|fc_089095e18118ee7d016a59d28cd578819084e64f9f42c54c38","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200461 -0700\tupdate by push\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201519 -0700\tupdate by push\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204306 -0700\tupdate by push\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204465 -0700\tupdate by push\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200461 -0700\tupdate by push\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201519 -0700\tupdate by push\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204306 -0700\tupdate by push\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204465 -0700\tupdate by push\n","startLine":1,"lineNumbers":[1,2,3,4,5]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\remotes\\origin\\build\\redispatch-truth-w1"}}},"isError":false,"timestamp":1784271501011}}
{"type":"message","id":"d41299b0","parentId":"e8451cfe","timestamp":"2026-07-17T06:58:21.050Z","message":{"role":"toolResult","toolCallId":"call_v5mBTEaZZ0WxMcveQ3n7spsm|fc_089095e18118ee7d016a59d28cd54881908a58861f1864cc54","toolName":"read","content":[{"type":"text","text":"//! The broker kernel (B1 + B2) — the stable half of the broker/brain split.\n//!\n//! Per ADR-0004 §B the broker holds **only** the resources a live consumer\n//! would lose continuity on if the logic restarted: the PTY master+writer and\n//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n//! client / listening sockets. It runs **no logic** — no routing, registry,\n//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n//! forward its output to the brain, inject the brain's input, report exit.\n//!\n//! ## Gapless handoff (B2 — the load-bearing invariant)\n//!\n//! A brain may be killed and restarted at any moment (a routine self-update, a\n//! crash). The broker — and every hosted child — must survive that untouched,\n//! and the new brain must re-attach to the output stream **gaplessly and\n//! exactly-once** (Spike #1 made real). Two mechanisms:\n//!\n//! - **Sequenced, buffered output.** Each session's output is appended to an\n//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n//!   in a bounded ring. The current subscriber (a brain connection) receives\n//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n//!   reach the brain in strict `seq` order with no interleave.\n//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n//!   live. The brain dedups by `seq`. Output produced *while no brain was\n//!   attached* sits in the ring and is replayed on the next subscribe — so a\n//!   brain restart loses nothing.\n//!\n//! Sessions are therefore **broker-owned and outlive any one connection**: a\n//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n//! output log persist. A session leaves the table only when its child exits\n//! (reaped by the exit waiter) or is explicitly killed.\n// [impl->REQ-DAEMON-2]\n\nuse std::collections::{HashMap, HashSet, VecDeque};\nuse std::io;\nuse std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};\nuse std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};\nuse std::sync::{Arc, Mutex};\nuse std::thread::{self, JoinHandle};\nuse std::time::{Duration, Instant};\n\nuse interprocess::local_socket::{prelude::*, Stream};\nuse spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n\nuse spt_net::net::attach::AttachIntent;\nuse spt_net::net::endpoint::EndpointAddr;\nuse spt_store::perch::{resolve_perch_path, ParentHint};\n\nuse crate::codec::read_frame;\nuse crate::conn::{BrokerConn, ConnWrite};\nuse crate::effect::{EffectJournal, EffectKey, EffectKind, Minter, Outcome};\nuse crate::frame::{Envelope, Role};\nuse crate::msg::{\n    applied_envelope, decode_bytes, displaced_envelope, endpoint_injected_envelope,\n    evicted_envelope, net_dialed_envelope, output_envelope,\n    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\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_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, 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]\nfn brain_write_deadline() -> Duration {\n    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(BRAIN_WRITE_DEADLINE),\n        Err(_) => BRAIN_WRITE_DEADLINE,\n    }\n}\n\n/// Bounded wait for a concurrent wake's session to register before a second wake for\n/// the same endpoint stops deduping and spawns itself (W4, REQ-SPAWN-COLLISION-GUARD-\n/// LIVE-DUP). A racing broker spawn registers its session in PTY-spawn time (ms), so a\n/// loser waits for the winner's session then no-op acks it (exactly one launch tree);\n/// only a spawn that OVERRAN this ceiling (presumed dead, its claim leaked) is taken\n/// over — availability over a vanishingly rare dup.\nconst WAKE_DEDUP_WAIT: Duration = Duration::from_secs(2);\n/// Poll slice while waiting on a racing wake's session to appear.\nconst WAKE_DEDUP_POLL: Duration = Duration::from_millis(50);\n\n/// RAII release of a single-flight wake claim (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP):\n/// the broker holds an endpoint id in [`Broker::wake_inflight`] for the duration of a\n/// spawn so a concurrent duplicate wake stands down; the guard releases it on EVERY\n/// exit (success, an early `?` spawn/drain error, or a panic), so a failed spawn never\n/// strands the endpoint permanently claimed.\nstruct WakeClaimGuard<'a> {\n    set: &'a Mutex<HashSet<String>>,\n    endpoint: String,\n}\n\nimpl Drop for WakeClaimGuard<'_> {\n    fn drop(&mut self) {\n        recover(self.set).remove(&self.endpoint);\n    }\n}\n\n/// The pure single-flight wake decision (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP), over\n/// the three observable inputs read together under the broker lock. `AlreadyLive` wins\n/// outright — the endpoint already has a live session, so a duplicate wake dedups to it\n/// (no spawn). Else a claim held by a racing spawn WITHIN the wait window is `Racing`\n/// (stand down + retry, waiting for the racer's session to register). Free — or a claim\n/// that OVERRAN the window (a leaked claim from a spawn presumed dead) — is `Claim`\n/// (proceed; availability over a vanishingly rare dup). Pure so the truth table is a unit.\n#[derive(Debug, PartialEq, Eq)]\nenum WakeGate {\n    AlreadyLive,\n    Racing,\n    Claim,\n}\n\n// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\nfn wake_gate_decision(live: bool, claimed: bool, past_deadline: bool) -> WakeGate {\n    if live {\n        WakeGate::AlreadyLive\n    } else if claimed && !past_deadline {\n        WakeGate::Racing\n    } else {\n        WakeGate::Claim\n    }\n}\n\n/// Whether a stall-evict re-stamps the perch INLINE (take/reattach path, already\n/// I/O-under-log-lock) or DEFERS the stamp to the off-lock convergence (the\n/// multi-session reap closure that forbids I/O under the shared sessions lock).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum StampMode {\n    Inline,\n    Deferred,\n}\n\n/// Broker-global tally of brain-subscriber stall-evicts (W2 observability rider,\n/// doyle ruling Q3): the count since broker boot + the wall-ms of the most recent.\n/// A brain that never returns is then a DIAGNOSABLE fact — surfaced on `daemon\n/// status` (via [`Broker::stall_evict_stats`]) alongside the per-evict broker log\n/// line, not a silent absence.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nstatic STALL_EVICT_COUNT: AtomicU64 = AtomicU64::new(0);\nstatic STALL_EVICT_LAST_MS: AtomicU64 = AtomicU64::new(0);\n\n/// Record one stall-evict in the broker-global tally (best-effort observability).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn record_stall_evict() {\n    STALL_EVICT_COUNT.fetch_add(1, Ordering::Relaxed);\n    STALL_EVICT_LAST_MS.store(crate::brain::now_ms(), Ordering::Relaxed);\n}\n\n/// The current stall-evict tally `(count_since_boot, last_evict_wall_ms)` —\n/// `last_evict_wall_ms` is 0 when none have happened. Read by the `daemon status`\n/// surface (the observability rider).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\npub(crate) fn stall_evict_stats() -> (u64, u64) {\n    (\n        STALL_EVICT_COUNT.load(Ordering::Relaxed),\n        STALL_EVICT_LAST_MS.load(Ordering::Relaxed),\n    )\n}\n\n/// MANDATORY bound on one injected keystroke sequence (W2 Layer C, ADR-0022 /\n/// doyle non-negotiable = W1's \"never park forever\"): the inject worker holds the\n/// floor — buffering operator input — only while a binary's response to one\n/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n/// buggy translation binary can therefore stall operator input for at most this\n/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\nconst INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n\n/// The effective commit deadline. Production is the const above; the int gate\n/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n/// so the structural outcome (floor releases → buffered input flushes → binary\n/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n/// the deadline's PRESENCE is the invariant, not its exact length.\nfn inject_commit_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_COMMIT_DEADLINE),\n        Err(_) => INJECT_COMMIT_DEADLINE,\n    }\n}\n\n[Showing lines 1-300 of 6298. Use :301 to continue]"}],"details":{"truncation":{"content":"//! The broker kernel (B1 + B2) — the stable half of the broker/brain split.\n//!\n//! Per ADR-0004 §B the broker holds **only** the resources a live consumer\n//! would lose continuity on if the logic restarted: the PTY master+writer and\n//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n//! client / listening sockets. It runs **no logic** — no routing, registry,\n//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n//! forward its output to the brain, inject the brain's input, report exit.\n//!\n//! ## Gapless handoff (B2 — the load-bearing invariant)\n//!\n//! A brain may be killed and restarted at any moment (a routine self-update, a\n//! crash). The broker — and every hosted child — must survive that untouched,\n//! and the new brain must re-attach to the output stream **gaplessly and\n//! exactly-once** (Spike #1 made real). Two mechanisms:\n//!\n//! - **Sequenced, buffered output.** Each session's output is appended to an\n//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n//!   in a bounded ring. The current subscriber (a brain connection) receives\n//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n//!   reach the brain in strict `seq` order with no interleave.\n//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n//!   live. The brain dedups by `seq`. Output produced *while no brain was\n//!   attached* sits in the ring and is replayed on the next subscribe — so a\n//!   brain restart loses nothing.\n//!\n//! Sessions are therefore **broker-owned and outlive any one connection**: a\n//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n//! output log persist. A session leaves the table only when its child exits\n//! (reaped by the exit waiter) or is explicitly killed.\n// [impl->REQ-DAEMON-2]\n\nuse std::collections::{HashMap, HashSet, VecDeque};\nuse std::io;\nuse std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};\nuse std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};\nuse std::sync::{Arc, Mutex};\nuse std::thread::{self, JoinHandle};\nuse std::time::{Duration, Instant};\n\nuse interprocess::local_socket::{prelude::*, Stream};\nuse spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n\nuse spt_net::net::attach::AttachIntent;\nuse spt_net::net::endpoint::EndpointAddr;\nuse spt_store::perch::{resolve_perch_path, ParentHint};\n\nuse crate::codec::read_frame;\nuse crate::conn::{BrokerConn, ConnWrite};\nuse crate::effect::{EffectJournal, EffectKey, EffectKind, Minter, Outcome};\nuse crate::frame::{Envelope, Role};\nuse crate::msg::{\n    applied_envelope, decode_bytes, displaced_envelope, endpoint_injected_envelope,\n    evicted_envelope, net_dialed_envelope, output_envelope,\n    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\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_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, 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]\nfn brain_write_deadline() -> Duration {\n    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(BRAIN_WRITE_DEADLINE),\n        Err(_) => BRAIN_WRITE_DEADLINE,\n    }\n}\n\n/// Bounded wait for a concurrent wake's session to register before a second wake for\n/// the same endpoint stops deduping and spawns itself (W4, REQ-SPAWN-COLLISION-GUARD-\n/// LIVE-DUP). A racing broker spawn registers its session in PTY-spawn time (ms), so a\n/// loser waits for the winner's session then no-op acks it (exactly one launch tree);\n/// only a spawn that OVERRAN this ceiling (presumed dead, its claim leaked) is taken\n/// over — availability over a vanishingly rare dup.\nconst WAKE_DEDUP_WAIT: Duration = Duration::from_secs(2);\n/// Poll slice while waiting on a racing wake's session to appear.\nconst WAKE_DEDUP_POLL: Duration = Duration::from_millis(50);\n\n/// RAII release of a single-flight wake claim (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP):\n/// the broker holds an endpoint id in [`Broker::wake_inflight`] for the duration of a\n/// spawn so a concurrent duplicate wake stands down; the guard releases it on EVERY\n/// exit (success, an early `?` spawn/drain error, or a panic), so a failed spawn never\n/// strands the endpoint permanently claimed.\nstruct WakeClaimGuard<'a> {\n    set: &'a Mutex<HashSet<String>>,\n    endpoint: String,\n}\n\nimpl Drop for WakeClaimGuard<'_> {\n    fn drop(&mut self) {\n        recover(self.set).remove(&self.endpoint);\n    }\n}\n\n/// The pure single-flight wake decision (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP), over\n/// the three observable inputs read together under the broker lock. `AlreadyLive` wins\n/// outright — the endpoint already has a live session, so a duplicate wake dedups to it\n/// (no spawn). Else a claim held by a racing spawn WITHIN the wait window is `Racing`\n/// (stand down + retry, waiting for the racer's session to register). Free — or a claim\n/// that OVERRAN the window (a leaked claim from a spawn presumed dead) — is `Claim`\n/// (proceed; availability over a vanishingly rare dup). Pure so the truth table is a unit.\n#[derive(Debug, PartialEq, Eq)]\nenum WakeGate {\n    AlreadyLive,\n    Racing,\n    Claim,\n}\n\n// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\nfn wake_gate_decision(live: bool, claimed: bool, past_deadline: bool) -> WakeGate {\n    if live {\n        WakeGate::AlreadyLive\n    } else if claimed && !past_deadline {\n        WakeGate::Racing\n    } else {\n        WakeGate::Claim\n    }\n}\n\n/// Whether a stall-evict re-stamps the perch INLINE (take/reattach path, already\n/// I/O-under-log-lock) or DEFERS the stamp to the off-lock convergence (the\n/// multi-session reap closure that forbids I/O under the shared sessions lock).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum StampMode {\n    Inline,\n    Deferred,\n}\n\n/// Broker-global tally of brain-subscriber stall-evicts (W2 observability rider,\n/// doyle ruling Q3): the count since broker boot + the wall-ms of the most recent.\n/// A brain that never returns is then a DIAGNOSABLE fact — surfaced on `daemon\n/// status` (via [`Broker::stall_evict_stats`]) alongside the per-evict broker log\n/// line, not a silent absence.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nstatic STALL_EVICT_COUNT: AtomicU64 = AtomicU64::new(0);\nstatic STALL_EVICT_LAST_MS: AtomicU64 = AtomicU64::new(0);\n\n/// Record one stall-evict in the broker-global tally (best-effort observability).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn record_stall_evict() {\n    STALL_EVICT_COUNT.fetch_add(1, Ordering::Relaxed);\n    STALL_EVICT_LAST_MS.store(crate::brain::now_ms(), Ordering::Relaxed);\n}\n\n/// The current stall-evict tally `(count_since_boot, last_evict_wall_ms)` —\n/// `last_evict_wall_ms` is 0 when none have happened. Read by the `daemon status`\n/// surface (the observability rider).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\npub(crate) fn stall_evict_stats() -> (u64, u64) {\n    (\n        STALL_EVICT_COUNT.load(Ordering::Relaxed),\n        STALL_EVICT_LAST_MS.load(Ordering::Relaxed),\n    )\n}\n\n/// MANDATORY bound on one injected keystroke sequence (W2 Layer C, ADR-0022 /\n/// doyle non-negotiable = W1's \"never park forever\"): the inject worker holds the\n/// floor — buffering operator input — only while a binary's response to one\n/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n/// buggy translation binary can therefore stall operator input for at most this\n/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\nconst INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n\n/// The effective commit deadline. Production is the const above; the int gate\n/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n/// so the structural outcome (floor releases → buffered input flushes → binary\n/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n/// the deadline's PRESENCE is the invariant, not its exact length.\nfn inject_commit_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_COMMIT_DEADLINE),\n        Err(_) => INJECT_COMMIT_DEADLINE,","truncated":true,"truncatedBy":"lines","totalLines":6298,"totalBytes":16796,"outputLines":300,"outputBytes":16796,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! The broker kernel (B1 + B2) — the stable half of the broker/brain split.\n//!\n//! Per ADR-0004 §B the broker holds **only** the resources a live consumer\n//! would lose continuity on if the logic restarted: the PTY master+writer and\n//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n//! client / listening sockets. It runs **no logic** — no routing, registry,\n//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n//! forward its output to the brain, inject the brain's input, report exit.\n//!\n//! ## Gapless handoff (B2 — the load-bearing invariant)\n//!\n//! A brain may be killed and restarted at any moment (a routine self-update, a\n//! crash). The broker — and every hosted child — must survive that untouched,\n//! and the new brain must re-attach to the output stream **gaplessly and\n//! exactly-once** (Spike #1 made real). Two mechanisms:\n//!\n//! - **Sequenced, buffered output.** Each session's output is appended to an\n//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held\n//!   in a bounded ring. The current subscriber (a brain connection) receives\n//!   chunks live; all subscriber writes happen *under the log lock*, so frames\n//!   reach the brain in strict `seq` order with no interleave.\n//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its\n//!   last-seen `seq + 1`; the broker replays the ring from there, then streams\n//!   live. The brain dedups by `seq`. Output produced *while no brain was\n//!   attached* sits in the ring and is replayed on the next subscribe — so a\n//!   brain restart loses nothing.\n//!\n//! Sessions are therefore **broker-owned and outlive any one connection**: a\n//! brain disconnect only detaches the subscriber; the child, its PTY, and the\n//! output log persist. A session leaves the table only when its child exits\n//! (reaped by the exit waiter) or is explicitly killed.\n// [impl->REQ-DAEMON-2]\n\nuse std::collections::{HashMap, HashSet, VecDeque};\nuse std::io;\nuse std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};\nuse std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};\nuse std::sync::{Arc, Mutex};\nuse std::thread::{self, JoinHandle};\nuse std::time::{Duration, Instant};\n\nuse interprocess::local_socket::{prelude::*, Stream};\nuse spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n\nuse spt_net::net::attach::AttachIntent;\nuse spt_net::net::endpoint::EndpointAddr;\nuse spt_store::perch::{resolve_perch_path, ParentHint};\n\nuse crate::codec::read_frame;\nuse crate::conn::{BrokerConn, ConnWrite};\nuse crate::effect::{EffectJournal, EffectKey, EffectKind, Minter, Outcome};\nuse crate::frame::{Envelope, Role};\nuse crate::msg::{\n    applied_envelope, decode_bytes, displaced_envelope, endpoint_injected_envelope,\n    evicted_envelope, net_dialed_envelope, output_envelope,\n    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,\n    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,\n    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,\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_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, 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]\nfn brain_write_deadline() -> Duration {\n    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(BRAIN_WRITE_DEADLINE),\n        Err(_) => BRAIN_WRITE_DEADLINE,\n    }\n}\n\n/// Bounded wait for a concurrent wake's session to register before a second wake for\n/// the same endpoint stops deduping and spawns itself (W4, REQ-SPAWN-COLLISION-GUARD-\n/// LIVE-DUP). A racing broker spawn registers its session in PTY-spawn time (ms), so a\n/// loser waits for the winner's session then no-op acks it (exactly one launch tree);\n/// only a spawn that OVERRAN this ceiling (presumed dead, its claim leaked) is taken\n/// over — availability over a vanishingly rare dup.\nconst WAKE_DEDUP_WAIT: Duration = Duration::from_secs(2);\n/// Poll slice while waiting on a racing wake's session to appear.\nconst WAKE_DEDUP_POLL: Duration = Duration::from_millis(50);\n\n/// RAII release of a single-flight wake claim (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP):\n/// the broker holds an endpoint id in [`Broker::wake_inflight`] for the duration of a\n/// spawn so a concurrent duplicate wake stands down; the guard releases it on EVERY\n/// exit (success, an early `?` spawn/drain error, or a panic), so a failed spawn never\n/// strands the endpoint permanently claimed.\nstruct WakeClaimGuard<'a> {\n    set: &'a Mutex<HashSet<String>>,\n    endpoint: String,\n}\n\nimpl Drop for WakeClaimGuard<'_> {\n    fn drop(&mut self) {\n        recover(self.set).remove(&self.endpoint);\n    }\n}\n\n/// The pure single-flight wake decision (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP), over\n/// the three observable inputs read together under the broker lock. `AlreadyLive` wins\n/// outright — the endpoint already has a live session, so a duplicate wake dedups to it\n/// (no spawn). Else a claim held by a racing spawn WITHIN the wait window is `Racing`\n/// (stand down + retry, waiting for the racer's session to register). Free — or a claim\n/// that OVERRAN the window (a leaked claim from a spawn presumed dead) — is `Claim`\n/// (proceed; availability over a vanishingly rare dup). Pure so the truth table is a unit.\n#[derive(Debug, PartialEq, Eq)]\nenum WakeGate {\n    AlreadyLive,\n    Racing,\n    Claim,\n}\n\n// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\nfn wake_gate_decision(live: bool, claimed: bool, past_deadline: bool) -> WakeGate {\n    if live {\n        WakeGate::AlreadyLive\n    } else if claimed && !past_deadline {\n        WakeGate::Racing\n    } else {\n        WakeGate::Claim\n    }\n}\n\n/// Whether a stall-evict re-stamps the perch INLINE (take/reattach path, already\n/// I/O-under-log-lock) or DEFERS the stamp to the off-lock convergence (the\n/// multi-session reap closure that forbids I/O under the shared sessions lock).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum StampMode {\n    Inline,\n    Deferred,\n}\n\n/// Broker-global tally of brain-subscriber stall-evicts (W2 observability rider,\n/// doyle ruling Q3): the count since broker boot + the wall-ms of the most recent.\n/// A brain that never returns is then a DIAGNOSABLE fact — surfaced on `daemon\n/// status` (via [`Broker::stall_evict_stats`]) alongside the per-evict broker log\n/// line, not a silent absence.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nstatic STALL_EVICT_COUNT: AtomicU64 = AtomicU64::new(0);\nstatic STALL_EVICT_LAST_MS: AtomicU64 = AtomicU64::new(0);\n\n/// Record one stall-evict in the broker-global tally (best-effort observability).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\nfn record_stall_evict() {\n    STALL_EVICT_COUNT.fetch_add(1, Ordering::Relaxed);\n    STALL_EVICT_LAST_MS.store(crate::brain::now_ms(), Ordering::Relaxed);\n}\n\n/// The current stall-evict tally `(count_since_boot, last_evict_wall_ms)` —\n/// `last_evict_wall_ms` is 0 when none have happened. Read by the `daemon status`\n/// surface (the observability rider).\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\npub(crate) fn stall_evict_stats() -> (u64, u64) {\n    (\n        STALL_EVICT_COUNT.load(Ordering::Relaxed),\n        STALL_EVICT_LAST_MS.load(Ordering::Relaxed),\n    )\n}\n\n/// MANDATORY bound on one injected keystroke sequence (W2 Layer C, ADR-0022 /\n/// doyle non-negotiable = W1's \"never park forever\"): the inject worker holds the\n/// floor — buffering operator input — only while a binary's response to one\n/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n/// buggy translation binary can therefore stall operator input for at most this\n/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\nconst INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n\n/// The effective commit deadline. Production is the const above; the int gate\n/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n/// so the structural outcome (floor releases → buffered input flushes → binary\n/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n/// the deadline's PRESENCE is the invariant, not its exact length.\nfn inject_commit_deadline() -> Duration {\n    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n        Ok(ms) => ms\n            .parse::<u64>()\n            .map(Duration::from_millis)\n            .unwrap_or(INJECT_COMMIT_DEADLINE),\n        Err(_) => INJECT_COMMIT_DEADLINE,\n    }\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":16796,"outputLines":300,"outputBytes":16796,"shownRange":{"start":1,"end":300},"nextOffset":301}}},"isError":false,"timestamp":1784271501050}}
{"type":"message","id":"a5bff58e","parentId":"d41299b0","timestamp":"2026-07-17T06:58:35.061Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning inspection of dispatch core loop**"},{"type":"toolCall","id":"call_tH7BpsRJnDPKyJdOxcJjAl0Q|fc_089095e18118ee7d016a59d29af784819098f11968a31f12cf","name":"grep","arguments":{"i":"Locating dispatcher loop","pattern":"fn should_claim|fn dispatch_worker|run_dispatch_loop|thread::spawn|net_streams|net_stream_retire|ClaimState::|CircuitBreaker","path":"crates/spt-daemon/src/dispatch.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_nNvi1MnjF60RKQw4K3yY83sS|fc_089095e18118ee7d016a59d29af79c8190958f782549f129f9","name":"grep","arguments":{"i":"Locating stream lifecycle implementation","pattern":"struct StreamLog|struct StreamSubscriber|fn attach|fn append|fn finish|fn retire|retire_stream|stream_opener|streams\\(|thread::spawn|subscriber busy|replay halted|lease","path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/brain.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_bbkjZqRzRA5rRdvFcBPBh5Ud|fc_089095e18118ee7d016a59d29af7a88190bcf558df05810e10","name":"grep","arguments":{"i":"Locating regression assertions","pattern":"fn (finished|active|transient|wedged|opener|poisoned|seat|stale|old_broker)|DISPATCH_EV|attempt|replay|thread|net_streams|retir","path":"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_5h2Ku2UCcbGaa51ohkVFQnQ6|fc_089095e18118ee7d016a59d29af7b481908ba30fa3d27eed2b","name":"grep","arguments":{"i":"Locating release documentation","pattern":"REDISPATCH|0\\.36\\.0|redispatch","path":"CHANGELOG.md; docs; .planning","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_EU1MEDBYEFfKGqUbbUV1XwD9|fc_089095e18118ee7d016a59d29af7bc8190a31e8fd852ecf219","name":"read","arguments":{"i":"Reading current branch history","path":".git/logs/HEAD","selector":"raw:1-200"}},{"type":"toolCall","id":"call_6fubdBDszyhMA3tEQtWxgT59|fc_089095e18118ee7d016a59d29af7c881909552d8674abc309e","name":"read","arguments":{"i":"Reading redispatch build ref","path":".git/refs/remotes/origin/build/redispatch-stall-w1","selector":"raw"}},{"type":"toolCall","id":"call_1pMdvJHFlcC1FPO6zkixvVIr|fc_089095e18118ee7d016a59d29af7d08190be913aa10bdf395c","name":"read","arguments":{"i":"Reading release tag ref","path":".git/refs/tags/v0.36.0","selector":"raw"}},{"type":"toolCall","id":"call_SqZlTA1bWovXSq1PRi9Itw1N|fc_089095e18118ee7d016a59d29af804819098f92ddeefc20dce","name":"read","arguments":{"i":"Reading main ref","path":".git/refs/heads/main","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":22110,"output":571,"cacheRead":17920,"cacheWrite":0,"totalTokens":40601,"reasoningTokens":47,"cost":{"input":0.11055000000000001,"output":0.01713,"cacheRead":0.00896,"cacheWrite":0,"total":0.13664}},"stopReason":"toolUse","timestamp":1784271501097,"responseId":"resp_089095e18118ee7d016a59d28d579481909b7ff31f8e3a3fe2","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2909dc08190a1f7bf8841fb39bc","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdKaDxy812XQAiipxgd7rW_CivhfDwxrJGVa0_MsorzHOpbTbmNalPev5w9BkjnCyRSYbOt0JWPDvR4dzKNA9O7t8wrGDo3p8zKaiDFFOJBl7Sr1HYxImWTK5IY5NrGX8qqFW0-_h3dq45QrpEoTqtdLQGURfzLjKMakf-xEzdP1WgzDyJELN9Z85nuDTxE112iP7mbVBOv5DGRhmW2AkNqTBnO7oxl5MqeGuCndhskEcAXIbMXz00eLylHixISS3bE4CqRbNFIp74KrjqywFosr5eIaMyNZif80zoI-pdpSy1yG8cJCHalltKDNj3J8GTpqG2tcNQzFgFdacSEYYW8eNbFd_n1SuegTOZ3lUq1R2f_-D0o1WF6uteYZQ-tnO_sCFK6CbTWDUOtt97v5V5m-p33fEZIu-KrzSY60kzzpqE6O5cTtSQ3YGwACPWc-zUIhU6B8_rUoO-a7iXTfkV2XkT-D4hIhUNYppETGYS5zSXK7vnfOYVfQfk-q6mP0ugZUUAyNcz6Ss9BYqTPQJoq1yrf0KBdLT4eAVyrdXqpe6vNimxs5-AkcKe2Tjk6I9RKrTgP0JPu1176YS8R7nb81MxWzYpI3JMzGdB5MJo97PvTKnAiXHPME0aoalPh75s4DezLDAFxaWKvHr8ayoCiS1rmO2qApaxeyTAfdG99e1LStrJ9PWVXSuedetGJ_u6guUjclDo7t-CDDBOmFrWI8ht5b_IrrLCf78VJlusmccrUF7nfEwdx2nHmde5BN4bMeOHt97xmEURZ0j678TG_0aXXVXb702k5_Q6QPxFXsDwXQatOEpxfa-WZYo1J77ginCNDY-LlgQ6Jm0OlAdYckMvVEEaNUp5FBNrpONlp-O_jcEGOc6zArUUc7VsPii7674Sgc4wczqGiPTY8lyBeuOiESPXXG1GjaL4dhfcfnY6OuUzzO5b18lUQcCJCfNokavxC7hXcdC8l69zCg5vEd_Hd6s2Cuz3oW95qpcmcT05tIPkhZfzJyQO-65HgZ8I9SaW2ZeMxxU5uQXpTVAQ2W7oXhc4YriRUCEsYY7ZyuO779zOLne6FpdOWuD0pX_zJMHM_BXOOIndKbWSZj0a2tZp1-wmDVkMOD7VSXigVbKDeoSxlI4zLFmi32GntDS75Ea0O91kVhI9r1fUOoMwfVqQt6c_BHjeOsSMHf2k4y_fJwxXKBXiE2p_OLu-FVqe6G","summary":[{"type":"summary_text","text":"**Planning inspection of dispatch core loop**"}]},{"id":"fc_089095e18118ee7d016a59d29af784819098f11968a31f12cf","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating dispatcher loop\",\"pattern\":\"fn should_claim|fn dispatch_worker|run_dispatch_loop|thread::spawn|net_streams|net_stream_retire|ClaimState::|CircuitBreaker\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_tH7BpsRJnDPKyJdOxcJjAl0Q","name":"grep"},{"id":"fc_089095e18118ee7d016a59d29af79c8190958f782549f129f9","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating stream lifecycle implementation\",\"pattern\":\"struct StreamLog|struct StreamSubscriber|fn attach|fn append|fn finish|fn retire|retire_stream|stream_opener|streams\\\\(|thread::spawn|subscriber busy|replay halted|lease\",\"path\":\"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/brain.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_nNvi1MnjF60RKQw4K3yY83sS","name":"grep"},{"id":"fc_089095e18118ee7d016a59d29af7a88190bcf558df05810e10","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating regression assertions\",\"pattern\":\"fn (finished|active|transient|wedged|opener|poisoned|seat|stale|old_broker)|DISPATCH_EV|attempt|replay|thread|net_streams|retir\",\"path\":\"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_bbkjZqRzRA5rRdvFcBPBh5Ud","name":"grep"},{"id":"fc_089095e18118ee7d016a59d29af7b481908ba30fa3d27eed2b","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating release documentation\",\"pattern\":\"REDISPATCH|0\\\\.36\\\\.0|redispatch\",\"path\":\"CHANGELOG.md; docs; .planning\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_5h2Ku2UCcbGaa51ohkVFQnQ6","name":"grep"},{"id":"fc_089095e18118ee7d016a59d29af7bc8190a31e8fd852ecf219","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading current branch history\",\"path\":\".git/logs/HEAD\",\"selector\":\"raw:1-200\"}","call_id":"call_EU1MEDBYEFfKGqUbbUV1XwD9","name":"read"},{"id":"fc_089095e18118ee7d016a59d29af7c881909552d8674abc309e","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch build ref\",\"path\":\".git/refs/remotes/origin/build/redispatch-stall-w1\",\"selector\":\"raw\"}","call_id":"call_6fubdBDszyhMA3tEQtWxgT59","name":"read"},{"id":"fc_089095e18118ee7d016a59d29af7d08190be913aa10bdf395c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release tag ref\",\"path\":\".git/refs/tags/v0.36.0\",\"selector\":\"raw\"}","call_id":"call_1pMdvJHFlcC1FPO6zkixvVIr","name":"read"},{"id":"fc_089095e18118ee7d016a59d29af804819098f92ddeefc20dce","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading main ref\",\"path\":\".git/refs/heads/main\",\"selector\":\"raw\"}","call_id":"call_SqZlTA1bWovXSq1PRi9Itw1N","name":"read"}]},"duration":13962.720200002193,"ttft":3530.9227000027895,"contextSnapshot":{"promptTokens":40030,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tH7BpsRJnDPKyJdOxcJjAl0Q|fc_089095e18118ee7d016a59d29af784819098f11968a31f12cf","toolName":"grep","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Locating dispatcher loop"},"id":"cb948330","parentId":"a5bff58e","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_nNvi1MnjF60RKQw4K3yY83sS|fc_089095e18118ee7d016a59d29af79c8190958f782549f129f9","toolName":"grep","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/brain.rs"},"intent":"Locating stream lifecycle implementation"},"id":"2aa1bdf4","parentId":"cb948330","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bbkjZqRzRA5rRdvFcBPBh5Ud|fc_089095e18118ee7d016a59d29af7a88190bcf558df05810e10","toolName":"grep","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs"},"intent":"Locating regression assertions"},"id":"36a6b0f6","parentId":"2aa1bdf4","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5h2Ku2UCcbGaa51ohkVFQnQ6|fc_089095e18118ee7d016a59d29af7b481908ba30fa3d27eed2b","toolName":"grep","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":"CHANGELOG.md; docs; .planning"},"intent":"Locating release documentation"},"id":"62cf757b","parentId":"36a6b0f6","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_EU1MEDBYEFfKGqUbbUV1XwD9|fc_089095e18118ee7d016a59d29af7bc8190a31e8fd852ecf219","toolName":"read","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":".git/logs/HEAD"},"intent":"Reading current branch history"},"id":"2c8382c6","parentId":"62cf757b","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6fubdBDszyhMA3tEQtWxgT59|fc_089095e18118ee7d016a59d29af7c881909552d8674abc309e","toolName":"read","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":".git/refs/remotes/origin/build/redispatch-stall-w1"},"intent":"Reading redispatch build ref"},"id":"4e77a57a","parentId":"2c8382c6","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_1pMdvJHFlcC1FPO6zkixvVIr|fc_089095e18118ee7d016a59d29af7d08190be913aa10bdf395c","toolName":"read","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":".git/refs/tags/v0.36.0"},"intent":"Reading release tag ref"},"id":"ff427799","parentId":"4e77a57a","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_SqZlTA1bWovXSq1PRi9Itw1N|fc_089095e18118ee7d016a59d29af804819098f92ddeefc20dce","toolName":"read","startedAt":"2026-07-17T06:58:35.062Z","args":{"path":".git/refs/heads/main"},"intent":"Reading main ref"},"id":"f83dfd23","parentId":"ff427799","timestamp":"2026-07-17T06:58:35.062Z"}
{"type":"message","id":"30ed7862","parentId":"f83dfd23","timestamp":"2026-07-17T06:58:35.069Z","message":{"role":"toolResult","toolCallId":"call_tH7BpsRJnDPKyJdOxcJjAl0Q|fc_089095e18118ee7d016a59d29af784819098f11968a31f12cf","toolName":"grep","content":[{"type":"text","text":" 246|#[derive(Debug, Default)]\n*247|struct CircuitBreaker {\n 248|    until: Option<Instant>,\n 249|    consecutive: u32,\n 250|}\n...\n 254|\n*255|impl CircuitBreaker {\n 256|    /// A seat-blocked failure landed: open (or extend) the window.\n 257|    fn trip(&mut self, now: Instant) -> Duration {\n 258|        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n...\n 299|// [impl->REQ-DISPATCH-CLAIM-RETRY]\n*300|fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n 301|    match state {\n 302|        None => Some(0),\n*303|        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n 304|        _ => None,\n 305|    }\n 306|}\n...\n 324|    match outcome {\n*325|        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n 326|        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n*327|            FailClass::SeatBlocked => ClaimState::Retry {\n 328|                attempts,\n 329|                next_at: now + breaker_window,\n 330|            },\n...\n 333|                if attempts >= MAX_DISPATCH_ATTEMPTS {\n*334|                    ClaimState::Terminal\n 335|                } else {\n*336|                    ClaimState::Retry {\n 337|                        attempts,\n 338|                        next_at: now + retry_backoff(attempts),\n 339|                    }\n...\n 428|/// stream).\n*429|pub fn run_dispatch_loop(\n 430|    broker_name: &str,\n 431|    paths: &DispatchPaths,\n 432|    registry: Arc<RegistryHost>,\n...\n 445|    // with workers so a seat-blocked retry paces on it.\n*446|    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n 447|    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n 448|    // with a large backlog) claims at most this many rows per poll — the\n 449|    // rest wait for the next tick, batching the cold enumeration instead of\n...\n 462|        }\n*463|        let reply = brain.net_streams()?;\n 464|        for s in reply.streams {\n 465|            // Claim only peer-initiated rows with bytes (a QUIC stream is\n 466|            // invisible until its first write anyway — gotcha #6 — but the\n...\n 479|                    Some(attempts) => {\n*480|                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n 481|                        attempts\n 482|                    }\n 483|                    None => continue,\n...\n 497|            pool.fetch_add(1, Ordering::Relaxed);\n*498|            thread::spawn(move || {\n 499|                let started = Instant::now();\n 500|                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n 501|                pool.fetch_sub(1, Ordering::Relaxed);\n...\n 511|                    if let Ok(mut retire_brain) = connect(&name) {\n*512|                        let _ = retire_brain.net_stream_retire(stream_id);\n 513|                    }\n 514|                    // Breaker reset = REAL progress only (gate round 1): a row\n 515|                    // that vanished before serve proves nothing about the\n...\n 1201|) -> thread::JoinHandle<()> {\n*1202|    thread::spawn(move || {\n 1203|        let paths = DispatchPaths::production();\n 1204|        if let Err(e) =\n*1205|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206|        {\n 1207|            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208|        }\n...\n 1282|        // In flight / terminal → never.\n*1283|        assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);\n*1284|        assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);\n 1285|\n 1286|        // Failure #1 releases with backoff — NOT claimable before next_at,\n 1287|        // claimable at/after it, carrying the attempt count.\n...\n 1289|        let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);\n*1290|        let ClaimState::Retry { attempts: 1, next_at } = s1 else {\n 1291|            panic!(\"failure #1 must requeue, got {s1:?}\");\n 1292|        };\n 1293|        assert_eq!(next_at, now + retry_backoff(1));\n...\n 1302|        let s2 = outcome_transition(&failed, 1, now, Duration::ZERO);\n*1303|        assert!(matches!(s2, ClaimState::Retry { attempts: 2, .. }));\n 1304|        let s3 = outcome_transition(&failed, 2, now, Duration::ZERO);\n*1305|        assert_eq!(s3, ClaimState::Terminal, \"attempt {MAX_DISPATCH_ATTEMPTS} exhausts the budget\");\n 1306|\n 1307|        // Terminal CLASSIFICATION outcomes are terminal immediately — a\n 1308|        // served exchange and an unclassifiable stream never retry.\n*1309|        assert_eq!(outcome_transition(&DispatchOutcome::Served(\"ok\".into()), 0, now, Duration::ZERO), ClaimState::Terminal);\n*1310|        assert_eq!(outcome_transition(&DispatchOutcome::Unknown, 0, now, Duration::ZERO), ClaimState::Terminal);\n 1311|    }\n 1312|\n 1313|    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:\n...\n 1379|        let now = Instant::now();\n*1380|        let mut b = CircuitBreaker::default();\n 1381|        assert!(!b.open(now), \"a fresh breaker is closed\");\n 1382|\n 1383|        // Trip #1: base window; open inside, closed at its end.\n...\n 1421|            s,\n*1422|            ClaimState::Retry {\n 1423|                attempts: MAX_DISPATCH_ATTEMPTS - 1,\n 1424|                next_at: now + window\n 1425|            },\n...\n 1434|            outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),\n*1435|            ClaimState::Terminal,\n 1436|            \"a transient at the budget edge still exhausts (D3 unchanged)\"\n 1437|        );\n 1438|    }"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":26,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":26}],"truncated":false,"displayContent":"  246│#[derive(Debug, Default)]\n *247│struct CircuitBreaker {\n  248│    until: Option<Instant>,\n  249│    consecutive: u32,\n  250│}\n     │...\n  254│\n *255│impl CircuitBreaker {\n  256│    /// A seat-blocked failure landed: open (or extend) the window.\n  257│    fn trip(&mut self, now: Instant) -> Duration {\n  258│        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n     │...\n  299│// [impl->REQ-DISPATCH-CLAIM-RETRY]\n *300│fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n  301│    match state {\n  302│        None => Some(0),\n *303│        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n  304│        _ => None,\n  305│    }\n  306│}\n     │...\n  324│    match outcome {\n *325│        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n  326│        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n *327│            FailClass::SeatBlocked => ClaimState::Retry {\n  328│                attempts,\n  329│                next_at: now + breaker_window,\n  330│            },\n     │...\n  333│                if attempts >= MAX_DISPATCH_ATTEMPTS {\n *334│                    ClaimState::Terminal\n  335│                } else {\n *336│                    ClaimState::Retry {\n  337│                        attempts,\n  338│                        next_at: now + retry_backoff(attempts),\n  339│                    }\n     │...\n  428│/// stream).\n *429│pub fn run_dispatch_loop(\n  430│    broker_name: &str,\n  431│    paths: &DispatchPaths,\n  432│    registry: Arc<RegistryHost>,\n     │...\n  445│    // with workers so a seat-blocked retry paces on it.\n *446│    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n  447│    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n  448│    // with a large backlog) claims at most this many rows per poll — the\n  449│    // rest wait for the next tick, batching the cold enumeration instead of\n     │...\n  462│        }\n *463│        let reply = brain.net_streams()?;\n  464│        for s in reply.streams {\n  465│            // Claim only peer-initiated rows with bytes (a QUIC stream is\n  466│            // invisible until its first write anyway — gotcha #6 — but the\n     │...\n  479│                    Some(attempts) => {\n *480│                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n  481│                        attempts\n  482│                    }\n  483│                    None => continue,\n     │...\n  497│            pool.fetch_add(1, Ordering::Relaxed);\n *498│            thread::spawn(move || {\n  499│                let started = Instant::now();\n  500│                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n  501│                pool.fetch_sub(1, Ordering::Relaxed);\n     │...\n  511│                    if let Ok(mut retire_brain) = connect(&name) {\n *512│                        let _ = retire_brain.net_stream_retire(stream_id);\n  513│                    }\n  514│                    // Breaker reset = REAL progress only (gate round 1): a row\n  515│                    // that vanished before serve proves nothing about the\n     │...\n 1201│) -> thread::JoinHandle<()> {\n*1202│    thread::spawn(move || {\n 1203│        let paths = DispatchPaths::production();\n 1204│        if let Err(e) =\n*1205│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206│        {\n 1207│            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208│        }\n     │...\n 1282│        // In flight / terminal → never.\n*1283│        assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);\n*1284│        assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);\n 1285│\n 1286│        // Failure #1 releases with backoff — NOT claimable before next_at,\n 1287│        // claimable at/after it, carrying the attempt count.\n     │...\n 1289│        let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);\n*1290│        let ClaimState::Retry { attempts: 1, next_at } = s1 else {\n 1291│            panic!(\"failure #1 must requeue, got {s1:?}\");\n 1292│        };\n 1293│        assert_eq!(next_at, now + retry_backoff(1));\n     │...\n 1302│        let s2 = outcome_transition(&failed, 1, now, Duration::ZERO);\n*1303│        assert!(matches!(s2, ClaimState::Retry { attempts: 2, .. }));\n 1304│        let s3 = outcome_transition(&failed, 2, now, Duration::ZERO);\n*1305│        assert_eq!(s3, ClaimState::Terminal, \"attempt {MAX_DISPATCH_ATTEMPTS} exhausts the budget\");\n 1306│\n 1307│        // Terminal CLASSIFICATION outcomes are terminal immediately — a\n 1308│        // served exchange and an unclassifiable stream never retry.\n*1309│        assert_eq!(outcome_transition(&DispatchOutcome::Served(\"ok\".into()), 0, now, Duration::ZERO), ClaimState::Terminal);\n*1310│        assert_eq!(outcome_transition(&DispatchOutcome::Unknown, 0, now, Duration::ZERO), ClaimState::Terminal);\n 1311│    }\n 1312│\n 1313│    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:\n     │...\n 1379│        let now = Instant::now();\n*1380│        let mut b = CircuitBreaker::default();\n 1381│        assert!(!b.open(now), \"a fresh breaker is closed\");\n 1382│\n 1383│        // Trip #1: base window; open inside, closed at its end.\n     │...\n 1421│            s,\n*1422│            ClaimState::Retry {\n 1423│                attempts: MAX_DISPATCH_ATTEMPTS - 1,\n 1424│                next_at: now + window\n 1425│            },\n     │...\n 1434│            outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),\n*1435│            ClaimState::Terminal,\n 1436│            \"a transient at the budget edge still exhausts (D3 unchanged)\"\n 1437│        );\n 1438│    }"},"isError":false,"timestamp":1784271515069}}
{"type":"message","id":"0175e39b","parentId":"30ed7862","timestamp":"2026-07-17T06:58:35.071Z","message":{"role":"toolResult","toolCallId":"call_SqZlTA1bWovXSq1PRi9Itw1N|fc_089095e18118ee7d016a59d29af804819098f92ddeefc20dce","toolName":"read","content":[{"type":"text","text":"fa3e474783a6e3ebbb178a478f03e4ebc8ce3928\n"}],"details":{"displayContent":{"text":"fa3e474783a6e3ebbb178a478f03e4ebc8ce3928\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\heads\\main"}}},"isError":false,"timestamp":1784271515071}}
{"type":"message","id":"1b50af3a","parentId":"0175e39b","timestamp":"2026-07-17T06:58:35.073Z","message":{"role":"toolResult","toolCallId":"call_1pMdvJHFlcC1FPO6zkixvVIr|fc_089095e18118ee7d016a59d29af7d08190be913aa10bdf395c","toolName":"read","content":[{"type":"text","text":"fa3e474783a6e3ebbb178a478f03e4ebc8ce3928\n"}],"details":{"displayContent":{"text":"fa3e474783a6e3ebbb178a478f03e4ebc8ce3928\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.36.0"}}},"isError":false,"timestamp":1784271515073}}
{"type":"message","id":"309ec464","parentId":"1b50af3a","timestamp":"2026-07-17T06:58:35.074Z","message":{"role":"toolResult","toolCallId":"call_6fubdBDszyhMA3tEQtWxgT59|fc_089095e18118ee7d016a59d29af7c881909552d8674abc309e","toolName":"read","content":[{"type":"text","text":"73b29b858bbffb2c758382c80d74def0aabb328f\n"}],"details":{"displayContent":{"text":"73b29b858bbffb2c758382c80d74def0aabb328f\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\remotes\\origin\\build\\redispatch-stall-w1"}}},"isError":false,"timestamp":1784271515074}}
{"type":"message","id":"7f35deca","parentId":"309ec464","timestamp":"2026-07-17T06:58:35.085Z","message":{"role":"toolResult","toolCallId":"call_bbkjZqRzRA5rRdvFcBPBh5Ud|fc_089095e18118ee7d016a59d29af7a88190bcf558df05810e10","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## redispatch.rs\n 12|//!   historical stream neither takes nor clears the current controller\n*13|//!   (post-restart input still renders; no EOF; no replay-storm log line;\n*14|//!   the retired row left the broker's enumeration).\n 15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n 16|//!   restart the dispatcher → durable classification (the pinned opener)\n 17|//!   resumes the SAME operator stream.\n...\n 27|use std::sync::{Arc, OnceLock};\n*28|use std::thread;\n 29|use std::time::{Duration, Instant};\n 30|\n 31|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 73|    let serve = Arc::clone(&broker);\n*74|    thread::spawn(move || {\n 75|        let _ = serve.serve();\n 76|    });\n 77|    broker\n...\n 84|        }\n*85|        thread::sleep(Duration::from_millis(10));\n 86|    }\n 87|    panic!(\"brain could not connect\");\n 88|}\n...\n 110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n*111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112|struct Generation {\n 113|    child: Child,\n 114|    log: std::path::PathBuf,\n...\n 144|    probe\n*145|        .net_streams()\n 146|        .expect(\"net-streams\")\n 147|        .streams\n 148|        .iter()\n...\n 153|\n*154|/// Render the operator's viewport (dedup `Output` by seq — replays after a\n 155|/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156|/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157|/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n...\n 187|                        if seq < *cursor {\n*188|                            continue; // replay duplicate — dedup at the render cursor\n 189|                        }\n 190|                        *cursor = seq + 1;\n 191|                        buf.extend_from_slice(\n...\n 216|// controller: post-restart input still renders (the frozen-PTY symptom is\n*217|// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218|// clear), the fresh generation logs NO replay of the historical stream id,\n*219|// and the retired row is GONE from the broker's enumeration\n*220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n*222|fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223|    init_home();\n 224|    let dir = tempfile::tempdir().expect(\"tempdir\");\n 225|    let (name_a, name_b) = (unique_name(), unique_name());\n...\n 251|\n*252|    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n*253|    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254|    let end = Instant::now() + Duration::from_secs(20);\n 255|    while peer_stream_ids(&mut probe).contains(&s1_a) {\n*256|        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n*257|        thread::sleep(Duration::from_millis(50));\n 258|    }\n 259|\n 260|    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n...\n 276|    // reconstruction re-took the controller (successor re-take parity), and\n*277|    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n 278|    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n 279|    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n 280|    assert!(\n 281|        !eof_post,\n*282|        \"the current controller's stream saw an EOF across the restart — a replayed \\\n 283|         historical detach cleared it (the 7.41 steal)\"\n 284|    );\n 285|    let painted = screen_of(&view2);\n## redispatch_stall.rs\n 4|//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n*5|//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n 6|//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n 7|//! windows (33 observed, all 15,000-15,154ms).\n 8|//!\n...\n 10|//! through the production broker subscribe path — gate ruling 1):\n*11|//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n 12|//!     the first failed write; the producer stays FLAT (never a serial poison\n*13|//!     composition); recovery replays gapless from the cursor.\n 14|//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n 15|//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n*16|//!     subscribed + replayed against the conn).\n 17|//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n 18|//!     stream's serve lease — the paired forwarding leg terminates at its\n 19|//!     next send; a new subscriber generation renews and serves again.\n...\n 23|//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n*24|//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n*25|//!     see) replayed by a fresh generation neither takes nor clears the live\n 26|//!     replacement controller (ownership/generation validation).\n 27|//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n 28|//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n...\n 32|//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n*33|//! binary alongside — the retire/steal regressions are substrate).\n 34|//!\n 35|//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n 36|//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n 37|//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n*38|//! `--test-threads=1` — tests set process-wide env knobs\n 39|//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n 40|\n 41|use std::process::{Child, Command, Stdio};\n...\n 43|use std::sync::{Arc, OnceLock};\n*44|use std::thread;\n 45|use std::time::{Duration, Instant};\n 46|\n 47|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 87|    let serve = Arc::clone(&broker);\n*88|    thread::spawn(move || {\n 89|        let _ = serve.serve();\n 90|    });\n 91|    broker\n...\n 98|        }\n*99|        thread::sleep(Duration::from_millis(10));\n 100|    }\n 101|    panic!(\"brain could not connect\");\n 102|}\n...\n 158|    probe\n*159|        .net_streams()\n 160|        .expect(\"net-streams\")\n 161|        .streams\n 162|        .iter()\n...\n 247|        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n*248|        thread::sleep(Duration::from_millis(25));\n 249|    };\n 250|    (operator, opened.stream_id, sid_a)\n 251|}\n...\n 254|/// no-reinstall drain window (\"subscriber busy\" surfaces as an Error event),\n*255|/// then accumulate the replay until `want_bytes` bytes arrived. Asserts DENSE\n 256|/// seq contiguity from 0 (gapless) and returns the bytes.\n 257|fn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n 258|    let end = Instant::now() + deadline;\n...\n 277|                        seq, next,\n*278|                        \"the recovery replay must be DENSE from 0 — a gap means the \\\n 279|                         overflow/poison lost retained bytes\"\n 280|                    );\n 281|                    next += 1;\n...\n 294|                    );\n*295|                    thread::sleep(Duration::from_millis(200));\n 296|                    continue 'attach;\n 297|                }\n 298|                Ok(_) => {}\n...\n 310|// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*311|// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n 312|// production broker subscribe path: a REAL subscriber conn that attaches with\n*313|// a full ring and never reads — its seat's replay wedges on the named-pipe\n 314|// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat\n 315|// is removed, later appends never re-feed it, and the PRODUCER — B's ack'd\n*316|// sends through A's read pump — stays flat throughout (pre-fix: the replay\n 317|// ran under the stream mutex with discarded errors, one 400ms poison window\n 318|// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh\n*319|// subscriber replays everything gapless from the cursor — no abandonment.\n 320|#[test]"}],"details":{"scopePath":"crates/spt-daemon/tests/redispatch.rs, crates/spt-daemon/tests/redispatch_stall.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":40,"fileCount":2,"files":["crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/redispatch.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/tests/\n## redispatch.rs\n  12│//!   historical stream neither takes nor clears the current controller\n *13│//!   (post-restart input still renders; no EOF; no replay-storm log line;\n *14│//!   the retired row left the broker's enumeration).\n  15│//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n  16│//!   restart the dispatcher → durable classification (the pinned opener)\n  17│//!   resumes the SAME operator stream.\n    │...\n  27│use std::sync::{Arc, OnceLock};\n *28│use std::thread;\n  29│use std::time::{Duration, Instant};\n  30│\n  31│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  73│    let serve = Arc::clone(&broker);\n *74│    thread::spawn(move || {\n  75│        let _ = serve.serve();\n  76│    });\n  77│    broker\n    │...\n  84│        }\n *85│        thread::sleep(Duration::from_millis(10));\n  86│    }\n  87│    panic!(\"brain could not connect\");\n  88│}\n    │...\n 110│/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n*111│/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112│struct Generation {\n 113│    child: Child,\n 114│    log: std::path::PathBuf,\n    │...\n 144│    probe\n*145│        .net_streams()\n 146│        .expect(\"net-streams\")\n 147│        .streams\n 148│        .iter()\n    │...\n 153│\n*154│/// Render the operator's viewport (dedup `Output` by seq — replays after a\n 155│/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156│/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157│/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n    │...\n 187│                        if seq < *cursor {\n*188│                            continue; // replay duplicate — dedup at the render cursor\n 189│                        }\n 190│                        *cursor = seq + 1;\n 191│                        buf.extend_from_slice(\n    │...\n 216│// controller: post-restart input still renders (the frozen-PTY symptom is\n*217│// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218│// clear), the fresh generation logs NO replay of the historical stream id,\n*219│// and the retired row is GONE from the broker's enumeration\n*220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n*222│fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223│    init_home();\n 224│    let dir = tempfile::tempdir().expect(\"tempdir\");\n 225│    let (name_a, name_b) = (unique_name(), unique_name());\n    │...\n 251│\n*252│    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n*253│    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254│    let end = Instant::now() + Duration::from_secs(20);\n 255│    while peer_stream_ids(&mut probe).contains(&s1_a) {\n*256│        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n*257│        thread::sleep(Duration::from_millis(50));\n 258│    }\n 259│\n 260│    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n    │...\n 276│    // reconstruction re-took the controller (successor re-take parity), and\n*277│    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n 278│    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n 279│    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n 280│    assert!(\n 281│        !eof_post,\n*282│        \"the current controller's stream saw an EOF across the restart — a replayed \\\n 283│         historical detach cleared it (the 7.41 steal)\"\n 284│    );\n 285│    let painted = screen_of(&view2);\n## redispatch_stall.rs\n   4│//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n  *5│//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n   6│//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n   7│//! windows (33 observed, all 15,000-15,154ms).\n   8│//!\n    │...\n  10│//! through the production broker subscribe path — gate ruling 1):\n *11│//!   - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at\n  12│//!     the first failed write; the producer stays FLAT (never a serial poison\n *13│//!     composition); recovery replays gapless from the cursor.\n  14│//!   - T2 `opener_transient_...`: a transport-class opener failure requeues\n  15│//!     bounded and NEVER creates a peek subscriber (pre-fix: each retry\n *16│//!     subscribed + replayed against the conn).\n  17│//!   - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the\n  18│//!     stream's serve lease — the paired forwarding leg terminates at its\n  19│//!     next send; a new subscriber generation renews and serves again.\n    │...\n  23│//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n *24│//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n *25│//!     see) replayed by a fresh generation neither takes nor clears the live\n  26│//!     replacement controller (ownership/generation validation).\n  27│//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n  28│//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n    │...\n  32│//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n *33│//! binary alongside — the retire/steal regressions are substrate).\n  34│//!\n  35│//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n  36│//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n  37│//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n *38│//! `--test-threads=1` — tests set process-wide env knobs\n  39│//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n  40│\n  41│use std::process::{Child, Command, Stdio};\n    │...\n  43│use std::sync::{Arc, OnceLock};\n *44│use std::thread;\n  45│use std::time::{Duration, Instant};\n  46│\n  47│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  87│    let serve = Arc::clone(&broker);\n *88│    thread::spawn(move || {\n  89│        let _ = serve.serve();\n  90│    });\n  91│    broker\n    │...\n  98│        }\n *99│        thread::sleep(Duration::from_millis(10));\n 100│    }\n 101│    panic!(\"brain could not connect\");\n 102│}\n    │...\n 158│    probe\n*159│        .net_streams()\n 160│        .expect(\"net-streams\")\n 161│        .streams\n 162│        .iter()\n    │...\n 247│        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n*248│        thread::sleep(Duration::from_millis(25));\n 249│    };\n 250│    (operator, opened.stream_id, sid_a)\n 251│}\n    │...\n 254│/// no-reinstall drain window (\"subscriber busy\" surfaces as an Error event),\n*255│/// then accumulate the replay until `want_bytes` bytes arrived. Asserts DENSE\n 256│/// seq contiguity from 0 (gapless) and returns the bytes.\n 257│fn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n 258│    let end = Instant::now() + deadline;\n    │...\n 277│                        seq, next,\n*278│                        \"the recovery replay must be DENSE from 0 — a gap means the \\\n 279│                         overflow/poison lost retained bytes\"\n 280│                    );\n 281│                    next += 1;\n    │...\n 294│                    );\n*295│                    thread::sleep(Duration::from_millis(200));\n 296│                    continue 'attach;\n 297│                }\n 298│                Ok(_) => {}\n    │...\n 310│// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*311│// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n 312│// production broker subscribe path: a REAL subscriber conn that attaches with\n*313│// a full ring and never reads — its seat's replay wedges on the named-pipe\n 314│// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat\n 315│// is removed, later appends never re-feed it, and the PRODUCER — B's ack'd\n*316│// sends through A's read pump — stays flat throughout (pre-fix: the replay\n 317│// ran under the stream mutex with discarded errors, one 400ms poison window\n 318│// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh\n*319│// subscriber replays everything gapless from the cursor — no abandonment.\n 320│#[test]"},"isError":false,"timestamp":1784271515085}}
{"type":"message","id":"28aa02f3","parentId":"7f35deca","timestamp":"2026-07-17T06:58:35.090Z","message":{"role":"toolResult","toolCallId":"call_5h2Ku2UCcbGaa51ohkVFQnQ6|fc_089095e18118ee7d016a59d29af7b481908ba30fa3d27eed2b","toolName":"grep","content":[{"type":"text","text":"# CHANGELOG.md\n 11|\n*12|## [0.36.0] - 2026-07-16\n 13|\n 14|A stability release for attached sessions. A daemon update no longer disrupts\n 15|active sessions, and a session stuck by a bad connection now recovers on its own.\n\n# docs/\n## KNOWN-HAZARDS.md\n 664|\n*665|### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream — historical replay must not steal or clear a live controller  `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`\n*666|<!-- [doc->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] -->\n 667|- **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply — same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter → the fresh dispatcher replays FINISHED historical attach streams. A repl...\n*668|- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...\n 669|- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spawn; `spt-daemon/src/nethost.rs` `NetShared.streams` insert/finish + `DEFAULT_STREAM_RING_CHUNKS`; `spt-daemon/src/applyhost.rs` (the brain-cycle trigger).\n*670|- **Source:** REDISPATCH-TRUTH W1 (hertz source-level RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`, doyle seam-verified; ADR-0038; distinct from the CLOSED 0.30.x session-cursor resume-steal saga).\n 671|\n 672|---\n 673|\n...\n 682|\n*683|### 7.43 One wedged stream subscriber must NEVER stall stream serving — replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims  `[REQ-HAZARD-REDISPATCH-STALL]`\n*684|<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n 685|- **Failure (paid-for, hertz field RCA 2026-07-16 — live v0.34 boxes, recurrent 20–30s PTY/RC freezes, DISPATCH tails 17–62s):** a COMPOSITION, not one new timer. The dispatcher's retryable claims (500ms/1s ×3) re-drove the opener fallback whose guard arm is a broad `Err(_)` (`dispatch.rs:414` — comment intends old-broker-only; catches transport timeout/EOF/poison), each pass installing a throwaway peek subscriber. `StreamLog::attach` synchronously replays the ENTIRE retained ring under the per-...\n 686|- **Invariant:** subscriber I/O never holds the `StreamLog` mutex (enqueue-under-lock, I/O outside, bounded per-subscriber queue; overflow = detach + resume-from-cursor); replay and fan-out halt at the FIRST failed subscriber write and the failed subscriber is REMOVED (attach + append + finish, all sites, `PresenceLog` mirrors included); the ring-peek fallback fires ONLY on the explicit unsupported-verb/old-broker answer; a deadline-poisoned replay is circuit-broken (global backoff, no reinstall until th...\n 687|- **spt-core mapping:** `spt-daemon/src/nethost.rs` `StreamLog::attach`/`push`/`finish` + `PresenceLog` mirrors + serve/wire worker pairing; `spt-daemon/src/dispatch.rs` `first_line` fallback arm + claim retry/backoff + worker spawn; SharedSend bounded-write deadline (round-4, KH 7.7 kin).\n*688|- **Source:** REDISPATCH-STALL W1 (hertz v0.34 field RCA 2026-07-16, doyle seam-verified same day; ADR-0038 Amendment 2026-07-16; `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; distinct from `update --restart`'s bounded 30s rc reconnect loop).\n 689|\n 690|---\n 691|\n## REDISPATCH-STALL-TRIAGE.md\n*1|# REDISPATCH-STALL — wave triage (doyle, 2026-07-16)\n 2|\n*3|<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n 4|\n*5|**Wave:** stop the v0.34 field stalls — REDISPATCH-TRUTH W1's claim retry and\n 6|the broad `Err(_)` opener fallback repeatedly drive `StreamLog::attach`'s\n 7|discard-errors replay-under-mutex into serial 15s subscriber-poison windows\n 8|that compose into 17–62s PTY/RC freezes. Counter-63 fast-follow after\n...\n 11|**Canonical RCA:** hertz 2026-07-16, doyle seam-verified the same day; repo\n*12|summary `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`.\n 13|**Decision record:** ADR-0038 **Amendment (2026-07-16)**.\n 14|**Hazard:** KNOWN-HAZARDS 7.43.\n 15|**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n...\n 89|\n*90|**Binding on every leg:** redispatch D1/D1b stay green (the retire/steal\n 91|regressions are substrate, not victims).\n 92|\n 93|**Missing-coverage census (fair, rides the wave):** D3 never poisons an\n...\n 141|doyle triaged (this doc) → todlando builds (branch\n*142|`build/redispatch-stall-w1`, all five REQs, flip `required_stages` at build\n 143|start per [[traceable-per-wave-activation]]) → doyle gates (rulings above) →\n 144|deployah releases (counter 63 fast-follow, bump-in-PR, counter from published\n 145|metadata) → hertz field-verify (discriminators above; they own the incident\n## REDISPATCH-TRUTH-TRIAGE.md\n*1|# REDISPATCH-TRUTH — milestone triage (JIT)\n 2|\n 3|> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n 4|> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n*5|> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n 6|> (hertz source-level, doyle seam-verified same day). Decision record:\n 7|> ADR-0038. Lane: doyle triages → todlando builds → doyle gates → deployah\n 8|> releases → hertz field-verify (they hold the incident logs).\n...\n 13|freezes active remote `spt rc` sessions. The PTYs and broker-held QUIC\n*14|streams survive by construction — the freeze is a **production redispatch\n 15|bug** in the fresh brain's stream reconstruction:\n 16|\n 17|1. **Stale replay steals live controllers.** The fresh\n...\n 57|Broker stream-table rows gain explicit **lifecycle truth**, and the\n*58|dispatcher redispatches only what is genuinely live:\n 59|\n*60|- **A — finished rows retire from redispatch eligibility.** A fresh\n 61|  dispatcher must never re-serve a terminal Attach request. Retain only the\n 62|  lifecycle state genuinely needed after EOF (presence/log reads); the\n*63|  redispatch enumeration must not see terminal rows.\n 64|- **B — restart-durable opener/classification metadata.** Pin the\n 65|  per-stream immutable opener fact (or the complete first NDJSON record)\n 66|  OUTSIDE the evictable data ring, held until stream close. Recovery\n...\n 84|### W1 — the fix cluster (single build wave; todlando)\n*85|- **REQ-HAZARD-REDISPATCH-CONTROL-STEAL** — the invariant (KNOWN-HAZARDS\n 86|  7.41) + regression D1.\n*87|- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n 88|- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n 89|- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n 90|\n\n## adr/\n### 0038-broker-stream-lifecycle-truth.md\n*1|# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\n 2|\n*3|- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL — see Amendment below)**\n*4|- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n 5|\n 6|## Context\n 7|\n...\n 10|no removal or retirement path exists. Stream *classification* is derived at\n*11|redispatch time by peeking ring seq 0, but the `StreamLog` data ring is\n 12|bounded (4096 transport chunks) and evicts the protocol opener under\n 13|traffic. Dispatcher claims are a per-process one-shot `HashSet` insert\n 14|performed before worker spawn, never cleared or retried.\n...\n 26|## Decision\n*27|<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n 28|\n*29|Stream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n 30|\n*31|1. **Finished is terminal for redispatch.** A finished/terminal stream row\n*32|   is retired from redispatch eligibility (removed from the enumeration the\n 33|   dispatcher claims from, or excluded by lifecycle state). Only the\n 34|   post-EOF state genuinely needed by other readers (presence, log reads)\n 35|   survives. A fresh dispatcher never re-serves a terminal Attach.\n...\n 72|\n*73|## Amendment — REDISPATCH-STALL (2026-07-16)\n 74|<!-- [doc->REQ-DISPATCH-FALLBACK-CIRCUIT] -->\n 75|<!-- [doc->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] -->\n 76|<!-- [doc->REQ-SERVE-OWNERSHIP-GENERATION] -->\n...\n 80|  day, both hours apart); RCA summary\n*81|  `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; hazard\n*82|  KNOWN-HAZARDS 7.43; build shape `docs/REDISPATCH-STALL-TRIAGE.md`.\n 83|\n 84|The W1 machinery this ADR shipped is field-correct on its own terms (D1/D1b\n 85|hold: finished rows never serve) but two of its legs COMPOSE into a stall\n...\n 121|   retirement cannot see.\n*122|6. **(new — dispatcher hygiene + telemetry)** The redispatch worker pool is\n 123|   bounded with batched cold enumeration and NO claim/sessions locks held\n 124|   across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12\n 125|   kin); dispatch telemetry is keyed per\n\nSkipped missing paths: .planning\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"CHANGELOG.md, docs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":34,"fileCount":5,"files":["CHANGELOG.md","docs/KNOWN-HAZARDS.md","docs/REDISPATCH-STALL-TRIAGE.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/adr/0038-broker-stream-lifecycle-truth.md"],"fileMatches":[{"path":"CHANGELOG.md","count":1},{"path":"docs/KNOWN-HAZARDS.md","count":7},{"path":"docs/REDISPATCH-STALL-TRIAGE.md","count":6},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","count":8},{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md","count":12}],"truncated":true,"displayContent":"# CHANGELOG.md\n 11│\n*12│## [0.36.0] - 2026-07-16\n 13│\n 14│A stability release for attached sessions. A daemon update no longer disrupts\n 15│active sessions, and a session stuck by a bad connection now recovers on its own.\n\n# docs/\n## KNOWN-HAZARDS.md\n 664│\n*665│### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream — historical replay must not steal or clear a live controller  `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`\n*666│<!-- [doc->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] -->\n 667│- **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply — same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter → the fresh dispatcher replays FINISHED historical attach streams. A repl...\n*668│- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...\n 669│- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spawn; `spt-daemon/src/nethost.rs` `NetShared.streams` insert/finish + `DEFAULT_STREAM_RING_CHUNKS`; `spt-daemon/src/applyhost.rs` (the brain-cycle trigger).\n*670│- **Source:** REDISPATCH-TRUTH W1 (hertz source-level RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`, doyle seam-verified; ADR-0038; distinct from the CLOSED 0.30.x session-cursor resume-steal saga).\n 671│\n 672│---\n 673│\n    │...\n 682│\n*683│### 7.43 One wedged stream subscriber must NEVER stall stream serving — replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims  `[REQ-HAZARD-REDISPATCH-STALL]`\n*684│<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n 685│- **Failure (paid-for, hertz field RCA 2026-07-16 — live v0.34 boxes, recurrent 20–30s PTY/RC freezes, DISPATCH tails 17–62s):** a COMPOSITION, not one new timer. The dispatcher's retryable claims (500ms/1s ×3) re-drove the opener fallback whose guard arm is a broad `Err(_)` (`dispatch.rs:414` — comment intends old-broker-only; catches transport timeout/EOF/poison), each pass installing a throwaway peek subscriber. `StreamLog::attach` synchronously replays the ENTIRE retained ring under the per-...\n 686│- **Invariant:** subscriber I/O never holds the `StreamLog` mutex (enqueue-under-lock, I/O outside, bounded per-subscriber queue; overflow = detach + resume-from-cursor); replay and fan-out halt at the FIRST failed subscriber write and the failed subscriber is REMOVED (attach + append + finish, all sites, `PresenceLog` mirrors included); the ring-peek fallback fires ONLY on the explicit unsupported-verb/old-broker answer; a deadline-poisoned replay is circuit-broken (global backoff, no reinstall until th...\n 687│- **spt-core mapping:** `spt-daemon/src/nethost.rs` `StreamLog::attach`/`push`/`finish` + `PresenceLog` mirrors + serve/wire worker pairing; `spt-daemon/src/dispatch.rs` `first_line` fallback arm + claim retry/backoff + worker spawn; SharedSend bounded-write deadline (round-4, KH 7.7 kin).\n*688│- **Source:** REDISPATCH-STALL W1 (hertz v0.34 field RCA 2026-07-16, doyle seam-verified same day; ADR-0038 Amendment 2026-07-16; `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; distinct from `update --restart`'s bounded 30s rc reconnect loop).\n 689│\n 690│---\n 691│\n## REDISPATCH-STALL-TRIAGE.md\n  *1│# REDISPATCH-STALL — wave triage (doyle, 2026-07-16)\n   2│\n  *3│<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n   4│\n  *5│**Wave:** stop the v0.34 field stalls — REDISPATCH-TRUTH W1's claim retry and\n   6│the broad `Err(_)` opener fallback repeatedly drive `StreamLog::attach`'s\n   7│discard-errors replay-under-mutex into serial 15s subscriber-poison windows\n   8│that compose into 17–62s PTY/RC freezes. Counter-63 fast-follow after\n    │...\n  11│**Canonical RCA:** hertz 2026-07-16, doyle seam-verified the same day; repo\n *12│summary `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`.\n  13│**Decision record:** ADR-0038 **Amendment (2026-07-16)**.\n  14│**Hazard:** KNOWN-HAZARDS 7.43.\n  15│**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n    │...\n  89│\n *90│**Binding on every leg:** redispatch D1/D1b stay green (the retire/steal\n  91│regressions are substrate, not victims).\n  92│\n  93│**Missing-coverage census (fair, rides the wave):** D3 never poisons an\n    │...\n 141│doyle triaged (this doc) → todlando builds (branch\n*142│`build/redispatch-stall-w1`, all five REQs, flip `required_stages` at build\n 143│start per [[traceable-per-wave-activation]]) → doyle gates (rulings above) →\n 144│deployah releases (counter 63 fast-follow, bump-in-PR, counter from published\n 145│metadata) → hertz field-verify (discriminators above; they own the incident\n## REDISPATCH-TRUTH-TRIAGE.md\n *1│# REDISPATCH-TRUTH — milestone triage (JIT)\n  2│\n  3│> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n  4│> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n *5│> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n  6│> (hertz source-level, doyle seam-verified same day). Decision record:\n  7│> ADR-0038. Lane: doyle triages → todlando builds → doyle gates → deployah\n  8│> releases → hertz field-verify (they hold the incident logs).\n   │...\n 13│freezes active remote `spt rc` sessions. The PTYs and broker-held QUIC\n*14│streams survive by construction — the freeze is a **production redispatch\n 15│bug** in the fresh brain's stream reconstruction:\n 16│\n 17│1. **Stale replay steals live controllers.** The fresh\n   │...\n 57│Broker stream-table rows gain explicit **lifecycle truth**, and the\n*58│dispatcher redispatches only what is genuinely live:\n 59│\n*60│- **A — finished rows retire from redispatch eligibility.** A fresh\n 61│  dispatcher must never re-serve a terminal Attach request. Retain only the\n 62│  lifecycle state genuinely needed after EOF (presence/log reads); the\n*63│  redispatch enumeration must not see terminal rows.\n 64│- **B — restart-durable opener/classification metadata.** Pin the\n 65│  per-stream immutable opener fact (or the complete first NDJSON record)\n 66│  OUTSIDE the evictable data ring, held until stream close. Recovery\n   │...\n 84│### W1 — the fix cluster (single build wave; todlando)\n*85│- **REQ-HAZARD-REDISPATCH-CONTROL-STEAL** — the invariant (KNOWN-HAZARDS\n 86│  7.41) + regression D1.\n*87│- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n 88│- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n 89│- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n 90│\n\n## adr/\n### 0038-broker-stream-lifecycle-truth.md\n  *1│# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\n   2│\n  *3│- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL — see Amendment below)**\n  *4│- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n   5│\n   6│## Context\n   7│\n    │...\n  10│no removal or retirement path exists. Stream *classification* is derived at\n *11│redispatch time by peeking ring seq 0, but the `StreamLog` data ring is\n  12│bounded (4096 transport chunks) and evicts the protocol opener under\n  13│traffic. Dispatcher claims are a per-process one-shot `HashSet` insert\n  14│performed before worker spawn, never cleared or retried.\n    │...\n  26│## Decision\n *27│<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n  28│\n *29│Stream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n  30│\n *31│1. **Finished is terminal for redispatch.** A finished/terminal stream row\n *32│   is retired from redispatch eligibility (removed from the enumeration the\n  33│   dispatcher claims from, or excluded by lifecycle state). Only the\n  34│   post-EOF state genuinely needed by other readers (presence, log reads)\n  35│   survives. A fresh dispatcher never re-serves a terminal Attach.\n    │...\n  72│\n *73│## Amendment — REDISPATCH-STALL (2026-07-16)\n  74│<!-- [doc->REQ-DISPATCH-FALLBACK-CIRCUIT] -->\n  75│<!-- [doc->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] -->\n  76│<!-- [doc->REQ-SERVE-OWNERSHIP-GENERATION] -->\n    │...\n  80│  day, both hours apart); RCA summary\n *81│  `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; hazard\n *82│  KNOWN-HAZARDS 7.43; build shape `docs/REDISPATCH-STALL-TRIAGE.md`.\n  83│\n  84│The W1 machinery this ADR shipped is field-correct on its own terms (D1/D1b\n  85│hold: finished rows never serve) but two of its legs COMPOSE into a stall\n    │...\n 121│   retirement cannot see.\n*122│6. **(new — dispatcher hygiene + telemetry)** The redispatch worker pool is\n 123│   bounded with batched cold enumeration and NO claim/sessions locks held\n 124│   across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12\n 125│   kin); dispatch telemetry is keyed per","missingPaths":[".planning"],"linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784271515090}}
{"type":"message","id":"3cf2f7fa","parentId":"28aa02f3","timestamp":"2026-07-17T06:58:35.093Z","message":{"role":"toolResult","toolCallId":"call_EU1MEDBYEFfKGqUbbUV1XwD9|fc_089095e18118ee7d016a59d29af7bc8190a31e8fd852ecf219","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 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780296118 -0700\tcheckout: moving from main to dev-freeform\n4fa29498d2552f277e918219f74d9c75183a6921 905c6b438f781847bcfda59501d30e073c85a832 Reavo End <decidel@gmail.com> 1780296234 -0700\tcommit: docs: add M0-PLAN.md (just-in-time task plan) + roadmap pointer\n905c6b438f781847bcfda59501d30e073c85a832 d6aa312316a28997507bf9738e9d8300ebb08cad Reavo End <decidel@gmail.com> 1780297115 -0700\tcommit: @\nd6aa312316a28997507bf9738e9d8300ebb08cad e8ec9803ae6e26efba6f2754579d1e97a7b6506e Reavo End <decidel@gmail.com> 1780297126 -0700\tcommit (amend): feat(spt-proto): M0 T2 — EVENT envelope grammar (compose + parse)\ne8ec9803ae6e26efba6f2754579d1e97a7b6506e f3a55450e27f9c7b96a6bc3e2a00929144a620e6 Reavo End <decidel@gmail.com> 1780297309 -0700\tcommit: feat(spt-proto): M0 T3 — EVENT-PART chunker + byte-exact reassembly\nf3a55450e27f9c7b96a6bc3e2a00929144a620e6 c04e07a3545e5a01de0a6135073584008e9a7eb6 Reavo End <decidel@gmail.com> 1780297415 -0700\tcommit: feat(spt-proto): M0 T4 — endpoint type taxonomy (open + agent/Shell seam)\nc04e07a3545e5a01de0a6135073584008e9a7eb6 e93037d11f41a72c25f451fa637c63c7b54226cc Reavo End <decidel@gmail.com> 1780297723 -0700\tcommit: feat(spt-proto): M0 T5 — Ed25519 identity primitive (clean-room)\ne93037d11f41a72c25f451fa637c63c7b54226cc 260f337d330600e8d4bfa66e06a0850d4f861b37 Reavo End <decidel@gmail.com> 1780297787 -0700\tcommit: feat(spt-proto): M0 T6 — wire-protocol version + N-1 compat window\n260f337d330600e8d4bfa66e06a0850d4f861b37 1821000dd20a7c5bd85257d72c26fd1e346fbef7 Reavo End <decidel@gmail.com> 1780297902 -0700\tcommit: feat(spt-proto): M0 T7 — payload model (typed commands + binary blobs)\n1821000dd20a7c5bd85257d72c26fd1e346fbef7 7fdc26c913420e8ca6114071fb7089520241cd84 Reavo End <decidel@gmail.com> 1780298723 -0700\tcommit: feat(spt-store): M0 T8 — atomic write (EBUSY retry) + UNC-path strip\n7fdc26c913420e8ca6114071fb7089520241cd84 e81c06f94a9a7278c1f067c6743a959110293246 Reavo End <decidel@gmail.com> 1780298874 -0700\tcommit: feat(spt-store): M0 T9 — SQLite spool (deferred-aware drain)\ne81c06f94a9a7278c1f067c6743a959110293246 459c51b9ba98c64a991e8384889358bf28c403d9 Reavo End <decidel@gmail.com> 1780299008 -0700\tcommit: feat(spt-store): M0 T10 — perch layout + single path resolver\n459c51b9ba98c64a991e8384889358bf28c403d9 e7beacafaafca3ab964711318eb20c102390992b Reavo End <decidel@gmail.com> 1780299239 -0700\tcommit: feat(spt-store): M0 T11 — registry persistence + stale-entry cleanup\ne7beacafaafca3ab964711318eb20c102390992b d98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 Reavo End <decidel@gmail.com> 1780299365 -0700\tcommit: feat(spt-store): M0 T12 — info.json shape + torn-read tolerance\nd98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 be5ed5806934dfe8729330e7596bb101226524f2 Reavo End <decidel@gmail.com> 1780299463 -0700\tcommit: ci(M0 T13): build + test + clippy matrix and traceable-reqs hard gate\nbe5ed5806934dfe8729330e7596bb101226524f2 849124569d1e726de50341ed9e01b6fd3cdcdc72 Reavo End <decidel@gmail.com> 1780299589 -0700\tcommit: chore(M0 T14): activation sweep — activate R-ARCH-1, mark M0 complete\n849124569d1e726de50341ed9e01b6fd3cdcdc72 f388817ccbb0a19dc0d8d2e245530e41da065b64 Reavo End <decidel@gmail.com> 1780300690 -0700\tcommit: docs: add M1-PLAN.md (local messaging + binary) + roadmap pointer\nf388817ccbb0a19dc0d8d2e245530e41da065b64 bdaf52ab67490ac500963533f8927456f3a97d6a Reavo End <decidel@gmail.com> 1780301333 -0700\tcommit: chore(M1 T0): register REQ-MSG-1/2/3 + scaffold spt-msg crate\nbdaf52ab67490ac500963533f8927456f3a97d6a d3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 Reavo End <decidel@gmail.com> 1780301461 -0700\tcommit: feat(spt-msg): M1 T1 — TCP wire framing + loopback listener\nd3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c Reavo End <decidel@gmail.com> 1780301593 -0700\tcommit: feat(spt-msg): M1 T2 — TCP-first/spool-fallback delivery + routing\n2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c 5eb4c37ed0b808e187fcaea91420edfc0dc95a30 Reavo End <decidel@gmail.com> 1780301696 -0700\tcommit: feat(spt-msg): M1 T3 — ready-agent lifecycle\n5eb4c37ed0b808e187fcaea91420edfc0dc95a30 bf319984d10a9a29525c1cfd325d604f8bd1b585 Reavo End <decidel@gmail.com> 1780301798 -0700\tcommit: feat(spt-msg): M1 T4 — ring (blocking send-and-wait-for-reply)\nbf319984d10a9a29525c1cfd325d604f8bd1b585 6e167121248cf8d4df9d65255f5bc24aeae01863 Reavo End <decidel@gmail.com> 1780301863 -0700\tcommit: feat(spt-msg): M1 T5 — deferred send (hook channel)\n6e167121248cf8d4df9d65255f5bc24aeae01863 356b8c7969468a6ec5c5779ec67c91a790d0b0c8 Reavo End <decidel@gmail.com> 1780302103 -0700\tcommit: feat(spt): M1 T6-T8 — spt binary + CLI (send/ring/ready/poll/list/stop/whoami)\n356b8c7969468a6ec5c5779ec67c91a790d0b0c8 af0fff0df8cff829fe0fab0a4696b612b25dd4e9 Reavo End <decidel@gmail.com> 1780302255 -0700\tcommit: test(spt-msg): M1 T9-T10 — killer-quickstart E2E + activation sweep\naf0fff0df8cff829fe0fab0a4696b612b25dd4e9 3b70fed82bfa77502df09d3e34eef140ce125b12 Reavo End <decidel@gmail.com> 1780304902 -0700\tcommit: fix(spt-msg): isolate unit tests to a temp SPT_HOME (stop polluting the live owlery)\n3b70fed82bfa77502df09d3e34eef140ce125b12 bdb719373191d5abb1d698dff0bdcd176d3008a7 Reavo End <decidel@gmail.com> 1780305400 -0700\tcommit: refactor(spt-store)+docs: default home spt-core/ + separate spt-plugin from spt-core scope\nbdb719373191d5abb1d698dff0bdcd176d3008a7 7e3acfbfc23f41bafd8c8514a830f9e7389cc14e Reavo End <decidel@gmail.com> 1780308344 -0700\tcommit: docs(M2a): author M2a-PLAN.md (harness contract) + split M2 in ROADMAP\n7e3acfbfc23f41bafd8c8514a830f9e7389cc14e 637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 Reavo End <decidel@gmail.com> 1780310110 -0700\tcommit: docs: reconcile daemon-hosted endpoint liveness (KNOWN-HAZARDS 2.5)\n637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 67414530a8b4d5fb2ff6aabf357c200ff56badca Reavo End <decidel@gmail.com> 1780310742 -0700\tcommit: feat(spt-runtime): M2a T0 — scaffold harness-contract crate\n67414530a8b4d5fb2ff6aabf357c200ff56badca ae2337649d66fb68714460a6bd66dee918dd6ce7 Reavo End <decidel@gmail.com> 1780311003 -0700\tcommit: feat(spt-runtime): M2a T1 — runtime-manifest schema + parse + validate\nae2337649d66fb68714460a6bd66dee918dd6ce7 fecd6d26f69cff0bfbdc56f29739d4bb6d581cac Reavo End <decidel@gmail.com> 1780311256 -0700\tcommit: feat(spt-runtime): M2a T2 — AgentRuntime seam + ManifestRuntime\nfecd6d26f69cff0bfbdc56f29739d4bb6d581cac 281eb8dd3c542f586ff8a8f827a5e6e0288b0263 Reavo End <decidel@gmail.com> 1780311614 -0700\tcommit: feat(spt): M2a T3 — `spt api` dispatch group + local-api-auth\n281eb8dd3c542f586ff8a8f827a5e6e0288b0263 d6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e Reavo End <decidel@gmail.com> 1780312122 -0700\tcommit: feat(spt): M2a T4 — harness-hosted startup (seed → listen → bind)\nd6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e bae7124badb654c27596ca14df5ce623db50303c Reavo End <decidel@gmail.com> 1780312254 -0700\tcommit: feat(spt): M2a T5 — delivery + activity (state / echo-gate / poll / inject)\nbae7124badb654c27596ca14df5ce623db50303c 42af360e619f7510d9c492507a9864c48b41ac1e Reavo End <decidel@gmail.com> 1780312367 -0700\tcommit: feat(spt): M2a T6 — worker perches + cascade-wipe guard\n42af360e619f7510d9c492507a9864c48b41ac1e d555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc Reavo End <decidel@gmail.com> 1780312565 -0700\tcommit: feat(spt): M2a T7 — reporting + boundary surface\nd555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc 146a7b83f67685fbb4d3fd9090a1614298357e22 Reavo End <decidel@gmail.com> 1780312746 -0700\tcommit: feat(mock-adapter): M2a T8 — generic mock adapter (manifest + helper)\n146a7b83f67685fbb4d3fd9090a1614298357e22 504b346c73583b4ddcb83e63da92fd7211c0d2ab Reavo End <decidel@gmail.com> 1780313178 -0700\tcommit: test(spt): M2a T9 — contract E2E through the mock adapter\n504b346c73583b4ddcb83e63da92fd7211c0d2ab dbdbabe8f82ce9d85a39dba35deda037d4c480e3 Reavo End <decidel@gmail.com> 1780313373 -0700\tcommit: chore(M2a): T10 — activation sweep + ROADMAP (M2a delivered)\ndbdbabe8f82ce9d85a39dba35deda037d4c480e3 104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 Reavo End <decidel@gmail.com> 1780314087 -0700\tcommit: docs(M2b): author M2b-PLAN.md (live-agent lifecycle)\n104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 e0b2c80024315541aa2415ca6ad0e6f3da43b895 Reavo End <decidel@gmail.com> 1780314638 -0700\tcommit: feat(spt-live): M2b T0 — scaffold live-agent lifecycle crate\ne0b2c80024315541aa2415ca6ad0e6f3da43b895 a9a079bc5dd69fbb77da2b46d7e0cdf70707e69e Reavo End <decidel@gmail.com> 1780315057 -0700\tcommit: feat(spt-live): M2b T1 — history subsystem (fetcher/locate-normalize/native)\na9a079bc5dd69fbb77da2b46d7e0cdf70707e69e 4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b Reavo End <decidel@gmail.com> 1780315333 -0700\tcommit: feat(spt-live): M2b T2 — spawn-psyche seam (nested perch + owned prompt)\n4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b c8bfe6e24c5752e2168c3f020dbc8aae2b628852 Reavo End <decidel@gmail.com> 1780315707 -0700\tcommit: feat(spt-live): M2b T3 — echo-commune seam (history brief → commune drop)\nc8bfe6e24c5752e2168c3f020dbc8aae2b628852 91380ab5dc0accc2286597c45efb9a6e620133a5 Reavo End <decidel@gmail.com> 1780315958 -0700\tcommit: feat(spt-live): M2b T4 — drop-file ingest (single-writer + precedence guard)\n91380ab5dc0accc2286597c45efb9a6e620133a5 5d085e8c480e1b79f6a6720d68ac425f002478a8 Reavo End <decidel@gmail.com> 1780316170 -0700\tcommit: feat(spt-live): M2b T5 — pulse heartbeat (interim per-tick driver)\n5d085e8c480e1b79f6a6720d68ac425f002478a8 86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 Reavo End <decidel@gmail.com> 1780316382 -0700\tcommit: feat(spt-live): M2b T6 — signoff + boundary ordering invariants\n86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 5f1a344bc06bd14d2f9025250ad2444d9994380b Reavo End <decidel@gmail.com> 1780316562 -0700\tcommit: feat(spt-live): M2b T7 — resume-session seam (continue-existing / fresh-preload)\n5f1a344bc06bd14d2f9025250ad2444d9994380b b91168c80df0f6be33b17a3d3031b2a748818cd7 Reavo End <decidel@gmail.com> 1780316889 -0700\tcommit: feat(spt): M2b T8 — api listen lifecycle wiring + graceful shutdown\nb91168c80df0f6be33b17a3d3031b2a748818cd7 27563a5104eabf32c4266fd89c90b2e5e0b38ec5 Reavo End <decidel@gmail.com> 1780317197 -0700\tcommit: test(spt): M2b T9 — LiveAgent lifecycle E2E through the mock adapter\n27563a5104eabf32c4266fd89c90b2e5e0b38ec5 edeeb41228fef7224fe69bbdef98572fa16c2091 Reavo End <decidel@gmail.com> 1780317412 -0700\tcommit: docs(M2b): T10 — activation sweep, ROADMAP delivered + M3 plan stub\nedeeb41228fef7224fe69bbdef98572fa16c2091 610c9958fb0c0a343fa65e475306a488be3c5515 Reavo End <decidel@gmail.com> 1780344513 -0700\tcommit: docs(M3): author M3-PLAN.md (terminal wrapper + daemon + self-update)\n610c9958fb0c0a343fa65e475306a488be3c5515 0c1a814f78b73d0883c2156816fb04896104eb50 Reavo End <decidel@gmail.com> 1780349561 -0700\tcommit: docs(M3): close Phase-0 gate — Spike #3 (QUIC survival) + #6 (idempotent boundary) PASS\n0c1a814f78b73d0883c2156816fb04896104eb50 9e64261a6d174cfdc1a5c4c23c65db4e1f59408e Reavo End <decidel@gmail.com> 1780349856 -0700\tcommit: docs(M3): close §E.3 — Spike #5 (100× restart + resize-under-load) PASS\n9e64261a6d174cfdc1a5c4c23c65db4e1f59408e 2549c9f8ef8112cf7cb9bfa27b0569292de6fcea Reavo End <decidel@gmail.com> 1780368815 -0700\tcommit: docs(M3): close §E.2 — Spike #4 (Linux forkpty parity) PASS; Phase-0 gate COMPLETE\n2549c9f8ef8112cf7cb9bfa27b0569292de6fcea eddad57f83fa98284bbc35673498a52553e7cc92 Reavo End <decidel@gmail.com> 1780384610 -0700\tcommit: docs: grill-firmed multi-subnet model, notifications, and PTY digest (ADR-0006/0007/0008)\neddad57f83fa98284bbc35673498a52553e7cc92 0581b42cbfe229ff2ed0b08407c0ec700433b1d0 Reavo End <decidel@gmail.com> 1780386806 -0700\tcommit: feat(proto): reserve endpoint-id charset; guard :/@ delimiters at creation seams\n0581b42cbfe229ff2ed0b08407c0ec700433b1d0 c9a466ed2627d98662bb62ccab1ed1ce6bed4950 Reavo End <decidel@gmail.com> 1780386958 -0700\tcommit: docs(M3a): author M3a-PLAN.md (spt-term session-surface mechanism)\nc9a466ed2627d98662bb62ccab1ed1ce6bed4950 f03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 Reavo End <decidel@gmail.com> 1780388886 -0700\tcommit: docs: grill-firm resource advertisement, subnet icon, endpoint access whitelist (ADR-0009)\nf03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 8e239574cf0183bc4c604f30f815ea2623917986 Reavo End <decidel@gmail.com> 1780389867 -0700\tcommit: feat(term): scaffold spt-term crate (M3a A0)\n8e239574cf0183bc4c604f30f815ea2623917986 4a00304fd08feb566e4ab7dca54ef79a6858796f Reavo End <decidel@gmail.com> 1780390319 -0700\tcommit: feat(term): SessionSurface trait + native PTY backend (M3a A1)\n4a00304fd08feb566e4ab7dca54ef79a6858796f 0adaae59f4c6092da3800934fcd0e8d7a8f36502 Reavo End <decidel@gmail.com> 1780390682 -0700\tcommit: feat(term): ConPTY-DSR auto-answer drain pump (M3a A2)\n0adaae59f4c6092da3800934fcd0e8d7a8f36502 ad21334df4bf97f9cbffa4fce0a025a486631ca0 Reavo End <decidel@gmail.com> 1780390906 -0700\tcommit: feat(term): send-keys + send-line injection (M3a A3)\nad21334df4bf97f9cbffa4fce0a025a486631ca0 4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf Reavo End <decidel@gmail.com> 1780391099 -0700\tcommit: feat(term): bounded byte-stream (M3a A4)\n4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf 3877dd2e966b4e846115d58cba0fb4e7d386c433 Reavo End <decidel@gmail.com> 1780391443 -0700\tcommit: feat(term): PTY digest parser primitive (M3a A5, ADR-0008)\n3877dd2e966b4e846115d58cba0fb4e7d386c433 1c39997815ce808a22772afd2e7d95e01b1a7a53 Reavo End <decidel@gmail.com> 1780391614 -0700\tcommit: docs(M3a): activation sweep — ROADMAP/CONTEXT amend + M3b stub (A6)\n1c39997815ce808a22772afd2e7d95e01b1a7a53 d8b854ea5c3482a0a835ecfff4e57fae398606e0 Reavo End <decidel@gmail.com> 1780392106 -0700\tcommit: fix(test): unix manifest fixtures broke on Linux (TOML quoting vs tokenizer)\nd8b854ea5c3482a0a835ecfff4e57fae398606e0 a32b0710dae8e0be0da7068203f8c2dc9d17f58b Reavo End <decidel@gmail.com> 1780393680 -0700\tcommit: fix(store): concurrent SQLite openers fail with 'database is locked' (4.7)\na32b0710dae8e0be0da7068203f8c2dc9d17f58b 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397231 -0700\tcommit: ci: install traceable-reqs with a PAT secret (cross-org private repo)\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397494 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397910 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 7db111fb673380fa953b47110d2e30ffdc217908 Reavo End <decidel@gmail.com> 1780398299 -0700\tcommit: docs(M3b): flesh M3b-PLAN.md from stub into full task table\n7db111fb673380fa953b47110d2e30ffdc217908 d213f005e918900ae04797c3730652630deedd8f Reavo End <decidel@gmail.com> 1780398821 -0700\tcommit: @\nd213f005e918900ae04797c3730652630deedd8f 0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc Reavo End <decidel@gmail.com> 1780398839 -0700\tcommit (amend): feat(M3b-B0): scaffold spt-daemon + versioned local-IPC transport\n0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc 181ce9194bc993789b0d9375a3e166ace78def65 Reavo End <decidel@gmail.com> 1780400958 -0700\tcommit: feat(M3b-B1): broker kernel hosts a real PTY child over the IPC\n181ce9194bc993789b0d9375a3e166ace78def65 c40524f8a2f92fd72a795d7a386031c1953fae51 Reavo End <decidel@gmail.com> 1780401821 -0700\tcommit: feat(M3b-B2): brain + gapless handoff (kill/restart survives, fresh gen_start)\nc40524f8a2f92fd72a795d7a386031c1953fae51 33d2a716b19dc83fc83e594d621d65b2fbd6fb12 Reavo End <decidel@gmail.com> 1780402840 -0700\tcommit: feat(M3b-B3): consolidate interim listen/pulse/Psyche loops into the brain\n33d2a716b19dc83fc83e594d621d65b2fbd6fb12 978e62b91fb28c37334061785c883244c9a7bf8a Reavo End <decidel@gmail.com> 1780403208 -0700\tcommit: Continuous work encouragement rev\n978e62b91fb28c37334061785c883244c9a7bf8a d3ce22ab7bf2874762d928cf4ad3482cea0d20c6 Reavo End <decidel@gmail.com> 1780404013 -0700\tcommit: M3b B4: daemon-authoritative liveness behind one resolver\nd3ce22ab7bf2874762d928cf4ad3482cea0d20c6 35072def7f5434b233d54d5dd289b8f77df63ed3 Reavo End <decidel@gmail.com> 1780437194 -0700\tcommit: feat(M3b-B5): idempotent broker<->brain boundary -- exactly-once across a brain crash\n35072def7f5434b233d54d5dd289b8f77df63ed3 9ea1e403736dbc7bfa235e624b5107f25341ac8a Reavo End <decidel@gmail.com> 1780464869 -0700\tcommit: docs: M4 design — adapter/shell/endpoint lifecycle decisions + fork (ADR-0010)\n9ea1e403736dbc7bfa235e624b5107f25341ac8a 25c8025c955bf011cabe9e258115c0f298399330 Reavo End <decidel@gmail.com> 1780469305 -0700\tcommit: docs: adopt BranchStore (git-KV) as the coarse/durable state substrate (ADR-0011)\n25c8025c955bf011cabe9e258115c0f298399330 115dabb85103a3c6ca7f64084d1994bf72e27c22 Reavo End <decidel@gmail.com> 1780469551 -0700\tcommit: docs(spike-07): BranchStore git-KV bench — PASS, coarse/hot boundary empirically grounded (ADR-0011)\n115dabb85103a3c6ca7f64084d1994bf72e27c22 c848ebd736375677a84c6d8c072be67dac347cda Reavo End <decidel@gmail.com> 1780471127 -0700\tcommit: docs(spike-07): in-process gix true number (~4.7ms/commit) via attractor pilot\nc848ebd736375677a84c6d8c072be67dac347cda c1c4f649966e0f302d3a8fe67bb3b93d3a6d928c Reavo End <decidel@gmail.com> 1780477473 -0700\tcommit: feat(daemon): B6 — auto-start + in-memory seed map (REQ-DAEMON-3, REQ-START-3)\nc1c4f649966e0f302d3a8fe67bb3b93d3a6d928c d06af5dc1f22276c3dd0458b5fe39d8291057e3d Reavo End <decidel@gmail.com> 1780478214 -0700\tcommit: feat(daemon): B7 — orphan-watch supervised-crash signoff (REQ-DAEMON-4)\nd06af5dc1f22276c3dd0458b5fe39d8291057e3d 16f4c8e017586982914e2ae8337b3036540e64ff Reavo End <decidel@gmail.com> 1780479906 -0700\tcommit: feat(daemon): B8 — PTY-digest daemon-half (REQ-TERM-4 int)\n16f4c8e017586982914e2ae8337b3036540e64ff 20817a6c519af2e676b1f37197dc4bb71c1a2e7e Reavo End <decidel@gmail.com> 1780480418 -0700\tcommit: feat(daemon): B9 — M3b activation sweep + daemon E2E (REQ-DAEMON-1/2/4 int)\n20817a6c519af2e676b1f37197dc4bb71c1a2e7e 2573101b65c6a1b0e2d119f4277762f69b4638d8 Reavo End <decidel@gmail.com> 1780504446 -0700\tcommit: fix(daemon): EffectJournal::open creates missing parent dir (broker-bind CI failure)\n2573101b65c6a1b0e2d119f4277762f69b4638d8 fe0acceaedb20cefc016c3f75baab16143c1128e Reavo End <decidel@gmail.com> 1780505326 -0700\tcommit: feat(daemon): M3c-C0 self-update engine + brain-only zero-interruption swap\nfe0acceaedb20cefc016c3f75baab16143c1128e ac4473acf248ed60504035e5a4a4820a4e48b339 Reavo End <decidel@gmail.com> 1780505958 -0700\tcommit: feat(daemon): M3c-C1 signed release verification + rollback hardening\nac4473acf248ed60504035e5a4a4820a4e48b339 b267f6e92f841c825a80eb60a2e785fff8f04174 Reavo End <decidel@gmail.com> 1780506686 -0700\tcommit: feat(daemon): M3c-C2 self-update consent gating + last-active resolver\nb267f6e92f841c825a80eb60a2e785fff8f04174 03a245c2085d77ac89ba31b4b90f707b085a70cc Reavo End <decidel@gmail.com> 1780507417 -0700\tcommit: feat(daemon): M3c-C3 adapter ripple-update + adapter content signing\n03a245c2085d77ac89ba31b4b90f707b085a70cc a14639a405d9f9122323a77cb847f17643550b71 Reavo End <decidel@gmail.com> 1780507695 -0700\tcommit: docs(m3c): M3c-C4 activation sweep + M3 complete + M4 stub\na14639a405d9f9122323a77cb847f17643550b71 ca6522b5ddfea0d433a3aa3732595b0d30f73b52 Reavo End <decidel@gmail.com> 1780508932 -0700\tcommit: docs(m4): expand M4-PLAN D-task layer at M4 start\nca6522b5ddfea0d433a3aa3732595b0d30f73b52 c47d2c6515c40e7712b4f40bb22c48043ddfce5e Reavo End <decidel@gmail.com> 1780508944 -0700\tcommit: feat(net): M4-D0 scaffold spt-net crate\nc47d2c6515c40e7712b4f40bb22c48043ddfce5e c49e522a25c92cae85e5687440b7e5091a625057 Reavo End <decidel@gmail.com> 1780509753 -0700\tcommit: docs(m4): record D6 cross-node merge-model decision (#7)\nc49e522a25c92cae85e5687440b7e5091a625057 3f726edf1df960cdc81c874563d317e035898dd0 Reavo End <decidel@gmail.com> 1780509775 -0700\tcommit: feat(net): M4-D1 iroh endpoint + identity binding + relay policy\n3f726edf1df960cdc81c874563d317e035898dd0 51981c4ecc51864a71748e530cb29023fa47f1e6 Reavo End <decidel@gmail.com> 1780509976 -0700\tcommit: feat(net): M4-D1c mDNS LAN discovery + connect-by-id\n51981c4ecc51864a71748e530cb29023fa47f1e6 d4c04d647e19af22fda4d163ec4c3ca60ca4ac64 Reavo End <decidel@gmail.com> 1780513837 -0700\tcommit: docs(m4): psyche outbound model — ADR-0012 + CONTEXT + hazard 7.3 + D7.5\nd4c04d647e19af22fda4d163ec4c3ca60ca4ac64 bfacfb855780e9e696ced4a638a6543a7e219593 Reavo End <decidel@gmail.com> 1780514489 -0700\tcommit: docs(m4): capture daemon non-blocking-scheduling invariant (hazard 7.4)\nbfacfb855780e9e696ced4a638a6543a7e219593 56f2190ba24ee7d3a337b0f1f4dd486d5756d660 Reavo End <decidel@gmail.com> 1780515295 -0700\tcommit: feat(net): M4-D2a TOTP (RFC 6238) + register D2 pairing hazard reqs\n56f2190ba24ee7d3a337b0f1f4dd486d5756d660 597786e5e85b6bc3cfc0ae02745c88931877e39b Reavo End <decidel@gmail.com> 1780515694 -0700\tcommit: feat(net): M4-D2b SPAKE2 handshake + pairing-transcript binding (#12)\n597786e5e85b6bc3cfc0ae02745c88931877e39b 75f967c960709596dc76ba852ac741cf767c3008 Reavo End <decidel@gmail.com> 1780516074 -0700\tcommit: feat(store): M4-D2c subnet seed store + epoch rotation (#10)\n75f967c960709596dc76ba852ac741cf767c3008 b0bdaab439be50747f60ee6e10baafadf049a54c Reavo End <decidel@gmail.com> 1780516898 -0700\tcommit: feat(net): M4-D2d subnet-global pairing rate limit (#11)\nb0bdaab439be50747f60ee6e10baafadf049a54c 1df7331dfe1cc86c16b0958184b8700ff7ed6586 Reavo End <decidel@gmail.com> 1780517211 -0700\tcommit: feat(store): M4-D2e trust store TOFU + warn-on-change (REQ-PAIR-2)\n1df7331dfe1cc86c16b0958184b8700ff7ed6586 f4042352d7e59ac2da6949bd1b19388e165d9298 Reavo End <decidel@gmail.com> 1780517341 -0700\tcommit: docs(m4): JIT plan for D2-wire pairing ALPN driver\nf4042352d7e59ac2da6949bd1b19388e165d9298 2ae22794cb42352431f03c8353938aeca7798719 Reavo End <decidel@gmail.com> 1780518704 -0700\tcommit: @\n2ae22794cb42352431f03c8353938aeca7798719 08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 Reavo End <decidel@gmail.com> 1780518748 -0700\tcommit (amend): feat(net): M4-D2-wire pairing ALPN ceremony driver (REQ-PAIR-1)\n08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 452358d79c2238142e99956e32dc3ce340fcee1a Reavo End <decidel@gmail.com> 1780522150 -0700\tcommit: feat(net): M4-D2f multi-subnet ceremony + seed transfer (REQ-PAIR-4/5)\n452358d79c2238142e99956e32dc3ce340fcee1a 72570d5c6e191afa75ade22787422ae8ebe7708a Reavo End <decidel@gmail.com> 1780523359 -0700\tcommit: docs(m4): JIT plan for D2g elevation-gated show-totp\n72570d5c6e191afa75ade22787422ae8ebe7708a fb201d61bd3b42deceb14ddafcab0c971197f723 Reavo End <decidel@gmail.com> 1780524004 -0700\tcommit: @\nfb201d61bd3b42deceb14ddafcab0c971197f723 f2cb019ef3ef143b3319ce7565ef9eebe349bc46 Reavo End <decidel@gmail.com> 1780524028 -0700\tcommit (amend): feat(net): M4-D2g elevation-gated `spt pair show-totp` (REQ-PAIR-3/6)\nf2cb019ef3ef143b3319ce7565ef9eebe349bc46 a82446f8077aec0f932ec70ccab16af8b2e9d94e Reavo End <decidel@gmail.com> 1780524412 -0700\tcommit: fix(net): silence D2g Elevation::Unknown dead_code on unix\na82446f8077aec0f932ec70ccab16af8b2e9d94e 8b47fa0051613de1fd9e5865de4259a39857615d Reavo End <decidel@gmail.com> 1780525691 -0700\tcommit: feat(net): M4-D3a/b subnet registry model + epoch lease (REQ-INST-7, hazard #8)\n8b47fa0051613de1fd9e5865de4259a39857615d de8167a763b07bb7d8f99a963ab05a88c031b344 Reavo End <decidel@gmail.com> 1780526470 -0700\tcommit: feat(net): M4-D3c qualified addressing + resolution policy (REQ-INST-10)\nde8167a763b07bb7d8f99a963ab05a88c031b344 afdc7340b73cf2e845a1fed204b0722737c2f20a Reavo End <decidel@gmail.com> 1780527380 -0700\tcommit: feat(net): M4-D3d join-time bare-id collision check (REQ-INST-9)\nafdc7340b73cf2e845a1fed204b0722737c2f20a affa65c6ce943e945eafc6a044ad8e078f9a050d Reavo End <decidel@gmail.com> 1780528414 -0700\tcommit: feat(net): M4-D3e per-(endpoint,subnet) visibility + sync gate (REQ-INST-12/13)\naffa65c6ce943e945eafc6a044ad8e078f9a050d 2766898be6745c9ffa82a19bd88d6014f1bc388b Reavo End <decidel@gmail.com> 1780529038 -0700\tcommit: feat(net): M4-D3f rename ripple — registry rows + perch tree + CLI (REQ-INST-11)\n2766898be6745c9ffa82a19bd88d6014f1bc388b 2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b Reavo End <decidel@gmail.com> 1780529157 -0700\tcommit: docs(m4): JIT plan for D4 broker QUIC ownership\n2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b 9601fe71d1a745bec10933cea66395ee8d14bdc9 Reavo End <decidel@gmail.com> 1780531019 -0700\tcommit: feat(net): M4-D4a broker-owned iroh endpoint + net IPC frames (ADR-0004 SB)\n9601fe71d1a745bec10933cea66395ee8d14bdc9 fa9b26f0d08170bc164684bce08548ae1f919312 Reavo End <decidel@gmail.com> 1780531349 -0700\tcommit: test(net): loopback-scoped sockets for hermetic endpoint tests\nfa9b26f0d08170bc164684bce08548ae1f919312 88510c5014255158b86341e60816db4af982ffa7 Reavo End <decidel@gmail.com> 1780531487 -0700\tcommit (amend): test(net): loopback-scoped sockets for hermetic endpoint tests\n88510c5014255158b86341e60816db4af982ffa7 518ee0ddf788038eedd874ea348c809060801e94 Reavo End <decidel@gmail.com> 1780531500 -0700\tcommit (amend): test(net): loopback-scoped sockets + no portmapper for hermetic endpoint tests\n518ee0ddf788038eedd874ea348c809060801e94 20b05788920237d2df7a6da452d55dc9e510947c Reavo End <decidel@gmail.com> 1780531746 -0700\tcommit: fix(store): pid 0 reads dead on every OS - D3f Linux CI regression\n20b05788920237d2df7a6da452d55dc9e510947c 0f67076f3f6d249179869de8224b26d02eeacdc1 Reavo End <decidel@gmail.com> 1780532105 -0700\tcommit: fix(store): deterministic rename ripple order - read_dir is OS-dependent\n0f67076f3f6d249179869de8224b26d02eeacdc1 0067170f7f1b96c3b651065e354aad0f6568abc8 Reavo End <decidel@gmail.com> 1780533148 -0700\tcommit: feat(net): M4-D4b gapless exactly-once QUIC stream survival (Spike #3 A-D)\n0067170f7f1b96c3b651065e354aad0f6568abc8 5bfa9e770aa50969a045072ee7ab0af4c4e382a3 Reavo End <decidel@gmail.com> 1780535351 -0700\tcommit: ci: phase 1 of self-host migration - windows leg on hfenduleam runner\n5bfa9e770aa50969a045072ee7ab0af4c4e382a3 05f08bc60e8fccce2416a9737b39d2142e2f70c5 Reavo End <decidel@gmail.com> 1780535444 -0700\tcommit: ci: self-hosted runner owns its rust toolchain; fix runner PATH\n05f08bc60e8fccce2416a9737b39d2142e2f70c5 02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 Reavo End <decidel@gmail.com> 1780536384 -0700\tcommit: ci: phase 2 of self-host migration - linux leg + traceability on gravity\n02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 d70ced7d793e5a96185e5b178974545914ad5431 Reavo End <decidel@gmail.com> 1780536763 -0700\tcommit: ci: traceability uses gravity's source-built traceable-reqs\nd70ced7d793e5a96185e5b178974545914ad5431 1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 Reavo End <decidel@gmail.com> 1780537825 -0700\tcommit: feat(net): M4-D4c PresenceChannel broker seam (REQ-EP-4)\n1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 237b1bb42a59cd7e112c8bf00badb4078ed7133c Reavo End <decidel@gmail.com> 1780538471 -0700\tcommit: feat(net): M4-D4d registry replication over the wire\n237b1bb42a59cd7e112c8bf00badb4078ed7133c 924e6b4bc71f742ba422bddade056ae24a4010fe Reavo End <decidel@gmail.com> 1780538587 -0700\tcommit: docs: begin fault-injection acceptance matrix (ADR-0002 SERIOUS #6, M4-D4 piece 5)\n924e6b4bc71f742ba422bddade056ae24a4010fe 017d103f02a7229eee2a27f60c1b2bb6f776c4f3 Reavo End <decidel@gmail.com> 1780538932 -0700\tcommit: fix(store): registry open creates the owlery dir — KNOWN-HAZARDS 4.9\n017d103f02a7229eee2a27f60c1b2bb6f776c4f3 1cbeb6d32573fd75c22125a8607c03b6270fad1b Reavo End <decidel@gmail.com> 1780539806 -0700\tcommit: docs: M4-D5 JIT plan — WAN msg, remote attach, reach-back, access whitelist\n1cbeb6d32573fd75c22125a8607c03b6270fad1b caab43332f3dec1bfdba4c526883ccd7c805a8dc Reavo End <decidel@gmail.com> 1780540739 -0700\tcommit: feat(net): M4-D5a WAN message delivery over the broker wire\ncaab43332f3dec1bfdba4c526883ccd7c805a8dc ef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 Reavo End <decidel@gmail.com> 1780541062 -0700\tcommit: fix(test): wanmsg life-1 loop satisfies clippy::never_loop (CI rustc 1.96)\nef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 71e301597060f3104ae565769d013c097fc4c864 Reavo End <decidel@gmail.com> 1780541328 -0700\tcommit: ci: bump actions/checkout v4 -> v5 (Node 20 deprecation, forced Node 24 on 2026-06-16)\n71e301597060f3104ae565769d013c097fc4c864 4ea3b3733b356d989b7d8af803dbb41dddbf3bcb Reavo End <decidel@gmail.com> 1780542941 -0700\tcommit: feat(net): M4-D5b remote terminal attach (REQ-INST-8)\n4ea3b3733b356d989b7d8af803dbb41dddbf3bcb 269b8ef5bffa3b003519d98121c931c32054cbdd Reavo End <decidel@gmail.com> 1780544566 -0700\tcommit: feat(net): M4-D5c remote-drive detection + file transfer (REQ-REACH-1)\n269b8ef5bffa3b003519d98121c931c32054cbdd fcad41ca27210276606356d72ab4abde0a3d65c0 Reavo End <decidel@gmail.com> 1780544873 -0700\tcommit: fix(test): attach restart ground-truth compares prefixes through the marker\nfcad41ca27210276606356d72ab4abde0a3d65c0 f49aba1984a28ca066345bfe218b839d5a5ff8e0 Reavo End <decidel@gmail.com> 1780545918 -0700\tcommit: feat(sec): M4-D5d endpoint access whitelist (REQ-SEC-1, ADR-0009)\nf49aba1984a28ca066345bfe218b839d5a5ff8e0 8f4ec2465b477617a9cd359a983ec41d93f0e099 Reavo End <decidel@gmail.com> 1780549041 -0700\tcommit: docs: M4-D6 design locked — ADR-0013 Psyche-reconciled context merge\n8f4ec2465b477617a9cd359a983ec41d93f0e099 8b42452ce7f8277b239242efd853dc93e763fc17 Reavo End <decidel@gmail.com> 1780549527 -0700\tcommit: feat(store): M4-D6a BranchStore + two-tier ContextStore + project_id (REQ-STORE-1, ADR-0011/0013)\n8b42452ce7f8277b239242efd853dc93e763fc17 1ef980218554bf2f7ba8c8cd7f7e210e2b34129f Reavo End <decidel@gmail.com> 1780550270 -0700\tcommit: feat(live): M4-D6a two-tier context routing + rename context arm (REQ-INST-2)\n1ef980218554bf2f7ba8c8cd7f7e210e2b34129f 7e9c2b54d68fd99210f3e5b133a64d254285ab76 Reavo End <decidel@gmail.com> 1780550799 -0700\tcommit: feat(store): M4-D6b distributed precedence — marker v2, version vectors, conflict artifacts\n7e9c2b54d68fd99210f3e5b133a64d254285ab76 50620e4a4834900120d725a13d0c6059ae841e6c Reavo End <decidel@gmail.com> 1780551377 -0700\tcommit: feat(live): M4-D6b Psyche reconcile + bounded turn driver (D7.5a pulled forward)\n50620e4a4834900120d725a13d0c6059ae841e6c 1ba6e06453577d88aaf1c1382a58a4a379069b1a Reavo End <decidel@gmail.com> 1780551649 -0700\tcommit: fix(test): reconcile unix mock command nested single quotes broke manifest TOML\n1ba6e06453577d88aaf1c1382a58a4a379069b1a f08aedcb3cd2615712f3f30daf07a35ee9870aac Reavo End <decidel@gmail.com> 1780552853 -0700\tcommit: feat(store): M4-D6c-1 bundle sync core — BranchStore bundle plumbing + per-file vector merge driver (REQ-NET-3)\nf08aedcb3cd2615712f3f30daf07a35ee9870aac fa97b80ef3a560c4fd77311b202cf982d7674abc Reavo End <decidel@gmail.com> 1780556100 -0700\tcommit: feat(net,daemon): M4-D6c-2 bundle sync over broker streams — wire records, server-side gate, reconcile wiring (REQ-NET-3, REQ-INST-5)\nfa97b80ef3a560c4fd77311b202cf982d7674abc 0d05e5c5512ba64986d032f543841b9f3301fa72 Reavo End <decidel@gmail.com> 1780558624 -0700\tcommit: feat(net,daemon): M4-D7-1 update propagation wire + staged-release cache (REQ-UPD-1)\n0d05e5c5512ba64986d032f543841b9f3301fa72 b67d4321b85d3633b67124933960f8ed25110150 Reavo End <decidel@gmail.com> 1780558959 -0700\tcommit: feat(daemon): M4-D7-2 update propagation serve/request drivers + E2E (REQ-UPD-1)\nb67d4321b85d3633b67124933960f8ed25110150 9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 Reavo End <decidel@gmail.com> 1780559260 -0700\tcommit: docs(plan): mark M4-D7 complete (both slices CI-green)\n9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 6fac5390690ed9c6ed759cca15e289d854009783 Reavo End <decidel@gmail.com> 1780559950 -0700\tcommit: feat(proto,live): M4-D7.5-1 event-type taxonomy + Psyche intent parser (ADR-0012)\n6fac5390690ed9c6ed759cca15e289d854009783 3f771e11c7d4762fc5721945866982dcd21e4a9c Reavo End <decidel@gmail.com> 1780560620 -0700\tcommit: feat(daemon): M4-D7.5-2 Psyche outbound relay + sanitize boundary (REQ-HAZARD-PSYCHE-OUTBOUND-PROXY)\n3f771e11c7d4762fc5721945866982dcd21e4a9c e405b4329b3c312b6bf73ca9b106de4f86c8f155 Reavo End <decidel@gmail.com> 1780560933 -0700\tcommit: docs(plan): mark M4-D7.5 complete (both slices CI-green)\ne405b4329b3c312b6bf73ca9b106de4f86c8f155 e1120e4938064baee56ea0cbc5b8a4fc7c390df0 Reavo End <decidel@gmail.com> 1780561369 -0700\tcommit: docs(plan): draft M4-D8-PLAN (subnet notification primitive, ADR-0007)\ne1120e4938064baee56ea0cbc5b8a4fc7c390df0 9a6548fbd0c85a20715632a62ff4c5c7c767d142 Reavo End <decidel@gmail.com> 1780561600 -0700\tcommit: feat(store): M4-D8-1 notif store + semilattice merge (ADR-0007)\n9a6548fbd0c85a20715632a62ff4c5c7c767d142 f612b3ff4f677b56048a2d6c1294ea62054e513b Reavo End <decidel@gmail.com> 1780562053 -0700\tcommit: feat(net,daemon): M4-D8-2 notif replication record + gated feed drivers + E2E (ADR-0007)\nf612b3ff4f677b56048a2d6c1294ea62054e513b 45484109079454c594a20515fe730480d0ad3db5 Reavo End <decidel@gmail.com> 1780562746 -0700\tcommit: feat(daemon,spt): M4-D8-3 notif first-fire + resurface engine + producers (REQ-NOTIF-1)\n45484109079454c594a20515fe730480d0ad3db5 126d8434d0c56ed0f3092d477a251c188a60e6ad Reavo End <decidel@gmail.com> 1780563316 -0700\tcommit: feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)\n126d8434d0c56ed0f3092d477a251c188a60e6ad b8d75af3cf12d899aff2b1af51caf44482d141a6 Reavo End <decidel@gmail.com> 1780563620 -0700\tcommit: docs(plan): mark M4-D8 complete (all four slices CI-green)\nb8d75af3cf12d899aff2b1af51caf44482d141a6 a71d23a3102a01168a47a47491489057cead4053 Reavo End <decidel@gmail.com> 1780570329 -0700\tcommit: docs(plan): draft M4-D9 JIT plan (closeout: loops, dormancy, INST-14/15, two-host)\na71d23a3102a01168a47a47491489057cead4053 ea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 Reavo End <decidel@gmail.com> 1780571295 -0700\tcommit: feat(daemon): M4-D9-1-1 inbound net-stream dispatch (production serve loops)\nea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 822d3ce76ce7de9b15bfdbf835ac244c249a6157 Reavo End <decidel@gmail.com> 1780572144 -0700\tcommit: feat(daemon,net): M4-D9-1-2 registry host + outbound peer pump (self-driving subnet)\n822d3ce76ce7de9b15bfdbf835ac244c249a6157 b15cf2cee93b33ac5f6e9bfb7534428b1cb05560 Reavo End <decidel@gmail.com> 1780572683 -0700\tcommit: feat(daemon): M4-D9-1-3 production VerifyPolicy + update pump leg (REQ-UPD-1/2/4)\nb15cf2cee93b33ac5f6e9bfb7534428b1cb05560 af7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 Reavo End <decidel@gmail.com> 1780572980 -0700\tcommit: feat(spt): M4-D9-1-4 spt send WAN leg (registry-resolved off-node delivery)\naf7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 0dd85d5bae817201f4d239d04ddf4a0431e6ec2f Reavo End <decidel@gmail.com> 1780573536 -0700\tcommit: feat(daemon,net): M4-D9-1-5 attach/xfer dispatch arms (remote-drive + file transfer production serve)\n0dd85d5bae817201f4d239d04ddf4a0431e6ec2f cb81e831eabe86dd7da4c64658eb2caf1ca3c99e Reavo End <decidel@gmail.com> 1780573894 -0700\tcommit: docs(plan): mark M4-D9-1 complete (five CI-green sub-slices)\ncb81e831eabe86dd7da4c64658eb2caf1ca3c99e 031578398664aa6559552f2dfd0d399928d5684d Reavo End <decidel@gmail.com> 1780574749 -0700\tcommit: feat(daemon,store,net): M4-D9-2-1 resting-state model (REQ-INST-3 activated)\n031578398664aa6559552f2dfd0d399928d5684d 8a7afd6792b75562b7699fc89b36c79d9db2b656 Reavo End <decidel@gmail.com> 1780575335 -0700\tcommit: feat(daemon): M4-D9-2-2 transition echo + wake effects (REQ-INST-4 activated)\n8a7afd6792b75562b7699fc89b36c79d9db2b656 56a98db5b944dc51f7c458c18f6f671332b05e2d Reavo End <decidel@gmail.com> 1780575928 -0700\tcommit: feat(daemon,spt): M4-D9-2-3 resting-edge production feeds + spt suspend/wake\n56a98db5b944dc51f7c458c18f6f671332b05e2d a52dacbe13b358978b89347801db32701aa5f15e Reavo End <decidel@gmail.com> 1780576258 -0700\tcommit: docs(plan): mark M4-D9-2 complete (three CI-green sub-slices)\na52dacbe13b358978b89347801db32701aa5f15e 785002e707f71652c714a13d71e6d5c100db003a Reavo End <decidel@gmail.com> 1780576758 -0700\tcommit: feat(daemon,docs): M4-D9-3 dormancy budget measured + policy locked [budget]\n785002e707f71652c714a13d71e6d5c100db003a 6f61a89677d039dc9816f2f736a05b3915f75e00 Reavo End <decidel@gmail.com> 1780577582 -0700\tcommit: docs(budget): M4-D9-3 gravity numbers + per-OS budget steps [budget]\n6f61a89677d039dc9816f2f736a05b3915f75e00 650c7fb326ed3478f5661891641a32b1b712ce19 Reavo End <decidel@gmail.com> 1780578309 -0700\tcommit: fix(ci): ASCII-only string in the Windows budget step [budget]\n650c7fb326ed3478f5661891641a32b1b712ce19 0ea318b3d77cdcc84c56fe45dad84de095baff96 Reavo End <decidel@gmail.com> 1780579051 -0700\tcommit: feat(net,daemon,store,spt): M4-D9-4 resource advertisement (REQ-INST-14 activated)\n0ea318b3d77cdcc84c56fe45dad84de095baff96 e69f59add554232ee75321a10a1641ab2bbece91 Reavo End <decidel@gmail.com> 1780579804 -0700\tcommit: feat(store,spt,daemon): M4-D9-5-1 immutable home subnet at creation\ne69f59add554232ee75321a10a1641ab2bbece91 0b30dea4a0f4829908bb0de71db07faafaa91e98 Reavo End <decidel@gmail.com> 1780580326 -0700\tcommit: feat(store,spt): M4-D9-5-2 spt fork — copy-then-diverge (REQ-INST-15 activated)\n0b30dea4a0f4829908bb0de71db07faafaa91e98 92fc261c83af6f2a09cf137b6f8ed9150bd21b32 Reavo End <decidel@gmail.com> 1780580669 -0700\tcommit: docs(plan): mark M4-D9-3/4/5 complete (budget measured, resources, home+fork)\n92fc261c83af6f2a09cf137b6f8ed9150bd21b32 107240eea81599ba191fb2cd2fe93d9f2c8a0390 Reavo End <decidel@gmail.com> 1780582455 -0700\tcommit: feat(net,daemon,ci,docs): M4-D9-6-1 two-host rig ladder — env-gated int tests + runbook\n107240eea81599ba191fb2cd2fe93d9f2c8a0390 a86371c9cfd87639c4d5dd5e2c82d464bc028ed0 Reavo End <decidel@gmail.com> 1780583265 -0700\tcommit: test(rig): two-host ladder rig run + durable flake ledger [twohost]\na86371c9cfd87639c4d5dd5e2c82d464bc028ed0 2ba79061f05c5b273485fac83671cf2bdbc4681d Reavo End <decidel@gmail.com> 1780583853 -0700\tcommit: docs(reqs,plan): M4-D9-6 complete — rig proof evidence, int stages live, gh-interim retired\n2ba79061f05c5b273485fac83671cf2bdbc4681d becb6ecb2a5458879c3c4f77139cd1ec31e39a36 Reavo End <decidel@gmail.com> 1780584205 -0700\tcommit: docs(plan,reqs): M4-D9-7 closeout sweep — M4 DELIVERED\nbecb6ecb2a5458879c3c4f77139cd1ec31e39a36 a6f5cabac5f0a8d340a5aa81206f94e096d215a5 Reavo End <decidel@gmail.com> 1780586451 -0700\tcommit: docs(plan): author M5-PLAN — scope locked at M5 start via user forks\na6f5cabac5f0a8d340a5aa81206f94e096d215a5 2a59e7d356d6329b2b35e25095ec355ee384fa18 Reavo End <decidel@gmail.com> 1780586580 -0700\tcommit: docs(reqs,roadmap): M5-D0 — scope alignment + requirement registry prep\n\n[Showing lines 1-203 of 1502. Use :204 to continue]"}],"details":{"truncation":{"content":"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 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780296118 -0700\tcheckout: moving from main to dev-freeform\n4fa29498d2552f277e918219f74d9c75183a6921 905c6b438f781847bcfda59501d30e073c85a832 Reavo End <decidel@gmail.com> 1780296234 -0700\tcommit: docs: add M0-PLAN.md (just-in-time task plan) + roadmap pointer\n905c6b438f781847bcfda59501d30e073c85a832 d6aa312316a28997507bf9738e9d8300ebb08cad Reavo End <decidel@gmail.com> 1780297115 -0700\tcommit: @\nd6aa312316a28997507bf9738e9d8300ebb08cad e8ec9803ae6e26efba6f2754579d1e97a7b6506e Reavo End <decidel@gmail.com> 1780297126 -0700\tcommit (amend): feat(spt-proto): M0 T2 — EVENT envelope grammar (compose + parse)\ne8ec9803ae6e26efba6f2754579d1e97a7b6506e f3a55450e27f9c7b96a6bc3e2a00929144a620e6 Reavo End <decidel@gmail.com> 1780297309 -0700\tcommit: feat(spt-proto): M0 T3 — EVENT-PART chunker + byte-exact reassembly\nf3a55450e27f9c7b96a6bc3e2a00929144a620e6 c04e07a3545e5a01de0a6135073584008e9a7eb6 Reavo End <decidel@gmail.com> 1780297415 -0700\tcommit: feat(spt-proto): M0 T4 — endpoint type taxonomy (open + agent/Shell seam)\nc04e07a3545e5a01de0a6135073584008e9a7eb6 e93037d11f41a72c25f451fa637c63c7b54226cc Reavo End <decidel@gmail.com> 1780297723 -0700\tcommit: feat(spt-proto): M0 T5 — Ed25519 identity primitive (clean-room)\ne93037d11f41a72c25f451fa637c63c7b54226cc 260f337d330600e8d4bfa66e06a0850d4f861b37 Reavo End <decidel@gmail.com> 1780297787 -0700\tcommit: feat(spt-proto): M0 T6 — wire-protocol version + N-1 compat window\n260f337d330600e8d4bfa66e06a0850d4f861b37 1821000dd20a7c5bd85257d72c26fd1e346fbef7 Reavo End <decidel@gmail.com> 1780297902 -0700\tcommit: feat(spt-proto): M0 T7 — payload model (typed commands + binary blobs)\n1821000dd20a7c5bd85257d72c26fd1e346fbef7 7fdc26c913420e8ca6114071fb7089520241cd84 Reavo End <decidel@gmail.com> 1780298723 -0700\tcommit: feat(spt-store): M0 T8 — atomic write (EBUSY retry) + UNC-path strip\n7fdc26c913420e8ca6114071fb7089520241cd84 e81c06f94a9a7278c1f067c6743a959110293246 Reavo End <decidel@gmail.com> 1780298874 -0700\tcommit: feat(spt-store): M0 T9 — SQLite spool (deferred-aware drain)\ne81c06f94a9a7278c1f067c6743a959110293246 459c51b9ba98c64a991e8384889358bf28c403d9 Reavo End <decidel@gmail.com> 1780299008 -0700\tcommit: feat(spt-store): M0 T10 — perch layout + single path resolver\n459c51b9ba98c64a991e8384889358bf28c403d9 e7beacafaafca3ab964711318eb20c102390992b Reavo End <decidel@gmail.com> 1780299239 -0700\tcommit: feat(spt-store): M0 T11 — registry persistence + stale-entry cleanup\ne7beacafaafca3ab964711318eb20c102390992b d98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 Reavo End <decidel@gmail.com> 1780299365 -0700\tcommit: feat(spt-store): M0 T12 — info.json shape + torn-read tolerance\nd98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 be5ed5806934dfe8729330e7596bb101226524f2 Reavo End <decidel@gmail.com> 1780299463 -0700\tcommit: ci(M0 T13): build + test + clippy matrix and traceable-reqs hard gate\nbe5ed5806934dfe8729330e7596bb101226524f2 849124569d1e726de50341ed9e01b6fd3cdcdc72 Reavo End <decidel@gmail.com> 1780299589 -0700\tcommit: chore(M0 T14): activation sweep — activate R-ARCH-1, mark M0 complete\n849124569d1e726de50341ed9e01b6fd3cdcdc72 f388817ccbb0a19dc0d8d2e245530e41da065b64 Reavo End <decidel@gmail.com> 1780300690 -0700\tcommit: docs: add M1-PLAN.md (local messaging + binary) + roadmap pointer\nf388817ccbb0a19dc0d8d2e245530e41da065b64 bdaf52ab67490ac500963533f8927456f3a97d6a Reavo End <decidel@gmail.com> 1780301333 -0700\tcommit: chore(M1 T0): register REQ-MSG-1/2/3 + scaffold spt-msg crate\nbdaf52ab67490ac500963533f8927456f3a97d6a d3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 Reavo End <decidel@gmail.com> 1780301461 -0700\tcommit: feat(spt-msg): M1 T1 — TCP wire framing + loopback listener\nd3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c Reavo End <decidel@gmail.com> 1780301593 -0700\tcommit: feat(spt-msg): M1 T2 — TCP-first/spool-fallback delivery + routing\n2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c 5eb4c37ed0b808e187fcaea91420edfc0dc95a30 Reavo End <decidel@gmail.com> 1780301696 -0700\tcommit: feat(spt-msg): M1 T3 — ready-agent lifecycle\n5eb4c37ed0b808e187fcaea91420edfc0dc95a30 bf319984d10a9a29525c1cfd325d604f8bd1b585 Reavo End <decidel@gmail.com> 1780301798 -0700\tcommit: feat(spt-msg): M1 T4 — ring (blocking send-and-wait-for-reply)\nbf319984d10a9a29525c1cfd325d604f8bd1b585 6e167121248cf8d4df9d65255f5bc24aeae01863 Reavo End <decidel@gmail.com> 1780301863 -0700\tcommit: feat(spt-msg): M1 T5 — deferred send (hook channel)\n6e167121248cf8d4df9d65255f5bc24aeae01863 356b8c7969468a6ec5c5779ec67c91a790d0b0c8 Reavo End <decidel@gmail.com> 1780302103 -0700\tcommit: feat(spt): M1 T6-T8 — spt binary + CLI (send/ring/ready/poll/list/stop/whoami)\n356b8c7969468a6ec5c5779ec67c91a790d0b0c8 af0fff0df8cff829fe0fab0a4696b612b25dd4e9 Reavo End <decidel@gmail.com> 1780302255 -0700\tcommit: test(spt-msg): M1 T9-T10 — killer-quickstart E2E + activation sweep\naf0fff0df8cff829fe0fab0a4696b612b25dd4e9 3b70fed82bfa77502df09d3e34eef140ce125b12 Reavo End <decidel@gmail.com> 1780304902 -0700\tcommit: fix(spt-msg): isolate unit tests to a temp SPT_HOME (stop polluting the live owlery)\n3b70fed82bfa77502df09d3e34eef140ce125b12 bdb719373191d5abb1d698dff0bdcd176d3008a7 Reavo End <decidel@gmail.com> 1780305400 -0700\tcommit: refactor(spt-store)+docs: default home spt-core/ + separate spt-plugin from spt-core scope\nbdb719373191d5abb1d698dff0bdcd176d3008a7 7e3acfbfc23f41bafd8c8514a830f9e7389cc14e Reavo End <decidel@gmail.com> 1780308344 -0700\tcommit: docs(M2a): author M2a-PLAN.md (harness contract) + split M2 in ROADMAP\n7e3acfbfc23f41bafd8c8514a830f9e7389cc14e 637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 Reavo End <decidel@gmail.com> 1780310110 -0700\tcommit: docs: reconcile daemon-hosted endpoint liveness (KNOWN-HAZARDS 2.5)\n637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 67414530a8b4d5fb2ff6aabf357c200ff56badca Reavo End <decidel@gmail.com> 1780310742 -0700\tcommit: feat(spt-runtime): M2a T0 — scaffold harness-contract crate\n67414530a8b4d5fb2ff6aabf357c200ff56badca ae2337649d66fb68714460a6bd66dee918dd6ce7 Reavo End <decidel@gmail.com> 1780311003 -0700\tcommit: feat(spt-runtime): M2a T1 — runtime-manifest schema + parse + validate\nae2337649d66fb68714460a6bd66dee918dd6ce7 fecd6d26f69cff0bfbdc56f29739d4bb6d581cac Reavo End <decidel@gmail.com> 1780311256 -0700\tcommit: feat(spt-runtime): M2a T2 — AgentRuntime seam + ManifestRuntime\nfecd6d26f69cff0bfbdc56f29739d4bb6d581cac 281eb8dd3c542f586ff8a8f827a5e6e0288b0263 Reavo End <decidel@gmail.com> 1780311614 -0700\tcommit: feat(spt): M2a T3 — `spt api` dispatch group + local-api-auth\n281eb8dd3c542f586ff8a8f827a5e6e0288b0263 d6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e Reavo End <decidel@gmail.com> 1780312122 -0700\tcommit: feat(spt): M2a T4 — harness-hosted startup (seed → listen → bind)\nd6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e bae7124badb654c27596ca14df5ce623db50303c Reavo End <decidel@gmail.com> 1780312254 -0700\tcommit: feat(spt): M2a T5 — delivery + activity (state / echo-gate / poll / inject)\nbae7124badb654c27596ca14df5ce623db50303c 42af360e619f7510d9c492507a9864c48b41ac1e Reavo End <decidel@gmail.com> 1780312367 -0700\tcommit: feat(spt): M2a T6 — worker perches + cascade-wipe guard\n42af360e619f7510d9c492507a9864c48b41ac1e d555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc Reavo End <decidel@gmail.com> 1780312565 -0700\tcommit: feat(spt): M2a T7 — reporting + boundary surface\nd555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc 146a7b83f67685fbb4d3fd9090a1614298357e22 Reavo End <decidel@gmail.com> 1780312746 -0700\tcommit: feat(mock-adapter): M2a T8 — generic mock adapter (manifest + helper)\n146a7b83f67685fbb4d3fd9090a1614298357e22 504b346c73583b4ddcb83e63da92fd7211c0d2ab Reavo End <decidel@gmail.com> 1780313178 -0700\tcommit: test(spt): M2a T9 — contract E2E through the mock adapter\n504b346c73583b4ddcb83e63da92fd7211c0d2ab dbdbabe8f82ce9d85a39dba35deda037d4c480e3 Reavo End <decidel@gmail.com> 1780313373 -0700\tcommit: chore(M2a): T10 — activation sweep + ROADMAP (M2a delivered)\ndbdbabe8f82ce9d85a39dba35deda037d4c480e3 104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 Reavo End <decidel@gmail.com> 1780314087 -0700\tcommit: docs(M2b): author M2b-PLAN.md (live-agent lifecycle)\n104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 e0b2c80024315541aa2415ca6ad0e6f3da43b895 Reavo End <decidel@gmail.com> 1780314638 -0700\tcommit: feat(spt-live): M2b T0 — scaffold live-agent lifecycle crate\ne0b2c80024315541aa2415ca6ad0e6f3da43b895 a9a079bc5dd69fbb77da2b46d7e0cdf70707e69e Reavo End <decidel@gmail.com> 1780315057 -0700\tcommit: feat(spt-live): M2b T1 — history subsystem (fetcher/locate-normalize/native)\na9a079bc5dd69fbb77da2b46d7e0cdf70707e69e 4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b Reavo End <decidel@gmail.com> 1780315333 -0700\tcommit: feat(spt-live): M2b T2 — spawn-psyche seam (nested perch + owned prompt)\n4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b c8bfe6e24c5752e2168c3f020dbc8aae2b628852 Reavo End <decidel@gmail.com> 1780315707 -0700\tcommit: feat(spt-live): M2b T3 — echo-commune seam (history brief → commune drop)\nc8bfe6e24c5752e2168c3f020dbc8aae2b628852 91380ab5dc0accc2286597c45efb9a6e620133a5 Reavo End <decidel@gmail.com> 1780315958 -0700\tcommit: feat(spt-live): M2b T4 — drop-file ingest (single-writer + precedence guard)\n91380ab5dc0accc2286597c45efb9a6e620133a5 5d085e8c480e1b79f6a6720d68ac425f002478a8 Reavo End <decidel@gmail.com> 1780316170 -0700\tcommit: feat(spt-live): M2b T5 — pulse heartbeat (interim per-tick driver)\n5d085e8c480e1b79f6a6720d68ac425f002478a8 86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 Reavo End <decidel@gmail.com> 1780316382 -0700\tcommit: feat(spt-live): M2b T6 — signoff + boundary ordering invariants\n86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 5f1a344bc06bd14d2f9025250ad2444d9994380b Reavo End <decidel@gmail.com> 1780316562 -0700\tcommit: feat(spt-live): M2b T7 — resume-session seam (continue-existing / fresh-preload)\n5f1a344bc06bd14d2f9025250ad2444d9994380b b91168c80df0f6be33b17a3d3031b2a748818cd7 Reavo End <decidel@gmail.com> 1780316889 -0700\tcommit: feat(spt): M2b T8 — api listen lifecycle wiring + graceful shutdown\nb91168c80df0f6be33b17a3d3031b2a748818cd7 27563a5104eabf32c4266fd89c90b2e5e0b38ec5 Reavo End <decidel@gmail.com> 1780317197 -0700\tcommit: test(spt): M2b T9 — LiveAgent lifecycle E2E through the mock adapter\n27563a5104eabf32c4266fd89c90b2e5e0b38ec5 edeeb41228fef7224fe69bbdef98572fa16c2091 Reavo End <decidel@gmail.com> 1780317412 -0700\tcommit: docs(M2b): T10 — activation sweep, ROADMAP delivered + M3 plan stub\nedeeb41228fef7224fe69bbdef98572fa16c2091 610c9958fb0c0a343fa65e475306a488be3c5515 Reavo End <decidel@gmail.com> 1780344513 -0700\tcommit: docs(M3): author M3-PLAN.md (terminal wrapper + daemon + self-update)\n610c9958fb0c0a343fa65e475306a488be3c5515 0c1a814f78b73d0883c2156816fb04896104eb50 Reavo End <decidel@gmail.com> 1780349561 -0700\tcommit: docs(M3): close Phase-0 gate — Spike #3 (QUIC survival) + #6 (idempotent boundary) PASS\n0c1a814f78b73d0883c2156816fb04896104eb50 9e64261a6d174cfdc1a5c4c23c65db4e1f59408e Reavo End <decidel@gmail.com> 1780349856 -0700\tcommit: docs(M3): close §E.3 — Spike #5 (100× restart + resize-under-load) PASS\n9e64261a6d174cfdc1a5c4c23c65db4e1f59408e 2549c9f8ef8112cf7cb9bfa27b0569292de6fcea Reavo End <decidel@gmail.com> 1780368815 -0700\tcommit: docs(M3): close §E.2 — Spike #4 (Linux forkpty parity) PASS; Phase-0 gate COMPLETE\n2549c9f8ef8112cf7cb9bfa27b0569292de6fcea eddad57f83fa98284bbc35673498a52553e7cc92 Reavo End <decidel@gmail.com> 1780384610 -0700\tcommit: docs: grill-firmed multi-subnet model, notifications, and PTY digest (ADR-0006/0007/0008)\neddad57f83fa98284bbc35673498a52553e7cc92 0581b42cbfe229ff2ed0b08407c0ec700433b1d0 Reavo End <decidel@gmail.com> 1780386806 -0700\tcommit: feat(proto): reserve endpoint-id charset; guard :/@ delimiters at creation seams\n0581b42cbfe229ff2ed0b08407c0ec700433b1d0 c9a466ed2627d98662bb62ccab1ed1ce6bed4950 Reavo End <decidel@gmail.com> 1780386958 -0700\tcommit: docs(M3a): author M3a-PLAN.md (spt-term session-surface mechanism)\nc9a466ed2627d98662bb62ccab1ed1ce6bed4950 f03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 Reavo End <decidel@gmail.com> 1780388886 -0700\tcommit: docs: grill-firm resource advertisement, subnet icon, endpoint access whitelist (ADR-0009)\nf03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 8e239574cf0183bc4c604f30f815ea2623917986 Reavo End <decidel@gmail.com> 1780389867 -0700\tcommit: feat(term): scaffold spt-term crate (M3a A0)\n8e239574cf0183bc4c604f30f815ea2623917986 4a00304fd08feb566e4ab7dca54ef79a6858796f Reavo End <decidel@gmail.com> 1780390319 -0700\tcommit: feat(term): SessionSurface trait + native PTY backend (M3a A1)\n4a00304fd08feb566e4ab7dca54ef79a6858796f 0adaae59f4c6092da3800934fcd0e8d7a8f36502 Reavo End <decidel@gmail.com> 1780390682 -0700\tcommit: feat(term): ConPTY-DSR auto-answer drain pump (M3a A2)\n0adaae59f4c6092da3800934fcd0e8d7a8f36502 ad21334df4bf97f9cbffa4fce0a025a486631ca0 Reavo End <decidel@gmail.com> 1780390906 -0700\tcommit: feat(term): send-keys + send-line injection (M3a A3)\nad21334df4bf97f9cbffa4fce0a025a486631ca0 4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf Reavo End <decidel@gmail.com> 1780391099 -0700\tcommit: feat(term): bounded byte-stream (M3a A4)\n4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf 3877dd2e966b4e846115d58cba0fb4e7d386c433 Reavo End <decidel@gmail.com> 1780391443 -0700\tcommit: feat(term): PTY digest parser primitive (M3a A5, ADR-0008)\n3877dd2e966b4e846115d58cba0fb4e7d386c433 1c39997815ce808a22772afd2e7d95e01b1a7a53 Reavo End <decidel@gmail.com> 1780391614 -0700\tcommit: docs(M3a): activation sweep — ROADMAP/CONTEXT amend + M3b stub (A6)\n1c39997815ce808a22772afd2e7d95e01b1a7a53 d8b854ea5c3482a0a835ecfff4e57fae398606e0 Reavo End <decidel@gmail.com> 1780392106 -0700\tcommit: fix(test): unix manifest fixtures broke on Linux (TOML quoting vs tokenizer)\nd8b854ea5c3482a0a835ecfff4e57fae398606e0 a32b0710dae8e0be0da7068203f8c2dc9d17f58b Reavo End <decidel@gmail.com> 1780393680 -0700\tcommit: fix(store): concurrent SQLite openers fail with 'database is locked' (4.7)\na32b0710dae8e0be0da7068203f8c2dc9d17f58b 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397231 -0700\tcommit: ci: install traceable-reqs with a PAT secret (cross-org private repo)\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397494 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397910 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 7db111fb673380fa953b47110d2e30ffdc217908 Reavo End <decidel@gmail.com> 1780398299 -0700\tcommit: docs(M3b): flesh M3b-PLAN.md from stub into full task table\n7db111fb673380fa953b47110d2e30ffdc217908 d213f005e918900ae04797c3730652630deedd8f Reavo End <decidel@gmail.com> 1780398821 -0700\tcommit: @\nd213f005e918900ae04797c3730652630deedd8f 0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc Reavo End <decidel@gmail.com> 1780398839 -0700\tcommit (amend): feat(M3b-B0): scaffold spt-daemon + versioned local-IPC transport\n0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc 181ce9194bc993789b0d9375a3e166ace78def65 Reavo End <decidel@gmail.com> 1780400958 -0700\tcommit: feat(M3b-B1): broker kernel hosts a real PTY child over the IPC\n181ce9194bc993789b0d9375a3e166ace78def65 c40524f8a2f92fd72a795d7a386031c1953fae51 Reavo End <decidel@gmail.com> 1780401821 -0700\tcommit: feat(M3b-B2): brain + gapless handoff (kill/restart survives, fresh gen_start)\nc40524f8a2f92fd72a795d7a386031c1953fae51 33d2a716b19dc83fc83e594d621d65b2fbd6fb12 Reavo End <decidel@gmail.com> 1780402840 -0700\tcommit: feat(M3b-B3): consolidate interim listen/pulse/Psyche loops into the brain\n33d2a716b19dc83fc83e594d621d65b2fbd6fb12 978e62b91fb28c37334061785c883244c9a7bf8a Reavo End <decidel@gmail.com> 1780403208 -0700\tcommit: Continuous work encouragement rev\n978e62b91fb28c37334061785c883244c9a7bf8a d3ce22ab7bf2874762d928cf4ad3482cea0d20c6 Reavo End <decidel@gmail.com> 1780404013 -0700\tcommit: M3b B4: daemon-authoritative liveness behind one resolver\nd3ce22ab7bf2874762d928cf4ad3482cea0d20c6 35072def7f5434b233d54d5dd289b8f77df63ed3 Reavo End <decidel@gmail.com> 1780437194 -0700\tcommit: feat(M3b-B5): idempotent broker<->brain boundary -- exactly-once across a brain crash\n35072def7f5434b233d54d5dd289b8f77df63ed3 9ea1e403736dbc7bfa235e624b5107f25341ac8a Reavo End <decidel@gmail.com> 1780464869 -0700\tcommit: docs: M4 design — adapter/shell/endpoint lifecycle decisions + fork (ADR-0010)\n9ea1e403736dbc7bfa235e624b5107f25341ac8a 25c8025c955bf011cabe9e258115c0f298399330 Reavo End <decidel@gmail.com> 1780469305 -0700\tcommit: docs: adopt BranchStore (git-KV) as the coarse/durable state substrate (ADR-0011)\n25c8025c955bf011cabe9e258115c0f298399330 115dabb85103a3c6ca7f64084d1994bf72e27c22 Reavo End <decidel@gmail.com> 1780469551 -0700\tcommit: docs(spike-07): BranchStore git-KV bench — PASS, coarse/hot boundary empirically grounded (ADR-0011)\n115dabb85103a3c6ca7f64084d1994bf72e27c22 c848ebd736375677a84c6d8c072be67dac347cda Reavo End <decidel@gmail.com> 1780471127 -0700\tcommit: docs(spike-07): in-process gix true number (~4.7ms/commit) via attractor pilot\nc848ebd736375677a84c6d8c072be67dac347cda c1c4f649966e0f302d3a8fe67bb3b93d3a6d928c Reavo End <decidel@gmail.com> 1780477473 -0700\tcommit: feat(daemon): B6 — auto-start + in-memory seed map (REQ-DAEMON-3, REQ-START-3)\nc1c4f649966e0f302d3a8fe67bb3b93d3a6d928c d06af5dc1f22276c3dd0458b5fe39d8291057e3d Reavo End <decidel@gmail.com> 1780478214 -0700\tcommit: feat(daemon): B7 — orphan-watch supervised-crash signoff (REQ-DAEMON-4)\nd06af5dc1f22276c3dd0458b5fe39d8291057e3d 16f4c8e017586982914e2ae8337b3036540e64ff Reavo End <decidel@gmail.com> 1780479906 -0700\tcommit: feat(daemon): B8 — PTY-digest daemon-half (REQ-TERM-4 int)\n16f4c8e017586982914e2ae8337b3036540e64ff 20817a6c519af2e676b1f37197dc4bb71c1a2e7e Reavo End <decidel@gmail.com> 1780480418 -0700\tcommit: feat(daemon): B9 — M3b activation sweep + daemon E2E (REQ-DAEMON-1/2/4 int)\n20817a6c519af2e676b1f37197dc4bb71c1a2e7e 2573101b65c6a1b0e2d119f4277762f69b4638d8 Reavo End <decidel@gmail.com> 1780504446 -0700\tcommit: fix(daemon): EffectJournal::open creates missing parent dir (broker-bind CI failure)\n2573101b65c6a1b0e2d119f4277762f69b4638d8 fe0acceaedb20cefc016c3f75baab16143c1128e Reavo End <decidel@gmail.com> 1780505326 -0700\tcommit: feat(daemon): M3c-C0 self-update engine + brain-only zero-interruption swap\nfe0acceaedb20cefc016c3f75baab16143c1128e ac4473acf248ed60504035e5a4a4820a4e48b339 Reavo End <decidel@gmail.com> 1780505958 -0700\tcommit: feat(daemon): M3c-C1 signed release verification + rollback hardening\nac4473acf248ed60504035e5a4a4820a4e48b339 b267f6e92f841c825a80eb60a2e785fff8f04174 Reavo End <decidel@gmail.com> 1780506686 -0700\tcommit: feat(daemon): M3c-C2 self-update consent gating + last-active resolver\nb267f6e92f841c825a80eb60a2e785fff8f04174 03a245c2085d77ac89ba31b4b90f707b085a70cc Reavo End <decidel@gmail.com> 1780507417 -0700\tcommit: feat(daemon): M3c-C3 adapter ripple-update + adapter content signing\n03a245c2085d77ac89ba31b4b90f707b085a70cc a14639a405d9f9122323a77cb847f17643550b71 Reavo End <decidel@gmail.com> 1780507695 -0700\tcommit: docs(m3c): M3c-C4 activation sweep + M3 complete + M4 stub\na14639a405d9f9122323a77cb847f17643550b71 ca6522b5ddfea0d433a3aa3732595b0d30f73b52 Reavo End <decidel@gmail.com> 1780508932 -0700\tcommit: docs(m4): expand M4-PLAN D-task layer at M4 start\nca6522b5ddfea0d433a3aa3732595b0d30f73b52 c47d2c6515c40e7712b4f40bb22c48043ddfce5e Reavo End <decidel@gmail.com> 1780508944 -0700\tcommit: feat(net): M4-D0 scaffold spt-net crate\nc47d2c6515c40e7712b4f40bb22c48043ddfce5e c49e522a25c92cae85e5687440b7e5091a625057 Reavo End <decidel@gmail.com> 1780509753 -0700\tcommit: docs(m4): record D6 cross-node merge-model decision (#7)\nc49e522a25c92cae85e5687440b7e5091a625057 3f726edf1df960cdc81c874563d317e035898dd0 Reavo End <decidel@gmail.com> 1780509775 -0700\tcommit: feat(net): M4-D1 iroh endpoint + identity binding + relay policy\n3f726edf1df960cdc81c874563d317e035898dd0 51981c4ecc51864a71748e530cb29023fa47f1e6 Reavo End <decidel@gmail.com> 1780509976 -0700\tcommit: feat(net): M4-D1c mDNS LAN discovery + connect-by-id\n51981c4ecc51864a71748e530cb29023fa47f1e6 d4c04d647e19af22fda4d163ec4c3ca60ca4ac64 Reavo End <decidel@gmail.com> 1780513837 -0700\tcommit: docs(m4): psyche outbound model — ADR-0012 + CONTEXT + hazard 7.3 + D7.5\nd4c04d647e19af22fda4d163ec4c3ca60ca4ac64 bfacfb855780e9e696ced4a638a6543a7e219593 Reavo End <decidel@gmail.com> 1780514489 -0700\tcommit: docs(m4): capture daemon non-blocking-scheduling invariant (hazard 7.4)\nbfacfb855780e9e696ced4a638a6543a7e219593 56f2190ba24ee7d3a337b0f1f4dd486d5756d660 Reavo End <decidel@gmail.com> 1780515295 -0700\tcommit: feat(net): M4-D2a TOTP (RFC 6238) + register D2 pairing hazard reqs\n56f2190ba24ee7d3a337b0f1f4dd486d5756d660 597786e5e85b6bc3cfc0ae02745c88931877e39b Reavo End <decidel@gmail.com> 1780515694 -0700\tcommit: feat(net): M4-D2b SPAKE2 handshake + pairing-transcript binding (#12)\n597786e5e85b6bc3cfc0ae02745c88931877e39b 75f967c960709596dc76ba852ac741cf767c3008 Reavo End <decidel@gmail.com> 1780516074 -0700\tcommit: feat(store): M4-D2c subnet seed store + epoch rotation (#10)\n75f967c960709596dc76ba852ac741cf767c3008 b0bdaab439be50747f60ee6e10baafadf049a54c Reavo End <decidel@gmail.com> 1780516898 -0700\tcommit: feat(net): M4-D2d subnet-global pairing rate limit (#11)\nb0bdaab439be50747f60ee6e10baafadf049a54c 1df7331dfe1cc86c16b0958184b8700ff7ed6586 Reavo End <decidel@gmail.com> 1780517211 -0700\tcommit: feat(store): M4-D2e trust store TOFU + warn-on-change (REQ-PAIR-2)\n1df7331dfe1cc86c16b0958184b8700ff7ed6586 f4042352d7e59ac2da6949bd1b19388e165d9298 Reavo End <decidel@gmail.com> 1780517341 -0700\tcommit: docs(m4): JIT plan for D2-wire pairing ALPN driver\nf4042352d7e59ac2da6949bd1b19388e165d9298 2ae22794cb42352431f03c8353938aeca7798719 Reavo End <decidel@gmail.com> 1780518704 -0700\tcommit: @\n2ae22794cb42352431f03c8353938aeca7798719 08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 Reavo End <decidel@gmail.com> 1780518748 -0700\tcommit (amend): feat(net): M4-D2-wire pairing ALPN ceremony driver (REQ-PAIR-1)\n08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 452358d79c2238142e99956e32dc3ce340fcee1a Reavo End <decidel@gmail.com> 1780522150 -0700\tcommit: feat(net): M4-D2f multi-subnet ceremony + seed transfer (REQ-PAIR-4/5)\n452358d79c2238142e99956e32dc3ce340fcee1a 72570d5c6e191afa75ade22787422ae8ebe7708a Reavo End <decidel@gmail.com> 1780523359 -0700\tcommit: docs(m4): JIT plan for D2g elevation-gated show-totp\n72570d5c6e191afa75ade22787422ae8ebe7708a fb201d61bd3b42deceb14ddafcab0c971197f723 Reavo End <decidel@gmail.com> 1780524004 -0700\tcommit: @\nfb201d61bd3b42deceb14ddafcab0c971197f723 f2cb019ef3ef143b3319ce7565ef9eebe349bc46 Reavo End <decidel@gmail.com> 1780524028 -0700\tcommit (amend): feat(net): M4-D2g elevation-gated `spt pair show-totp` (REQ-PAIR-3/6)\nf2cb019ef3ef143b3319ce7565ef9eebe349bc46 a82446f8077aec0f932ec70ccab16af8b2e9d94e Reavo End <decidel@gmail.com> 1780524412 -0700\tcommit: fix(net): silence D2g Elevation::Unknown dead_code on unix\na82446f8077aec0f932ec70ccab16af8b2e9d94e 8b47fa0051613de1fd9e5865de4259a39857615d Reavo End <decidel@gmail.com> 1780525691 -0700\tcommit: feat(net): M4-D3a/b subnet registry model + epoch lease (REQ-INST-7, hazard #8)\n8b47fa0051613de1fd9e5865de4259a39857615d de8167a763b07bb7d8f99a963ab05a88c031b344 Reavo End <decidel@gmail.com> 1780526470 -0700\tcommit: feat(net): M4-D3c qualified addressing + resolution policy (REQ-INST-10)\nde8167a763b07bb7d8f99a963ab05a88c031b344 afdc7340b73cf2e845a1fed204b0722737c2f20a Reavo End <decidel@gmail.com> 1780527380 -0700\tcommit: feat(net): M4-D3d join-time bare-id collision check (REQ-INST-9)\nafdc7340b73cf2e845a1fed204b0722737c2f20a affa65c6ce943e945eafc6a044ad8e078f9a050d Reavo End <decidel@gmail.com> 1780528414 -0700\tcommit: feat(net): M4-D3e per-(endpoint,subnet) visibility + sync gate (REQ-INST-12/13)\naffa65c6ce943e945eafc6a044ad8e078f9a050d 2766898be6745c9ffa82a19bd88d6014f1bc388b Reavo End <decidel@gmail.com> 1780529038 -0700\tcommit: feat(net): M4-D3f rename ripple — registry rows + perch tree + CLI (REQ-INST-11)\n2766898be6745c9ffa82a19bd88d6014f1bc388b 2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b Reavo End <decidel@gmail.com> 1780529157 -0700\tcommit: docs(m4): JIT plan for D4 broker QUIC ownership\n2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b 9601fe71d1a745bec10933cea66395ee8d14bdc9 Reavo End <decidel@gmail.com> 1780531019 -0700\tcommit: feat(net): M4-D4a broker-owned iroh endpoint + net IPC frames (ADR-0004 SB)\n9601fe71d1a745bec10933cea66395ee8d14bdc9 fa9b26f0d08170bc164684bce08548ae1f919312 Reavo End <decidel@gmail.com> 1780531349 -0700\tcommit: test(net): loopback-scoped sockets for hermetic endpoint tests\nfa9b26f0d08170bc164684bce08548ae1f919312 88510c5014255158b86341e60816db4af982ffa7 Reavo End <decidel@gmail.com> 1780531487 -0700\tcommit (amend): test(net): loopback-scoped sockets for hermetic endpoint tests\n88510c5014255158b86341e60816db4af982ffa7 518ee0ddf788038eedd874ea348c809060801e94 Reavo End <decidel@gmail.com> 1780531500 -0700\tcommit (amend): test(net): loopback-scoped sockets + no portmapper for hermetic endpoint tests\n518ee0ddf788038eedd874ea348c809060801e94 20b05788920237d2df7a6da452d55dc9e510947c Reavo End <decidel@gmail.com> 1780531746 -0700\tcommit: fix(store): pid 0 reads dead on every OS - D3f Linux CI regression\n20b05788920237d2df7a6da452d55dc9e510947c 0f67076f3f6d249179869de8224b26d02eeacdc1 Reavo End <decidel@gmail.com> 1780532105 -0700\tcommit: fix(store): deterministic rename ripple order - read_dir is OS-dependent\n0f67076f3f6d249179869de8224b26d02eeacdc1 0067170f7f1b96c3b651065e354aad0f6568abc8 Reavo End <decidel@gmail.com> 1780533148 -0700\tcommit: feat(net): M4-D4b gapless exactly-once QUIC stream survival (Spike #3 A-D)\n0067170f7f1b96c3b651065e354aad0f6568abc8 5bfa9e770aa50969a045072ee7ab0af4c4e382a3 Reavo End <decidel@gmail.com> 1780535351 -0700\tcommit: ci: phase 1 of self-host migration - windows leg on hfenduleam runner\n5bfa9e770aa50969a045072ee7ab0af4c4e382a3 05f08bc60e8fccce2416a9737b39d2142e2f70c5 Reavo End <decidel@gmail.com> 1780535444 -0700\tcommit: ci: self-hosted runner owns its rust toolchain; fix runner PATH\n05f08bc60e8fccce2416a9737b39d2142e2f70c5 02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 Reavo End <decidel@gmail.com> 1780536384 -0700\tcommit: ci: phase 2 of self-host migration - linux leg + traceability on gravity\n02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 d70ced7d793e5a96185e5b178974545914ad5431 Reavo End <decidel@gmail.com> 1780536763 -0700\tcommit: ci: traceability uses gravity's source-built traceable-reqs\nd70ced7d793e5a96185e5b178974545914ad5431 1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 Reavo End <decidel@gmail.com> 1780537825 -0700\tcommit: feat(net): M4-D4c PresenceChannel broker seam (REQ-EP-4)\n1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 237b1bb42a59cd7e112c8bf00badb4078ed7133c Reavo End <decidel@gmail.com> 1780538471 -0700\tcommit: feat(net): M4-D4d registry replication over the wire\n237b1bb42a59cd7e112c8bf00badb4078ed7133c 924e6b4bc71f742ba422bddade056ae24a4010fe Reavo End <decidel@gmail.com> 1780538587 -0700\tcommit: docs: begin fault-injection acceptance matrix (ADR-0002 SERIOUS #6, M4-D4 piece 5)\n924e6b4bc71f742ba422bddade056ae24a4010fe 017d103f02a7229eee2a27f60c1b2bb6f776c4f3 Reavo End <decidel@gmail.com> 1780538932 -0700\tcommit: fix(store): registry open creates the owlery dir — KNOWN-HAZARDS 4.9\n017d103f02a7229eee2a27f60c1b2bb6f776c4f3 1cbeb6d32573fd75c22125a8607c03b6270fad1b Reavo End <decidel@gmail.com> 1780539806 -0700\tcommit: docs: M4-D5 JIT plan — WAN msg, remote attach, reach-back, access whitelist\n1cbeb6d32573fd75c22125a8607c03b6270fad1b caab43332f3dec1bfdba4c526883ccd7c805a8dc Reavo End <decidel@gmail.com> 1780540739 -0700\tcommit: feat(net): M4-D5a WAN message delivery over the broker wire\ncaab43332f3dec1bfdba4c526883ccd7c805a8dc ef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 Reavo End <decidel@gmail.com> 1780541062 -0700\tcommit: fix(test): wanmsg life-1 loop satisfies clippy::never_loop (CI rustc 1.96)\nef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 71e301597060f3104ae565769d013c097fc4c864 Reavo End <decidel@gmail.com> 1780541328 -0700\tcommit: ci: bump actions/checkout v4 -> v5 (Node 20 deprecation, forced Node 24 on 2026-06-16)\n71e301597060f3104ae565769d013c097fc4c864 4ea3b3733b356d989b7d8af803dbb41dddbf3bcb Reavo End <decidel@gmail.com> 1780542941 -0700\tcommit: feat(net): M4-D5b remote terminal attach (REQ-INST-8)\n4ea3b3733b356d989b7d8af803dbb41dddbf3bcb 269b8ef5bffa3b003519d98121c931c32054cbdd Reavo End <decidel@gmail.com> 1780544566 -0700\tcommit: feat(net): M4-D5c remote-drive detection + file transfer (REQ-REACH-1)\n269b8ef5bffa3b003519d98121c931c32054cbdd fcad41ca27210276606356d72ab4abde0a3d65c0 Reavo End <decidel@gmail.com> 1780544873 -0700\tcommit: fix(test): attach restart ground-truth compares prefixes through the marker\nfcad41ca27210276606356d72ab4abde0a3d65c0 f49aba1984a28ca066345bfe218b839d5a5ff8e0 Reavo End <decidel@gmail.com> 1780545918 -0700\tcommit: feat(sec): M4-D5d endpoint access whitelist (REQ-SEC-1, ADR-0009)\nf49aba1984a28ca066345bfe218b839d5a5ff8e0 8f4ec2465b477617a9cd359a983ec41d93f0e099 Reavo End <decidel@gmail.com> 1780549041 -0700\tcommit: docs: M4-D6 design locked — ADR-0013 Psyche-reconciled context merge\n8f4ec2465b477617a9cd359a983ec41d93f0e099 8b42452ce7f8277b239242efd853dc93e763fc17 Reavo End <decidel@gmail.com> 1780549527 -0700\tcommit: feat(store): M4-D6a BranchStore + two-tier ContextStore + project_id (REQ-STORE-1, ADR-0011/0013)\n8b42452ce7f8277b239242efd853dc93e763fc17 1ef980218554bf2f7ba8c8cd7f7e210e2b34129f Reavo End <decidel@gmail.com> 1780550270 -0700\tcommit: feat(live): M4-D6a two-tier context routing + rename context arm (REQ-INST-2)\n1ef980218554bf2f7ba8c8cd7f7e210e2b34129f 7e9c2b54d68fd99210f3e5b133a64d254285ab76 Reavo End <decidel@gmail.com> 1780550799 -0700\tcommit: feat(store): M4-D6b distributed precedence — marker v2, version vectors, conflict artifacts\n7e9c2b54d68fd99210f3e5b133a64d254285ab76 50620e4a4834900120d725a13d0c6059ae841e6c Reavo End <decidel@gmail.com> 1780551377 -0700\tcommit: feat(live): M4-D6b Psyche reconcile + bounded turn driver (D7.5a pulled forward)\n50620e4a4834900120d725a13d0c6059ae841e6c 1ba6e06453577d88aaf1c1382a58a4a379069b1a Reavo End <decidel@gmail.com> 1780551649 -0700\tcommit: fix(test): reconcile unix mock command nested single quotes broke manifest TOML\n1ba6e06453577d88aaf1c1382a58a4a379069b1a f08aedcb3cd2615712f3f30daf07a35ee9870aac Reavo End <decidel@gmail.com> 1780552853 -0700\tcommit: feat(store): M4-D6c-1 bundle sync core — BranchStore bundle plumbing + per-file vector merge driver (REQ-NET-3)\nf08aedcb3cd2615712f3f30daf07a35ee9870aac fa97b80ef3a560c4fd77311b202cf982d7674abc Reavo End <decidel@gmail.com> 1780556100 -0700\tcommit: feat(net,daemon): M4-D6c-2 bundle sync over broker streams — wire records, server-side gate, reconcile wiring (REQ-NET-3, REQ-INST-5)\nfa97b80ef3a560c4fd77311b202cf982d7674abc 0d05e5c5512ba64986d032f543841b9f3301fa72 Reavo End <decidel@gmail.com> 1780558624 -0700\tcommit: feat(net,daemon): M4-D7-1 update propagation wire + staged-release cache (REQ-UPD-1)\n0d05e5c5512ba64986d032f543841b9f3301fa72 b67d4321b85d3633b67124933960f8ed25110150 Reavo End <decidel@gmail.com> 1780558959 -0700\tcommit: feat(daemon): M4-D7-2 update propagation serve/request drivers + E2E (REQ-UPD-1)\nb67d4321b85d3633b67124933960f8ed25110150 9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 Reavo End <decidel@gmail.com> 1780559260 -0700\tcommit: docs(plan): mark M4-D7 complete (both slices CI-green)\n9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 6fac5390690ed9c6ed759cca15e289d854009783 Reavo End <decidel@gmail.com> 1780559950 -0700\tcommit: feat(proto,live): M4-D7.5-1 event-type taxonomy + Psyche intent parser (ADR-0012)\n6fac5390690ed9c6ed759cca15e289d854009783 3f771e11c7d4762fc5721945866982dcd21e4a9c Reavo End <decidel@gmail.com> 1780560620 -0700\tcommit: feat(daemon): M4-D7.5-2 Psyche outbound relay + sanitize boundary (REQ-HAZARD-PSYCHE-OUTBOUND-PROXY)\n3f771e11c7d4762fc5721945866982dcd21e4a9c e405b4329b3c312b6bf73ca9b106de4f86c8f155 Reavo End <decidel@gmail.com> 1780560933 -0700\tcommit: docs(plan): mark M4-D7.5 complete (both slices CI-green)\ne405b4329b3c312b6bf73ca9b106de4f86c8f155 e1120e4938064baee56ea0cbc5b8a4fc7c390df0 Reavo End <decidel@gmail.com> 1780561369 -0700\tcommit: docs(plan): draft M4-D8-PLAN (subnet notification primitive, ADR-0007)\ne1120e4938064baee56ea0cbc5b8a4fc7c390df0 9a6548fbd0c85a20715632a62ff4c5c7c767d142 Reavo End <decidel@gmail.com> 1780561600 -0700\tcommit: feat(store): M4-D8-1 notif store + semilattice merge (ADR-0007)\n9a6548fbd0c85a20715632a62ff4c5c7c767d142 f612b3ff4f677b56048a2d6c1294ea62054e513b Reavo End <decidel@gmail.com> 1780562053 -0700\tcommit: feat(net,daemon): M4-D8-2 notif replication record + gated feed drivers + E2E (ADR-0007)\nf612b3ff4f677b56048a2d6c1294ea62054e513b 45484109079454c594a20515fe730480d0ad3db5 Reavo End <decidel@gmail.com> 1780562746 -0700\tcommit: feat(daemon,spt): M4-D8-3 notif first-fire + resurface engine + producers (REQ-NOTIF-1)\n45484109079454c594a20515fe730480d0ad3db5 126d8434d0c56ed0f3092d477a251c188a60e6ad Reavo End <decidel@gmail.com> 1780563316 -0700\tcommit: feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)\n126d8434d0c56ed0f3092d477a251c188a60e6ad b8d75af3cf12d899aff2b1af51caf44482d141a6 Reavo End <decidel@gmail.com> 1780563620 -0700\tcommit: docs(plan): mark M4-D8 complete (all four slices CI-green)\nb8d75af3cf12d899aff2b1af51caf44482d141a6 a71d23a3102a01168a47a47491489057cead4053 Reavo End <decidel@gmail.com> 1780570329 -0700\tcommit: docs(plan): draft M4-D9 JIT plan (closeout: loops, dormancy, INST-14/15, two-host)\na71d23a3102a01168a47a47491489057cead4053 ea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 Reavo End <decidel@gmail.com> 1780571295 -0700\tcommit: feat(daemon): M4-D9-1-1 inbound net-stream dispatch (production serve loops)\nea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 822d3ce76ce7de9b15bfdbf835ac244c249a6157 Reavo End <decidel@gmail.com> 1780572144 -0700\tcommit: feat(daemon,net): M4-D9-1-2 registry host + outbound peer pump (self-driving subnet)\n822d3ce76ce7de9b15bfdbf835ac244c249a6157 b15cf2cee93b33ac5f6e9bfb7534428b1cb05560 Reavo End <decidel@gmail.com> 1780572683 -0700\tcommit: feat(daemon): M4-D9-1-3 production VerifyPolicy + update pump leg (REQ-UPD-1/2/4)\nb15cf2cee93b33ac5f6e9bfb7534428b1cb05560 af7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 Reavo End <decidel@gmail.com> 1780572980 -0700\tcommit: feat(spt): M4-D9-1-4 spt send WAN leg (registry-resolved off-node delivery)\naf7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 0dd85d5bae817201f4d239d04ddf4a0431e6ec2f Reavo End <decidel@gmail.com> 1780573536 -0700\tcommit: feat(daemon,net): M4-D9-1-5 attach/xfer dispatch arms (remote-drive + file transfer production serve)\n0dd85d5bae817201f4d239d04ddf4a0431e6ec2f cb81e831eabe86dd7da4c64658eb2caf1ca3c99e Reavo End <decidel@gmail.com> 1780573894 -0700\tcommit: docs(plan): mark M4-D9-1 complete (five CI-green sub-slices)\ncb81e831eabe86dd7da4c64658eb2caf1ca3c99e 031578398664aa6559552f2dfd0d399928d5684d Reavo End <decidel@gmail.com> 1780574749 -0700\tcommit: feat(daemon,store,net): M4-D9-2-1 resting-state model (REQ-INST-3 activated)\n031578398664aa6559552f2dfd0d399928d5684d 8a7afd6792b75562b7699fc89b36c79d9db2b656 Reavo End <decidel@gmail.com> 1780575335 -0700\tcommit: feat(daemon): M4-D9-2-2 transition echo + wake effects (REQ-INST-4 activated)\n8a7afd6792b75562b7699fc89b36c79d9db2b656 56a98db5b944dc51f7c458c18f6f671332b05e2d Reavo End <decidel@gmail.com> 1780575928 -0700\tcommit: feat(daemon,spt): M4-D9-2-3 resting-edge production feeds + spt suspend/wake\n56a98db5b944dc51f7c458c18f6f671332b05e2d a52dacbe13b358978b89347801db32701aa5f15e Reavo End <decidel@gmail.com> 1780576258 -0700\tcommit: docs(plan): mark M4-D9-2 complete (three CI-green sub-slices)\na52dacbe13b358978b89347801db32701aa5f15e 785002e707f71652c714a13d71e6d5c100db003a Reavo End <decidel@gmail.com> 1780576758 -0700\tcommit: feat(daemon,docs): M4-D9-3 dormancy budget measured + policy locked [budget]\n785002e707f71652c714a13d71e6d5c100db003a 6f61a89677d039dc9816f2f736a05b3915f75e00 Reavo End <decidel@gmail.com> 1780577582 -0700\tcommit: docs(budget): M4-D9-3 gravity numbers + per-OS budget steps [budget]\n6f61a89677d039dc9816f2f736a05b3915f75e00 650c7fb326ed3478f5661891641a32b1b712ce19 Reavo End <decidel@gmail.com> 1780578309 -0700\tcommit: fix(ci): ASCII-only string in the Windows budget step [budget]\n650c7fb326ed3478f5661891641a32b1b712ce19 0ea318b3d77cdcc84c56fe45dad84de095baff96 Reavo End <decidel@gmail.com> 1780579051 -0700\tcommit: feat(net,daemon,store,spt): M4-D9-4 resource advertisement (REQ-INST-14 activated)\n0ea318b3d77cdcc84c56fe45dad84de095baff96 e69f59add554232ee75321a10a1641ab2bbece91 Reavo End <decidel@gmail.com> 1780579804 -0700\tcommit: feat(store,spt,daemon): M4-D9-5-1 immutable home subnet at creation\ne69f59add554232ee75321a10a1641ab2bbece91 0b30dea4a0f4829908bb0de71db07faafaa91e98 Reavo End <decidel@gmail.com> 1780580326 -0700\tcommit: feat(store,spt): M4-D9-5-2 spt fork — copy-then-diverge (REQ-INST-15 activated)\n0b30dea4a0f4829908bb0de71db07faafaa91e98 92fc261c83af6f2a09cf137b6f8ed9150bd21b32 Reavo End <decidel@gmail.com> 1780580669 -0700\tcommit: docs(plan): mark M4-D9-3/4/5 complete (budget measured, resources, home+fork)\n92fc261c83af6f2a09cf137b6f8ed9150bd21b32 107240eea81599ba191fb2cd2fe93d9f2c8a0390 Reavo End <decidel@gmail.com> 1780582455 -0700\tcommit: feat(net,daemon,ci,docs): M4-D9-6-1 two-host rig ladder — env-gated int tests + runbook\n107240eea81599ba191fb2cd2fe93d9f2c8a0390 a86371c9cfd87639c4d5dd5e2c82d464bc028ed0 Reavo End <decidel@gmail.com> 1780583265 -0700\tcommit: test(rig): two-host ladder rig run + durable flake ledger [twohost]\na86371c9cfd87639c4d5dd5e2c82d464bc028ed0 2ba79061f05c5b273485fac83671cf2bdbc4681d Reavo End <decidel@gmail.com> 1780583853 -0700\tcommit: docs(reqs,plan): M4-D9-6 complete — rig proof evidence, int stages live, gh-interim retired\n2ba79061f05c5b273485fac83671cf2bdbc4681d becb6ecb2a5458879c3c4f77139cd1ec31e39a36 Reavo End <decidel@gmail.com> 1780584205 -0700\tcommit: docs(plan,reqs): M4-D9-7 closeout sweep — M4 DELIVERED\nbecb6ecb2a5458879c3c4f77139cd1ec31e39a36 a6f5cabac5f0a8d340a5aa81206f94e096d215a5 Reavo End <decidel@gmail.com> 1780586451 -0700\tcommit: docs(plan): author M5-PLAN — scope locked at M5 start via user forks\na6f5cabac5f0a8d340a5aa81206f94e096d215a5 2a59e7d356d6329b2b35e25095ec355ee384fa18 Reavo End <decidel@gmail.com> 1780586580 -0700\tcommit: docs(reqs,roadmap): M5-D0 — scope alignment + requirement registry prep","truncated":true,"truncatedBy":"lines","totalLines":1502,"totalBytes":41799,"outputLines":203,"outputBytes":41799,"lastLinePartial":false,"firstLineExceedsLimit":false},"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 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780296118 -0700\tcheckout: moving from main to dev-freeform\n4fa29498d2552f277e918219f74d9c75183a6921 905c6b438f781847bcfda59501d30e073c85a832 Reavo End <decidel@gmail.com> 1780296234 -0700\tcommit: docs: add M0-PLAN.md (just-in-time task plan) + roadmap pointer\n905c6b438f781847bcfda59501d30e073c85a832 d6aa312316a28997507bf9738e9d8300ebb08cad Reavo End <decidel@gmail.com> 1780297115 -0700\tcommit: @\nd6aa312316a28997507bf9738e9d8300ebb08cad e8ec9803ae6e26efba6f2754579d1e97a7b6506e Reavo End <decidel@gmail.com> 1780297126 -0700\tcommit (amend): feat(spt-proto): M0 T2 — EVENT envelope grammar (compose + parse)\ne8ec9803ae6e26efba6f2754579d1e97a7b6506e f3a55450e27f9c7b96a6bc3e2a00929144a620e6 Reavo End <decidel@gmail.com> 1780297309 -0700\tcommit: feat(spt-proto): M0 T3 — EVENT-PART chunker + byte-exact reassembly\nf3a55450e27f9c7b96a6bc3e2a00929144a620e6 c04e07a3545e5a01de0a6135073584008e9a7eb6 Reavo End <decidel@gmail.com> 1780297415 -0700\tcommit: feat(spt-proto): M0 T4 — endpoint type taxonomy (open + agent/Shell seam)\nc04e07a3545e5a01de0a6135073584008e9a7eb6 e93037d11f41a72c25f451fa637c63c7b54226cc Reavo End <decidel@gmail.com> 1780297723 -0700\tcommit: feat(spt-proto): M0 T5 — Ed25519 identity primitive (clean-room)\ne93037d11f41a72c25f451fa637c63c7b54226cc 260f337d330600e8d4bfa66e06a0850d4f861b37 Reavo End <decidel@gmail.com> 1780297787 -0700\tcommit: feat(spt-proto): M0 T6 — wire-protocol version + N-1 compat window\n260f337d330600e8d4bfa66e06a0850d4f861b37 1821000dd20a7c5bd85257d72c26fd1e346fbef7 Reavo End <decidel@gmail.com> 1780297902 -0700\tcommit: feat(spt-proto): M0 T7 — payload model (typed commands + binary blobs)\n1821000dd20a7c5bd85257d72c26fd1e346fbef7 7fdc26c913420e8ca6114071fb7089520241cd84 Reavo End <decidel@gmail.com> 1780298723 -0700\tcommit: feat(spt-store): M0 T8 — atomic write (EBUSY retry) + UNC-path strip\n7fdc26c913420e8ca6114071fb7089520241cd84 e81c06f94a9a7278c1f067c6743a959110293246 Reavo End <decidel@gmail.com> 1780298874 -0700\tcommit: feat(spt-store): M0 T9 — SQLite spool (deferred-aware drain)\ne81c06f94a9a7278c1f067c6743a959110293246 459c51b9ba98c64a991e8384889358bf28c403d9 Reavo End <decidel@gmail.com> 1780299008 -0700\tcommit: feat(spt-store): M0 T10 — perch layout + single path resolver\n459c51b9ba98c64a991e8384889358bf28c403d9 e7beacafaafca3ab964711318eb20c102390992b Reavo End <decidel@gmail.com> 1780299239 -0700\tcommit: feat(spt-store): M0 T11 — registry persistence + stale-entry cleanup\ne7beacafaafca3ab964711318eb20c102390992b d98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 Reavo End <decidel@gmail.com> 1780299365 -0700\tcommit: feat(spt-store): M0 T12 — info.json shape + torn-read tolerance\nd98f8d7cb0553a10a5ba4c09aba37e58258ca5a4 be5ed5806934dfe8729330e7596bb101226524f2 Reavo End <decidel@gmail.com> 1780299463 -0700\tcommit: ci(M0 T13): build + test + clippy matrix and traceable-reqs hard gate\nbe5ed5806934dfe8729330e7596bb101226524f2 849124569d1e726de50341ed9e01b6fd3cdcdc72 Reavo End <decidel@gmail.com> 1780299589 -0700\tcommit: chore(M0 T14): activation sweep — activate R-ARCH-1, mark M0 complete\n849124569d1e726de50341ed9e01b6fd3cdcdc72 f388817ccbb0a19dc0d8d2e245530e41da065b64 Reavo End <decidel@gmail.com> 1780300690 -0700\tcommit: docs: add M1-PLAN.md (local messaging + binary) + roadmap pointer\nf388817ccbb0a19dc0d8d2e245530e41da065b64 bdaf52ab67490ac500963533f8927456f3a97d6a Reavo End <decidel@gmail.com> 1780301333 -0700\tcommit: chore(M1 T0): register REQ-MSG-1/2/3 + scaffold spt-msg crate\nbdaf52ab67490ac500963533f8927456f3a97d6a d3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 Reavo End <decidel@gmail.com> 1780301461 -0700\tcommit: feat(spt-msg): M1 T1 — TCP wire framing + loopback listener\nd3f62660a70bfda0f182fd7fd5e58cf4f6e66ce0 2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c Reavo End <decidel@gmail.com> 1780301593 -0700\tcommit: feat(spt-msg): M1 T2 — TCP-first/spool-fallback delivery + routing\n2fda4cfac63bfae3c76e6d5ea90d9013a8cf637c 5eb4c37ed0b808e187fcaea91420edfc0dc95a30 Reavo End <decidel@gmail.com> 1780301696 -0700\tcommit: feat(spt-msg): M1 T3 — ready-agent lifecycle\n5eb4c37ed0b808e187fcaea91420edfc0dc95a30 bf319984d10a9a29525c1cfd325d604f8bd1b585 Reavo End <decidel@gmail.com> 1780301798 -0700\tcommit: feat(spt-msg): M1 T4 — ring (blocking send-and-wait-for-reply)\nbf319984d10a9a29525c1cfd325d604f8bd1b585 6e167121248cf8d4df9d65255f5bc24aeae01863 Reavo End <decidel@gmail.com> 1780301863 -0700\tcommit: feat(spt-msg): M1 T5 — deferred send (hook channel)\n6e167121248cf8d4df9d65255f5bc24aeae01863 356b8c7969468a6ec5c5779ec67c91a790d0b0c8 Reavo End <decidel@gmail.com> 1780302103 -0700\tcommit: feat(spt): M1 T6-T8 — spt binary + CLI (send/ring/ready/poll/list/stop/whoami)\n356b8c7969468a6ec5c5779ec67c91a790d0b0c8 af0fff0df8cff829fe0fab0a4696b612b25dd4e9 Reavo End <decidel@gmail.com> 1780302255 -0700\tcommit: test(spt-msg): M1 T9-T10 — killer-quickstart E2E + activation sweep\naf0fff0df8cff829fe0fab0a4696b612b25dd4e9 3b70fed82bfa77502df09d3e34eef140ce125b12 Reavo End <decidel@gmail.com> 1780304902 -0700\tcommit: fix(spt-msg): isolate unit tests to a temp SPT_HOME (stop polluting the live owlery)\n3b70fed82bfa77502df09d3e34eef140ce125b12 bdb719373191d5abb1d698dff0bdcd176d3008a7 Reavo End <decidel@gmail.com> 1780305400 -0700\tcommit: refactor(spt-store)+docs: default home spt-core/ + separate spt-plugin from spt-core scope\nbdb719373191d5abb1d698dff0bdcd176d3008a7 7e3acfbfc23f41bafd8c8514a830f9e7389cc14e Reavo End <decidel@gmail.com> 1780308344 -0700\tcommit: docs(M2a): author M2a-PLAN.md (harness contract) + split M2 in ROADMAP\n7e3acfbfc23f41bafd8c8514a830f9e7389cc14e 637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 Reavo End <decidel@gmail.com> 1780310110 -0700\tcommit: docs: reconcile daemon-hosted endpoint liveness (KNOWN-HAZARDS 2.5)\n637cf9bd9521458c0ebe8c7fa4e2244f78e18a99 67414530a8b4d5fb2ff6aabf357c200ff56badca Reavo End <decidel@gmail.com> 1780310742 -0700\tcommit: feat(spt-runtime): M2a T0 — scaffold harness-contract crate\n67414530a8b4d5fb2ff6aabf357c200ff56badca ae2337649d66fb68714460a6bd66dee918dd6ce7 Reavo End <decidel@gmail.com> 1780311003 -0700\tcommit: feat(spt-runtime): M2a T1 — runtime-manifest schema + parse + validate\nae2337649d66fb68714460a6bd66dee918dd6ce7 fecd6d26f69cff0bfbdc56f29739d4bb6d581cac Reavo End <decidel@gmail.com> 1780311256 -0700\tcommit: feat(spt-runtime): M2a T2 — AgentRuntime seam + ManifestRuntime\nfecd6d26f69cff0bfbdc56f29739d4bb6d581cac 281eb8dd3c542f586ff8a8f827a5e6e0288b0263 Reavo End <decidel@gmail.com> 1780311614 -0700\tcommit: feat(spt): M2a T3 — `spt api` dispatch group + local-api-auth\n281eb8dd3c542f586ff8a8f827a5e6e0288b0263 d6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e Reavo End <decidel@gmail.com> 1780312122 -0700\tcommit: feat(spt): M2a T4 — harness-hosted startup (seed → listen → bind)\nd6610b698e6ea6d1d7fc4e06187bb179ae3bcb4e bae7124badb654c27596ca14df5ce623db50303c Reavo End <decidel@gmail.com> 1780312254 -0700\tcommit: feat(spt): M2a T5 — delivery + activity (state / echo-gate / poll / inject)\nbae7124badb654c27596ca14df5ce623db50303c 42af360e619f7510d9c492507a9864c48b41ac1e Reavo End <decidel@gmail.com> 1780312367 -0700\tcommit: feat(spt): M2a T6 — worker perches + cascade-wipe guard\n42af360e619f7510d9c492507a9864c48b41ac1e d555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc Reavo End <decidel@gmail.com> 1780312565 -0700\tcommit: feat(spt): M2a T7 — reporting + boundary surface\nd555c93a1d38b5e85a4cb4a2e2fcff4ae9287ebc 146a7b83f67685fbb4d3fd9090a1614298357e22 Reavo End <decidel@gmail.com> 1780312746 -0700\tcommit: feat(mock-adapter): M2a T8 — generic mock adapter (manifest + helper)\n146a7b83f67685fbb4d3fd9090a1614298357e22 504b346c73583b4ddcb83e63da92fd7211c0d2ab Reavo End <decidel@gmail.com> 1780313178 -0700\tcommit: test(spt): M2a T9 — contract E2E through the mock adapter\n504b346c73583b4ddcb83e63da92fd7211c0d2ab dbdbabe8f82ce9d85a39dba35deda037d4c480e3 Reavo End <decidel@gmail.com> 1780313373 -0700\tcommit: chore(M2a): T10 — activation sweep + ROADMAP (M2a delivered)\ndbdbabe8f82ce9d85a39dba35deda037d4c480e3 104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 Reavo End <decidel@gmail.com> 1780314087 -0700\tcommit: docs(M2b): author M2b-PLAN.md (live-agent lifecycle)\n104ea8e466fc20f5b8ab7879a3a3b901034ffbd6 e0b2c80024315541aa2415ca6ad0e6f3da43b895 Reavo End <decidel@gmail.com> 1780314638 -0700\tcommit: feat(spt-live): M2b T0 — scaffold live-agent lifecycle crate\ne0b2c80024315541aa2415ca6ad0e6f3da43b895 a9a079bc5dd69fbb77da2b46d7e0cdf70707e69e Reavo End <decidel@gmail.com> 1780315057 -0700\tcommit: feat(spt-live): M2b T1 — history subsystem (fetcher/locate-normalize/native)\na9a079bc5dd69fbb77da2b46d7e0cdf70707e69e 4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b Reavo End <decidel@gmail.com> 1780315333 -0700\tcommit: feat(spt-live): M2b T2 — spawn-psyche seam (nested perch + owned prompt)\n4b2ee3c4afd90bd6fd8121c54a094dbe7d69df3b c8bfe6e24c5752e2168c3f020dbc8aae2b628852 Reavo End <decidel@gmail.com> 1780315707 -0700\tcommit: feat(spt-live): M2b T3 — echo-commune seam (history brief → commune drop)\nc8bfe6e24c5752e2168c3f020dbc8aae2b628852 91380ab5dc0accc2286597c45efb9a6e620133a5 Reavo End <decidel@gmail.com> 1780315958 -0700\tcommit: feat(spt-live): M2b T4 — drop-file ingest (single-writer + precedence guard)\n91380ab5dc0accc2286597c45efb9a6e620133a5 5d085e8c480e1b79f6a6720d68ac425f002478a8 Reavo End <decidel@gmail.com> 1780316170 -0700\tcommit: feat(spt-live): M2b T5 — pulse heartbeat (interim per-tick driver)\n5d085e8c480e1b79f6a6720d68ac425f002478a8 86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 Reavo End <decidel@gmail.com> 1780316382 -0700\tcommit: feat(spt-live): M2b T6 — signoff + boundary ordering invariants\n86305ca9ba0ec1618bd2a4ab9433f9bc18aa99e5 5f1a344bc06bd14d2f9025250ad2444d9994380b Reavo End <decidel@gmail.com> 1780316562 -0700\tcommit: feat(spt-live): M2b T7 — resume-session seam (continue-existing / fresh-preload)\n5f1a344bc06bd14d2f9025250ad2444d9994380b b91168c80df0f6be33b17a3d3031b2a748818cd7 Reavo End <decidel@gmail.com> 1780316889 -0700\tcommit: feat(spt): M2b T8 — api listen lifecycle wiring + graceful shutdown\nb91168c80df0f6be33b17a3d3031b2a748818cd7 27563a5104eabf32c4266fd89c90b2e5e0b38ec5 Reavo End <decidel@gmail.com> 1780317197 -0700\tcommit: test(spt): M2b T9 — LiveAgent lifecycle E2E through the mock adapter\n27563a5104eabf32c4266fd89c90b2e5e0b38ec5 edeeb41228fef7224fe69bbdef98572fa16c2091 Reavo End <decidel@gmail.com> 1780317412 -0700\tcommit: docs(M2b): T10 — activation sweep, ROADMAP delivered + M3 plan stub\nedeeb41228fef7224fe69bbdef98572fa16c2091 610c9958fb0c0a343fa65e475306a488be3c5515 Reavo End <decidel@gmail.com> 1780344513 -0700\tcommit: docs(M3): author M3-PLAN.md (terminal wrapper + daemon + self-update)\n610c9958fb0c0a343fa65e475306a488be3c5515 0c1a814f78b73d0883c2156816fb04896104eb50 Reavo End <decidel@gmail.com> 1780349561 -0700\tcommit: docs(M3): close Phase-0 gate — Spike #3 (QUIC survival) + #6 (idempotent boundary) PASS\n0c1a814f78b73d0883c2156816fb04896104eb50 9e64261a6d174cfdc1a5c4c23c65db4e1f59408e Reavo End <decidel@gmail.com> 1780349856 -0700\tcommit: docs(M3): close §E.3 — Spike #5 (100× restart + resize-under-load) PASS\n9e64261a6d174cfdc1a5c4c23c65db4e1f59408e 2549c9f8ef8112cf7cb9bfa27b0569292de6fcea Reavo End <decidel@gmail.com> 1780368815 -0700\tcommit: docs(M3): close §E.2 — Spike #4 (Linux forkpty parity) PASS; Phase-0 gate COMPLETE\n2549c9f8ef8112cf7cb9bfa27b0569292de6fcea eddad57f83fa98284bbc35673498a52553e7cc92 Reavo End <decidel@gmail.com> 1780384610 -0700\tcommit: docs: grill-firmed multi-subnet model, notifications, and PTY digest (ADR-0006/0007/0008)\neddad57f83fa98284bbc35673498a52553e7cc92 0581b42cbfe229ff2ed0b08407c0ec700433b1d0 Reavo End <decidel@gmail.com> 1780386806 -0700\tcommit: feat(proto): reserve endpoint-id charset; guard :/@ delimiters at creation seams\n0581b42cbfe229ff2ed0b08407c0ec700433b1d0 c9a466ed2627d98662bb62ccab1ed1ce6bed4950 Reavo End <decidel@gmail.com> 1780386958 -0700\tcommit: docs(M3a): author M3a-PLAN.md (spt-term session-surface mechanism)\nc9a466ed2627d98662bb62ccab1ed1ce6bed4950 f03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 Reavo End <decidel@gmail.com> 1780388886 -0700\tcommit: docs: grill-firm resource advertisement, subnet icon, endpoint access whitelist (ADR-0009)\nf03a490bb3de232f2ce4df4de1ea4762e0bfe1d6 8e239574cf0183bc4c604f30f815ea2623917986 Reavo End <decidel@gmail.com> 1780389867 -0700\tcommit: feat(term): scaffold spt-term crate (M3a A0)\n8e239574cf0183bc4c604f30f815ea2623917986 4a00304fd08feb566e4ab7dca54ef79a6858796f Reavo End <decidel@gmail.com> 1780390319 -0700\tcommit: feat(term): SessionSurface trait + native PTY backend (M3a A1)\n4a00304fd08feb566e4ab7dca54ef79a6858796f 0adaae59f4c6092da3800934fcd0e8d7a8f36502 Reavo End <decidel@gmail.com> 1780390682 -0700\tcommit: feat(term): ConPTY-DSR auto-answer drain pump (M3a A2)\n0adaae59f4c6092da3800934fcd0e8d7a8f36502 ad21334df4bf97f9cbffa4fce0a025a486631ca0 Reavo End <decidel@gmail.com> 1780390906 -0700\tcommit: feat(term): send-keys + send-line injection (M3a A3)\nad21334df4bf97f9cbffa4fce0a025a486631ca0 4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf Reavo End <decidel@gmail.com> 1780391099 -0700\tcommit: feat(term): bounded byte-stream (M3a A4)\n4b789eecdfdfd30c64eaa0f67ab60a90ffe266bf 3877dd2e966b4e846115d58cba0fb4e7d386c433 Reavo End <decidel@gmail.com> 1780391443 -0700\tcommit: feat(term): PTY digest parser primitive (M3a A5, ADR-0008)\n3877dd2e966b4e846115d58cba0fb4e7d386c433 1c39997815ce808a22772afd2e7d95e01b1a7a53 Reavo End <decidel@gmail.com> 1780391614 -0700\tcommit: docs(M3a): activation sweep — ROADMAP/CONTEXT amend + M3b stub (A6)\n1c39997815ce808a22772afd2e7d95e01b1a7a53 d8b854ea5c3482a0a835ecfff4e57fae398606e0 Reavo End <decidel@gmail.com> 1780392106 -0700\tcommit: fix(test): unix manifest fixtures broke on Linux (TOML quoting vs tokenizer)\nd8b854ea5c3482a0a835ecfff4e57fae398606e0 a32b0710dae8e0be0da7068203f8c2dc9d17f58b Reavo End <decidel@gmail.com> 1780393680 -0700\tcommit: fix(store): concurrent SQLite openers fail with 'database is locked' (4.7)\na32b0710dae8e0be0da7068203f8c2dc9d17f58b 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397231 -0700\tcommit: ci: install traceable-reqs with a PAT secret (cross-org private repo)\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397494 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 5c26a4b6c2fe21c80380e9ae22cca317913cd73d Reavo End <decidel@gmail.com> 1780397910 -0700\treset: moving to HEAD\n5c26a4b6c2fe21c80380e9ae22cca317913cd73d 7db111fb673380fa953b47110d2e30ffdc217908 Reavo End <decidel@gmail.com> 1780398299 -0700\tcommit: docs(M3b): flesh M3b-PLAN.md from stub into full task table\n7db111fb673380fa953b47110d2e30ffdc217908 d213f005e918900ae04797c3730652630deedd8f Reavo End <decidel@gmail.com> 1780398821 -0700\tcommit: @\nd213f005e918900ae04797c3730652630deedd8f 0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc Reavo End <decidel@gmail.com> 1780398839 -0700\tcommit (amend): feat(M3b-B0): scaffold spt-daemon + versioned local-IPC transport\n0740d1d25bf519ee8fb2f477a1a51a1ac0d6dbfc 181ce9194bc993789b0d9375a3e166ace78def65 Reavo End <decidel@gmail.com> 1780400958 -0700\tcommit: feat(M3b-B1): broker kernel hosts a real PTY child over the IPC\n181ce9194bc993789b0d9375a3e166ace78def65 c40524f8a2f92fd72a795d7a386031c1953fae51 Reavo End <decidel@gmail.com> 1780401821 -0700\tcommit: feat(M3b-B2): brain + gapless handoff (kill/restart survives, fresh gen_start)\nc40524f8a2f92fd72a795d7a386031c1953fae51 33d2a716b19dc83fc83e594d621d65b2fbd6fb12 Reavo End <decidel@gmail.com> 1780402840 -0700\tcommit: feat(M3b-B3): consolidate interim listen/pulse/Psyche loops into the brain\n33d2a716b19dc83fc83e594d621d65b2fbd6fb12 978e62b91fb28c37334061785c883244c9a7bf8a Reavo End <decidel@gmail.com> 1780403208 -0700\tcommit: Continuous work encouragement rev\n978e62b91fb28c37334061785c883244c9a7bf8a d3ce22ab7bf2874762d928cf4ad3482cea0d20c6 Reavo End <decidel@gmail.com> 1780404013 -0700\tcommit: M3b B4: daemon-authoritative liveness behind one resolver\nd3ce22ab7bf2874762d928cf4ad3482cea0d20c6 35072def7f5434b233d54d5dd289b8f77df63ed3 Reavo End <decidel@gmail.com> 1780437194 -0700\tcommit: feat(M3b-B5): idempotent broker<->brain boundary -- exactly-once across a brain crash\n35072def7f5434b233d54d5dd289b8f77df63ed3 9ea1e403736dbc7bfa235e624b5107f25341ac8a Reavo End <decidel@gmail.com> 1780464869 -0700\tcommit: docs: M4 design — adapter/shell/endpoint lifecycle decisions + fork (ADR-0010)\n9ea1e403736dbc7bfa235e624b5107f25341ac8a 25c8025c955bf011cabe9e258115c0f298399330 Reavo End <decidel@gmail.com> 1780469305 -0700\tcommit: docs: adopt BranchStore (git-KV) as the coarse/durable state substrate (ADR-0011)\n25c8025c955bf011cabe9e258115c0f298399330 115dabb85103a3c6ca7f64084d1994bf72e27c22 Reavo End <decidel@gmail.com> 1780469551 -0700\tcommit: docs(spike-07): BranchStore git-KV bench — PASS, coarse/hot boundary empirically grounded (ADR-0011)\n115dabb85103a3c6ca7f64084d1994bf72e27c22 c848ebd736375677a84c6d8c072be67dac347cda Reavo End <decidel@gmail.com> 1780471127 -0700\tcommit: docs(spike-07): in-process gix true number (~4.7ms/commit) via attractor pilot\nc848ebd736375677a84c6d8c072be67dac347cda c1c4f649966e0f302d3a8fe67bb3b93d3a6d928c Reavo End <decidel@gmail.com> 1780477473 -0700\tcommit: feat(daemon): B6 — auto-start + in-memory seed map (REQ-DAEMON-3, REQ-START-3)\nc1c4f649966e0f302d3a8fe67bb3b93d3a6d928c d06af5dc1f22276c3dd0458b5fe39d8291057e3d Reavo End <decidel@gmail.com> 1780478214 -0700\tcommit: feat(daemon): B7 — orphan-watch supervised-crash signoff (REQ-DAEMON-4)\nd06af5dc1f22276c3dd0458b5fe39d8291057e3d 16f4c8e017586982914e2ae8337b3036540e64ff Reavo End <decidel@gmail.com> 1780479906 -0700\tcommit: feat(daemon): B8 — PTY-digest daemon-half (REQ-TERM-4 int)\n16f4c8e017586982914e2ae8337b3036540e64ff 20817a6c519af2e676b1f37197dc4bb71c1a2e7e Reavo End <decidel@gmail.com> 1780480418 -0700\tcommit: feat(daemon): B9 — M3b activation sweep + daemon E2E (REQ-DAEMON-1/2/4 int)\n20817a6c519af2e676b1f37197dc4bb71c1a2e7e 2573101b65c6a1b0e2d119f4277762f69b4638d8 Reavo End <decidel@gmail.com> 1780504446 -0700\tcommit: fix(daemon): EffectJournal::open creates missing parent dir (broker-bind CI failure)\n2573101b65c6a1b0e2d119f4277762f69b4638d8 fe0acceaedb20cefc016c3f75baab16143c1128e Reavo End <decidel@gmail.com> 1780505326 -0700\tcommit: feat(daemon): M3c-C0 self-update engine + brain-only zero-interruption swap\nfe0acceaedb20cefc016c3f75baab16143c1128e ac4473acf248ed60504035e5a4a4820a4e48b339 Reavo End <decidel@gmail.com> 1780505958 -0700\tcommit: feat(daemon): M3c-C1 signed release verification + rollback hardening\nac4473acf248ed60504035e5a4a4820a4e48b339 b267f6e92f841c825a80eb60a2e785fff8f04174 Reavo End <decidel@gmail.com> 1780506686 -0700\tcommit: feat(daemon): M3c-C2 self-update consent gating + last-active resolver\nb267f6e92f841c825a80eb60a2e785fff8f04174 03a245c2085d77ac89ba31b4b90f707b085a70cc Reavo End <decidel@gmail.com> 1780507417 -0700\tcommit: feat(daemon): M3c-C3 adapter ripple-update + adapter content signing\n03a245c2085d77ac89ba31b4b90f707b085a70cc a14639a405d9f9122323a77cb847f17643550b71 Reavo End <decidel@gmail.com> 1780507695 -0700\tcommit: docs(m3c): M3c-C4 activation sweep + M3 complete + M4 stub\na14639a405d9f9122323a77cb847f17643550b71 ca6522b5ddfea0d433a3aa3732595b0d30f73b52 Reavo End <decidel@gmail.com> 1780508932 -0700\tcommit: docs(m4): expand M4-PLAN D-task layer at M4 start\nca6522b5ddfea0d433a3aa3732595b0d30f73b52 c47d2c6515c40e7712b4f40bb22c48043ddfce5e Reavo End <decidel@gmail.com> 1780508944 -0700\tcommit: feat(net): M4-D0 scaffold spt-net crate\nc47d2c6515c40e7712b4f40bb22c48043ddfce5e c49e522a25c92cae85e5687440b7e5091a625057 Reavo End <decidel@gmail.com> 1780509753 -0700\tcommit: docs(m4): record D6 cross-node merge-model decision (#7)\nc49e522a25c92cae85e5687440b7e5091a625057 3f726edf1df960cdc81c874563d317e035898dd0 Reavo End <decidel@gmail.com> 1780509775 -0700\tcommit: feat(net): M4-D1 iroh endpoint + identity binding + relay policy\n3f726edf1df960cdc81c874563d317e035898dd0 51981c4ecc51864a71748e530cb29023fa47f1e6 Reavo End <decidel@gmail.com> 1780509976 -0700\tcommit: feat(net): M4-D1c mDNS LAN discovery + connect-by-id\n51981c4ecc51864a71748e530cb29023fa47f1e6 d4c04d647e19af22fda4d163ec4c3ca60ca4ac64 Reavo End <decidel@gmail.com> 1780513837 -0700\tcommit: docs(m4): psyche outbound model — ADR-0012 + CONTEXT + hazard 7.3 + D7.5\nd4c04d647e19af22fda4d163ec4c3ca60ca4ac64 bfacfb855780e9e696ced4a638a6543a7e219593 Reavo End <decidel@gmail.com> 1780514489 -0700\tcommit: docs(m4): capture daemon non-blocking-scheduling invariant (hazard 7.4)\nbfacfb855780e9e696ced4a638a6543a7e219593 56f2190ba24ee7d3a337b0f1f4dd486d5756d660 Reavo End <decidel@gmail.com> 1780515295 -0700\tcommit: feat(net): M4-D2a TOTP (RFC 6238) + register D2 pairing hazard reqs\n56f2190ba24ee7d3a337b0f1f4dd486d5756d660 597786e5e85b6bc3cfc0ae02745c88931877e39b Reavo End <decidel@gmail.com> 1780515694 -0700\tcommit: feat(net): M4-D2b SPAKE2 handshake + pairing-transcript binding (#12)\n597786e5e85b6bc3cfc0ae02745c88931877e39b 75f967c960709596dc76ba852ac741cf767c3008 Reavo End <decidel@gmail.com> 1780516074 -0700\tcommit: feat(store): M4-D2c subnet seed store + epoch rotation (#10)\n75f967c960709596dc76ba852ac741cf767c3008 b0bdaab439be50747f60ee6e10baafadf049a54c Reavo End <decidel@gmail.com> 1780516898 -0700\tcommit: feat(net): M4-D2d subnet-global pairing rate limit (#11)\nb0bdaab439be50747f60ee6e10baafadf049a54c 1df7331dfe1cc86c16b0958184b8700ff7ed6586 Reavo End <decidel@gmail.com> 1780517211 -0700\tcommit: feat(store): M4-D2e trust store TOFU + warn-on-change (REQ-PAIR-2)\n1df7331dfe1cc86c16b0958184b8700ff7ed6586 f4042352d7e59ac2da6949bd1b19388e165d9298 Reavo End <decidel@gmail.com> 1780517341 -0700\tcommit: docs(m4): JIT plan for D2-wire pairing ALPN driver\nf4042352d7e59ac2da6949bd1b19388e165d9298 2ae22794cb42352431f03c8353938aeca7798719 Reavo End <decidel@gmail.com> 1780518704 -0700\tcommit: @\n2ae22794cb42352431f03c8353938aeca7798719 08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 Reavo End <decidel@gmail.com> 1780518748 -0700\tcommit (amend): feat(net): M4-D2-wire pairing ALPN ceremony driver (REQ-PAIR-1)\n08c6c9b744dc3bd26c0f6ae1013f2520d68a7c68 452358d79c2238142e99956e32dc3ce340fcee1a Reavo End <decidel@gmail.com> 1780522150 -0700\tcommit: feat(net): M4-D2f multi-subnet ceremony + seed transfer (REQ-PAIR-4/5)\n452358d79c2238142e99956e32dc3ce340fcee1a 72570d5c6e191afa75ade22787422ae8ebe7708a Reavo End <decidel@gmail.com> 1780523359 -0700\tcommit: docs(m4): JIT plan for D2g elevation-gated show-totp\n72570d5c6e191afa75ade22787422ae8ebe7708a fb201d61bd3b42deceb14ddafcab0c971197f723 Reavo End <decidel@gmail.com> 1780524004 -0700\tcommit: @\nfb201d61bd3b42deceb14ddafcab0c971197f723 f2cb019ef3ef143b3319ce7565ef9eebe349bc46 Reavo End <decidel@gmail.com> 1780524028 -0700\tcommit (amend): feat(net): M4-D2g elevation-gated `spt pair show-totp` (REQ-PAIR-3/6)\nf2cb019ef3ef143b3319ce7565ef9eebe349bc46 a82446f8077aec0f932ec70ccab16af8b2e9d94e Reavo End <decidel@gmail.com> 1780524412 -0700\tcommit: fix(net): silence D2g Elevation::Unknown dead_code on unix\na82446f8077aec0f932ec70ccab16af8b2e9d94e 8b47fa0051613de1fd9e5865de4259a39857615d Reavo End <decidel@gmail.com> 1780525691 -0700\tcommit: feat(net): M4-D3a/b subnet registry model + epoch lease (REQ-INST-7, hazard #8)\n8b47fa0051613de1fd9e5865de4259a39857615d de8167a763b07bb7d8f99a963ab05a88c031b344 Reavo End <decidel@gmail.com> 1780526470 -0700\tcommit: feat(net): M4-D3c qualified addressing + resolution policy (REQ-INST-10)\nde8167a763b07bb7d8f99a963ab05a88c031b344 afdc7340b73cf2e845a1fed204b0722737c2f20a Reavo End <decidel@gmail.com> 1780527380 -0700\tcommit: feat(net): M4-D3d join-time bare-id collision check (REQ-INST-9)\nafdc7340b73cf2e845a1fed204b0722737c2f20a affa65c6ce943e945eafc6a044ad8e078f9a050d Reavo End <decidel@gmail.com> 1780528414 -0700\tcommit: feat(net): M4-D3e per-(endpoint,subnet) visibility + sync gate (REQ-INST-12/13)\naffa65c6ce943e945eafc6a044ad8e078f9a050d 2766898be6745c9ffa82a19bd88d6014f1bc388b Reavo End <decidel@gmail.com> 1780529038 -0700\tcommit: feat(net): M4-D3f rename ripple — registry rows + perch tree + CLI (REQ-INST-11)\n2766898be6745c9ffa82a19bd88d6014f1bc388b 2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b Reavo End <decidel@gmail.com> 1780529157 -0700\tcommit: docs(m4): JIT plan for D4 broker QUIC ownership\n2cbf91ff1f76ddf43f058ffd7e97c7d8c9d5503b 9601fe71d1a745bec10933cea66395ee8d14bdc9 Reavo End <decidel@gmail.com> 1780531019 -0700\tcommit: feat(net): M4-D4a broker-owned iroh endpoint + net IPC frames (ADR-0004 SB)\n9601fe71d1a745bec10933cea66395ee8d14bdc9 fa9b26f0d08170bc164684bce08548ae1f919312 Reavo End <decidel@gmail.com> 1780531349 -0700\tcommit: test(net): loopback-scoped sockets for hermetic endpoint tests\nfa9b26f0d08170bc164684bce08548ae1f919312 88510c5014255158b86341e60816db4af982ffa7 Reavo End <decidel@gmail.com> 1780531487 -0700\tcommit (amend): test(net): loopback-scoped sockets for hermetic endpoint tests\n88510c5014255158b86341e60816db4af982ffa7 518ee0ddf788038eedd874ea348c809060801e94 Reavo End <decidel@gmail.com> 1780531500 -0700\tcommit (amend): test(net): loopback-scoped sockets + no portmapper for hermetic endpoint tests\n518ee0ddf788038eedd874ea348c809060801e94 20b05788920237d2df7a6da452d55dc9e510947c Reavo End <decidel@gmail.com> 1780531746 -0700\tcommit: fix(store): pid 0 reads dead on every OS - D3f Linux CI regression\n20b05788920237d2df7a6da452d55dc9e510947c 0f67076f3f6d249179869de8224b26d02eeacdc1 Reavo End <decidel@gmail.com> 1780532105 -0700\tcommit: fix(store): deterministic rename ripple order - read_dir is OS-dependent\n0f67076f3f6d249179869de8224b26d02eeacdc1 0067170f7f1b96c3b651065e354aad0f6568abc8 Reavo End <decidel@gmail.com> 1780533148 -0700\tcommit: feat(net): M4-D4b gapless exactly-once QUIC stream survival (Spike #3 A-D)\n0067170f7f1b96c3b651065e354aad0f6568abc8 5bfa9e770aa50969a045072ee7ab0af4c4e382a3 Reavo End <decidel@gmail.com> 1780535351 -0700\tcommit: ci: phase 1 of self-host migration - windows leg on hfenduleam runner\n5bfa9e770aa50969a045072ee7ab0af4c4e382a3 05f08bc60e8fccce2416a9737b39d2142e2f70c5 Reavo End <decidel@gmail.com> 1780535444 -0700\tcommit: ci: self-hosted runner owns its rust toolchain; fix runner PATH\n05f08bc60e8fccce2416a9737b39d2142e2f70c5 02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 Reavo End <decidel@gmail.com> 1780536384 -0700\tcommit: ci: phase 2 of self-host migration - linux leg + traceability on gravity\n02c4ff7fe5fe0dbfd18e2d1311a61f816e635ad3 d70ced7d793e5a96185e5b178974545914ad5431 Reavo End <decidel@gmail.com> 1780536763 -0700\tcommit: ci: traceability uses gravity's source-built traceable-reqs\nd70ced7d793e5a96185e5b178974545914ad5431 1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 Reavo End <decidel@gmail.com> 1780537825 -0700\tcommit: feat(net): M4-D4c PresenceChannel broker seam (REQ-EP-4)\n1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695 237b1bb42a59cd7e112c8bf00badb4078ed7133c Reavo End <decidel@gmail.com> 1780538471 -0700\tcommit: feat(net): M4-D4d registry replication over the wire\n237b1bb42a59cd7e112c8bf00badb4078ed7133c 924e6b4bc71f742ba422bddade056ae24a4010fe Reavo End <decidel@gmail.com> 1780538587 -0700\tcommit: docs: begin fault-injection acceptance matrix (ADR-0002 SERIOUS #6, M4-D4 piece 5)\n924e6b4bc71f742ba422bddade056ae24a4010fe 017d103f02a7229eee2a27f60c1b2bb6f776c4f3 Reavo End <decidel@gmail.com> 1780538932 -0700\tcommit: fix(store): registry open creates the owlery dir — KNOWN-HAZARDS 4.9\n017d103f02a7229eee2a27f60c1b2bb6f776c4f3 1cbeb6d32573fd75c22125a8607c03b6270fad1b Reavo End <decidel@gmail.com> 1780539806 -0700\tcommit: docs: M4-D5 JIT plan — WAN msg, remote attach, reach-back, access whitelist\n1cbeb6d32573fd75c22125a8607c03b6270fad1b caab43332f3dec1bfdba4c526883ccd7c805a8dc Reavo End <decidel@gmail.com> 1780540739 -0700\tcommit: feat(net): M4-D5a WAN message delivery over the broker wire\ncaab43332f3dec1bfdba4c526883ccd7c805a8dc ef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 Reavo End <decidel@gmail.com> 1780541062 -0700\tcommit: fix(test): wanmsg life-1 loop satisfies clippy::never_loop (CI rustc 1.96)\nef92e739ea5be9ecd12c77669d9d7d40fd73b6e4 71e301597060f3104ae565769d013c097fc4c864 Reavo End <decidel@gmail.com> 1780541328 -0700\tcommit: ci: bump actions/checkout v4 -> v5 (Node 20 deprecation, forced Node 24 on 2026-06-16)\n71e301597060f3104ae565769d013c097fc4c864 4ea3b3733b356d989b7d8af803dbb41dddbf3bcb Reavo End <decidel@gmail.com> 1780542941 -0700\tcommit: feat(net): M4-D5b remote terminal attach (REQ-INST-8)\n4ea3b3733b356d989b7d8af803dbb41dddbf3bcb 269b8ef5bffa3b003519d98121c931c32054cbdd Reavo End <decidel@gmail.com> 1780544566 -0700\tcommit: feat(net): M4-D5c remote-drive detection + file transfer (REQ-REACH-1)\n269b8ef5bffa3b003519d98121c931c32054cbdd fcad41ca27210276606356d72ab4abde0a3d65c0 Reavo End <decidel@gmail.com> 1780544873 -0700\tcommit: fix(test): attach restart ground-truth compares prefixes through the marker\nfcad41ca27210276606356d72ab4abde0a3d65c0 f49aba1984a28ca066345bfe218b839d5a5ff8e0 Reavo End <decidel@gmail.com> 1780545918 -0700\tcommit: feat(sec): M4-D5d endpoint access whitelist (REQ-SEC-1, ADR-0009)\nf49aba1984a28ca066345bfe218b839d5a5ff8e0 8f4ec2465b477617a9cd359a983ec41d93f0e099 Reavo End <decidel@gmail.com> 1780549041 -0700\tcommit: docs: M4-D6 design locked — ADR-0013 Psyche-reconciled context merge\n8f4ec2465b477617a9cd359a983ec41d93f0e099 8b42452ce7f8277b239242efd853dc93e763fc17 Reavo End <decidel@gmail.com> 1780549527 -0700\tcommit: feat(store): M4-D6a BranchStore + two-tier ContextStore + project_id (REQ-STORE-1, ADR-0011/0013)\n8b42452ce7f8277b239242efd853dc93e763fc17 1ef980218554bf2f7ba8c8cd7f7e210e2b34129f Reavo End <decidel@gmail.com> 1780550270 -0700\tcommit: feat(live): M4-D6a two-tier context routing + rename context arm (REQ-INST-2)\n1ef980218554bf2f7ba8c8cd7f7e210e2b34129f 7e9c2b54d68fd99210f3e5b133a64d254285ab76 Reavo End <decidel@gmail.com> 1780550799 -0700\tcommit: feat(store): M4-D6b distributed precedence — marker v2, version vectors, conflict artifacts\n7e9c2b54d68fd99210f3e5b133a64d254285ab76 50620e4a4834900120d725a13d0c6059ae841e6c Reavo End <decidel@gmail.com> 1780551377 -0700\tcommit: feat(live): M4-D6b Psyche reconcile + bounded turn driver (D7.5a pulled forward)\n50620e4a4834900120d725a13d0c6059ae841e6c 1ba6e06453577d88aaf1c1382a58a4a379069b1a Reavo End <decidel@gmail.com> 1780551649 -0700\tcommit: fix(test): reconcile unix mock command nested single quotes broke manifest TOML\n1ba6e06453577d88aaf1c1382a58a4a379069b1a f08aedcb3cd2615712f3f30daf07a35ee9870aac Reavo End <decidel@gmail.com> 1780552853 -0700\tcommit: feat(store): M4-D6c-1 bundle sync core — BranchStore bundle plumbing + per-file vector merge driver (REQ-NET-3)\nf08aedcb3cd2615712f3f30daf07a35ee9870aac fa97b80ef3a560c4fd77311b202cf982d7674abc Reavo End <decidel@gmail.com> 1780556100 -0700\tcommit: feat(net,daemon): M4-D6c-2 bundle sync over broker streams — wire records, server-side gate, reconcile wiring (REQ-NET-3, REQ-INST-5)\nfa97b80ef3a560c4fd77311b202cf982d7674abc 0d05e5c5512ba64986d032f543841b9f3301fa72 Reavo End <decidel@gmail.com> 1780558624 -0700\tcommit: feat(net,daemon): M4-D7-1 update propagation wire + staged-release cache (REQ-UPD-1)\n0d05e5c5512ba64986d032f543841b9f3301fa72 b67d4321b85d3633b67124933960f8ed25110150 Reavo End <decidel@gmail.com> 1780558959 -0700\tcommit: feat(daemon): M4-D7-2 update propagation serve/request drivers + E2E (REQ-UPD-1)\nb67d4321b85d3633b67124933960f8ed25110150 9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 Reavo End <decidel@gmail.com> 1780559260 -0700\tcommit: docs(plan): mark M4-D7 complete (both slices CI-green)\n9b5c39cf9139a57185cfb7ba0b9d24e8dcee71b3 6fac5390690ed9c6ed759cca15e289d854009783 Reavo End <decidel@gmail.com> 1780559950 -0700\tcommit: feat(proto,live): M4-D7.5-1 event-type taxonomy + Psyche intent parser (ADR-0012)\n6fac5390690ed9c6ed759cca15e289d854009783 3f771e11c7d4762fc5721945866982dcd21e4a9c Reavo End <decidel@gmail.com> 1780560620 -0700\tcommit: feat(daemon): M4-D7.5-2 Psyche outbound relay + sanitize boundary (REQ-HAZARD-PSYCHE-OUTBOUND-PROXY)\n3f771e11c7d4762fc5721945866982dcd21e4a9c e405b4329b3c312b6bf73ca9b106de4f86c8f155 Reavo End <decidel@gmail.com> 1780560933 -0700\tcommit: docs(plan): mark M4-D7.5 complete (both slices CI-green)\ne405b4329b3c312b6bf73ca9b106de4f86c8f155 e1120e4938064baee56ea0cbc5b8a4fc7c390df0 Reavo End <decidel@gmail.com> 1780561369 -0700\tcommit: docs(plan): draft M4-D8-PLAN (subnet notification primitive, ADR-0007)\ne1120e4938064baee56ea0cbc5b8a4fc7c390df0 9a6548fbd0c85a20715632a62ff4c5c7c767d142 Reavo End <decidel@gmail.com> 1780561600 -0700\tcommit: feat(store): M4-D8-1 notif store + semilattice merge (ADR-0007)\n9a6548fbd0c85a20715632a62ff4c5c7c767d142 f612b3ff4f677b56048a2d6c1294ea62054e513b Reavo End <decidel@gmail.com> 1780562053 -0700\tcommit: feat(net,daemon): M4-D8-2 notif replication record + gated feed drivers + E2E (ADR-0007)\nf612b3ff4f677b56048a2d6c1294ea62054e513b 45484109079454c594a20515fe730480d0ad3db5 Reavo End <decidel@gmail.com> 1780562746 -0700\tcommit: feat(daemon,spt): M4-D8-3 notif first-fire + resurface engine + producers (REQ-NOTIF-1)\n45484109079454c594a20515fe730480d0ad3db5 126d8434d0c56ed0f3092d477a251c188a60e6ad Reavo End <decidel@gmail.com> 1780563316 -0700\tcommit: feat(spt,runtime,daemon): M4-D8-4 spt notify CLI + notif_command manifest seam (REQ-NOTIF-2)\n126d8434d0c56ed0f3092d477a251c188a60e6ad b8d75af3cf12d899aff2b1af51caf44482d141a6 Reavo End <decidel@gmail.com> 1780563620 -0700\tcommit: docs(plan): mark M4-D8 complete (all four slices CI-green)\nb8d75af3cf12d899aff2b1af51caf44482d141a6 a71d23a3102a01168a47a47491489057cead4053 Reavo End <decidel@gmail.com> 1780570329 -0700\tcommit: docs(plan): draft M4-D9 JIT plan (closeout: loops, dormancy, INST-14/15, two-host)\na71d23a3102a01168a47a47491489057cead4053 ea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 Reavo End <decidel@gmail.com> 1780571295 -0700\tcommit: feat(daemon): M4-D9-1-1 inbound net-stream dispatch (production serve loops)\nea73c9a8ddff71e8a4cf98576d70bb63cbed4b08 822d3ce76ce7de9b15bfdbf835ac244c249a6157 Reavo End <decidel@gmail.com> 1780572144 -0700\tcommit: feat(daemon,net): M4-D9-1-2 registry host + outbound peer pump (self-driving subnet)\n822d3ce76ce7de9b15bfdbf835ac244c249a6157 b15cf2cee93b33ac5f6e9bfb7534428b1cb05560 Reavo End <decidel@gmail.com> 1780572683 -0700\tcommit: feat(daemon): M4-D9-1-3 production VerifyPolicy + update pump leg (REQ-UPD-1/2/4)\nb15cf2cee93b33ac5f6e9bfb7534428b1cb05560 af7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 Reavo End <decidel@gmail.com> 1780572980 -0700\tcommit: feat(spt): M4-D9-1-4 spt send WAN leg (registry-resolved off-node delivery)\naf7f394f8434b9cb4d3dc46ccd6ae6a36d698bc7 0dd85d5bae817201f4d239d04ddf4a0431e6ec2f Reavo End <decidel@gmail.com> 1780573536 -0700\tcommit: feat(daemon,net): M4-D9-1-5 attach/xfer dispatch arms (remote-drive + file transfer production serve)\n0dd85d5bae817201f4d239d04ddf4a0431e6ec2f cb81e831eabe86dd7da4c64658eb2caf1ca3c99e Reavo End <decidel@gmail.com> 1780573894 -0700\tcommit: docs(plan): mark M4-D9-1 complete (five CI-green sub-slices)\ncb81e831eabe86dd7da4c64658eb2caf1ca3c99e 031578398664aa6559552f2dfd0d399928d5684d Reavo End <decidel@gmail.com> 1780574749 -0700\tcommit: feat(daemon,store,net): M4-D9-2-1 resting-state model (REQ-INST-3 activated)\n031578398664aa6559552f2dfd0d399928d5684d 8a7afd6792b75562b7699fc89b36c79d9db2b656 Reavo End <decidel@gmail.com> 1780575335 -0700\tcommit: feat(daemon): M4-D9-2-2 transition echo + wake effects (REQ-INST-4 activated)\n8a7afd6792b75562b7699fc89b36c79d9db2b656 56a98db5b944dc51f7c458c18f6f671332b05e2d Reavo End <decidel@gmail.com> 1780575928 -0700\tcommit: feat(daemon,spt): M4-D9-2-3 resting-edge production feeds + spt suspend/wake\n56a98db5b944dc51f7c458c18f6f671332b05e2d a52dacbe13b358978b89347801db32701aa5f15e Reavo End <decidel@gmail.com> 1780576258 -0700\tcommit: docs(plan): mark M4-D9-2 complete (three CI-green sub-slices)\na52dacbe13b358978b89347801db32701aa5f15e 785002e707f71652c714a13d71e6d5c100db003a Reavo End <decidel@gmail.com> 1780576758 -0700\tcommit: feat(daemon,docs): M4-D9-3 dormancy budget measured + policy locked [budget]\n785002e707f71652c714a13d71e6d5c100db003a 6f61a89677d039dc9816f2f736a05b3915f75e00 Reavo End <decidel@gmail.com> 1780577582 -0700\tcommit: docs(budget): M4-D9-3 gravity numbers + per-OS budget steps [budget]\n6f61a89677d039dc9816f2f736a05b3915f75e00 650c7fb326ed3478f5661891641a32b1b712ce19 Reavo End <decidel@gmail.com> 1780578309 -0700\tcommit: fix(ci): ASCII-only string in the Windows budget step [budget]\n650c7fb326ed3478f5661891641a32b1b712ce19 0ea318b3d77cdcc84c56fe45dad84de095baff96 Reavo End <decidel@gmail.com> 1780579051 -0700\tcommit: feat(net,daemon,store,spt): M4-D9-4 resource advertisement (REQ-INST-14 activated)\n0ea318b3d77cdcc84c56fe45dad84de095baff96 e69f59add554232ee75321a10a1641ab2bbece91 Reavo End <decidel@gmail.com> 1780579804 -0700\tcommit: feat(store,spt,daemon): M4-D9-5-1 immutable home subnet at creation\ne69f59add554232ee75321a10a1641ab2bbece91 0b30dea4a0f4829908bb0de71db07faafaa91e98 Reavo End <decidel@gmail.com> 1780580326 -0700\tcommit: feat(store,spt): M4-D9-5-2 spt fork — copy-then-diverge (REQ-INST-15 activated)\n0b30dea4a0f4829908bb0de71db07faafaa91e98 92fc261c83af6f2a09cf137b6f8ed9150bd21b32 Reavo End <decidel@gmail.com> 1780580669 -0700\tcommit: docs(plan): mark M4-D9-3/4/5 complete (budget measured, resources, home+fork)\n92fc261c83af6f2a09cf137b6f8ed9150bd21b32 107240eea81599ba191fb2cd2fe93d9f2c8a0390 Reavo End <decidel@gmail.com> 1780582455 -0700\tcommit: feat(net,daemon,ci,docs): M4-D9-6-1 two-host rig ladder — env-gated int tests + runbook\n107240eea81599ba191fb2cd2fe93d9f2c8a0390 a86371c9cfd87639c4d5dd5e2c82d464bc028ed0 Reavo End <decidel@gmail.com> 1780583265 -0700\tcommit: test(rig): two-host ladder rig run + durable flake ledger [twohost]\na86371c9cfd87639c4d5dd5e2c82d464bc028ed0 2ba79061f05c5b273485fac83671cf2bdbc4681d Reavo End <decidel@gmail.com> 1780583853 -0700\tcommit: docs(reqs,plan): M4-D9-6 complete — rig proof evidence, int stages live, gh-interim retired\n2ba79061f05c5b273485fac83671cf2bdbc4681d becb6ecb2a5458879c3c4f77139cd1ec31e39a36 Reavo End <decidel@gmail.com> 1780584205 -0700\tcommit: docs(plan,reqs): M4-D9-7 closeout sweep — M4 DELIVERED\nbecb6ecb2a5458879c3c4f77139cd1ec31e39a36 a6f5cabac5f0a8d340a5aa81206f94e096d215a5 Reavo End <decidel@gmail.com> 1780586451 -0700\tcommit: docs(plan): author M5-PLAN — scope locked at M5 start via user forks\na6f5cabac5f0a8d340a5aa81206f94e096d215a5 2a59e7d356d6329b2b35e25095ec355ee384fa18 Reavo End <decidel@gmail.com> 1780586580 -0700\tcommit: docs(reqs,roadmap): M5-D0 — scope alignment + requirement registry prep","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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\HEAD"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1502,"totalBytes":41799,"outputLines":203,"outputBytes":41799,"shownRange":{"start":1,"end":203},"nextOffset":204}}},"isError":false,"timestamp":1784271515093}}
{"type":"message","id":"b8cc68f9","parentId":"3cf2f7fa","timestamp":"2026-07-17T06:58:35.096Z","message":{"role":"toolResult","toolCallId":"call_nNvi1MnjF60RKQw4K3yY83sS|fc_089095e18118ee7d016a59d29af79c8190958f782549f129f9","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## nethost.rs\n 130|/// either, but only a POISON (real write failure on the conn) cancels the\n*131|/// stream's lease; an overflow is a slow-but-healthy brain being detached.\n 132|// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 133|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 134|enum SeatFail {\n...\n 174|    /// re-serve \"got seq 134 want 6\" CI red.)\n*175|    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\n*176|    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\n 177|    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\n 178|    /// a traffic-quiet stream where no producer enqueue would ever run\n 179|    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\n*180|    /// left the lease alive until the next append; the T4 forwarder starved).\n 181|    /// A LATE cancel from a superseded writer — poisoned OR healthily\n 182|    /// displaced — is harmless by construction: every install mints a fresh\n*183|    /// lease Arc, so an old writer cancels only its own generation's Arc.\n 184|    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 185|    fn install(\n 186|        sub: SharedSend,\n 187|        replay: Vec<Envelope>,\n*188|        lease: Option<Arc<StreamLease>>,\n 189|    ) -> SubscriberSeat {\n 190|        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\n 191|        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\n...\n 195|        let writer_poisoned = Arc::clone(&poisoned);\n*196|        std::thread::spawn(move || {\n 197|            let poison = |halt_reason: &str| {\n*198|                writer_poisoned.store(true, Ordering::Release);\n*199|                if let Some(l) = &lease {\n 200|                    l.cancel();\n 201|                }\n 202|                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\n...\n 208|                    poison(\"replay write failed\");\n*209|                    writer_done.store(true, Ordering::Release);\n 210|                    return;\n 211|                }\n 212|            }\n...\n 219|            }\n*220|            writer_done.store(true, Ordering::Release);\n 221|        });\n 222|        SubscriberSeat {\n 223|            sub,\n...\n 253|\n*254|/// A stream's serve lease (ADR-0038 Amendment fix 4; built here so the\n 255|/// DAEMON-LIFECYCLE C2 work consumes it rather than growing a second\n 256|/// mechanism): canceled when the stream's subscriber seat POISONS, which\n 257|/// makes every later `send_stream` on the stream refuse — the paired\n...\n 261|#[derive(Default)]\n*262|pub struct StreamLease {\n 263|    canceled: std::sync::atomic::AtomicBool,\n 264|}\n 265|\n*266|impl StreamLease {\n*267|    /// Cancel the lease (subscriber poison; C2 will add its own callers).\n 268|    pub fn cancel(&self) {\n*269|        self.canceled.store(true, Ordering::Release);\n 270|    }\n 271|\n*272|    /// Whether the lease was canceled.\n 273|    pub fn is_canceled(&self) -> bool {\n 274|        self.canceled.load(Ordering::Acquire)\n 275|    }\n...\n 334|// [impl->REQ-DAEMON-2]\n*335|struct StreamLog {\n 336|    stream_id: u64,\n 337|    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n 338|    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n...\n 351|    subscriber: Option<SubscriberSeat>,\n*352|    /// The stream's serve lease (Amendment fix 4): canceled on subscriber\n 353|    /// POISON so the paired serve/wire worker's sends refuse.\n*354|    lease: Arc<StreamLease>,\n 355|    /// Read side finished cleanly (peer called finish / conn ended).\n 356|    finished: bool,\n 357|    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n## broker.rs\n 66|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n*67|    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n 68|    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n 69|    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n 70|};\n...\n 200|\n*201|/// RAII release of a single-flight wake claim (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP):\n 202|/// the broker holds an endpoint id in [`Broker::wake_inflight`] for the duration of a\n*203|/// spawn so a concurrent duplicate wake stands down; the guard releases it on EVERY\n 204|/// exit (success, an early `?` spawn/drain error, or a panic), so a failed spawn never\n 205|/// strands the endpoint permanently claimed.\n 206|struct WakeClaimGuard<'a> {\n...\n 282|/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n*283|/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n 284|/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n 285|/// buggy translation binary can therefore stall operator input for at most this\n 286|/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n...\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...\n 307|/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n*308|/// alive; the watchdog's job is ANTI-STALL (release the operator floor), not\n 309|/// execution-verification. Terminating a healthy binary over one eaten `{commit}`\n 310|/// was B6's exact root: the checkpoint clear boundary self-sends a FIRE-with-\n 311|/// nothing-armed that answers zero records + no bare `{commit}`, so EVERY `/clear`\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 800|\n*801|    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n 802|        let seq = self.next_seq;\n 803|        self.next_seq += 1;\n 804|        self.ring.push_back((seq, bytes.to_vec()));\n...\n 823|                    if let Some(sink) = self.viewers.remove(&vid) {\n*824|                        sink.evicted.store(true, Ordering::Release);\n 825|                    }\n 826|                }\n 827|                self.stamp_viewer_count();\n...\n 947|        let writer_blocked = Arc::clone(&write_blocked_since);\n*948|        let writer = thread::spawn(move || {\n 949|            controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate, writer_blocked)\n 950|        });\n 951|        self.controller = Some(ControllerSink {\n...\n 1012|        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n*1013|        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1014|        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1015|        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n 1016|        self.stall_evict_controller(Instant::now(), StampMode::Deferred)\n...\n 1048|    /// controller conn is black-holed reads wedged here until the W2 stall-evict\n*1049|    /// releases it (or the conn EOFs); a cleanly-closed conn is simply absent. A\n 1050|    /// REMOTE operator's wedged conn (`by: Some`) is the W2 stall-evict concern, NOT\n 1051|    /// an old-gen brain conn, so it is EXCLUDED — a stuck remote viewer must never\n 1052|    /// gate a brain promotion. Pure over the injected `now`/`deadline` (no thread).\n...\n 1059|    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n*1060|    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1061|    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1062|    /// record the evict for observability (broker log now; W3 persists the stderr,\n 1063|    /// the tally surfaces on `daemon status`). The take/reattach path and the\n...\n 1080|            \"BRAIN_SUBSCRIBER_STALL_EVICT:{} conn={conn} {} — controller writer blocked > {}ms \\\n*1081|             on a suspended/black-holed brain conn; detached, driven_by released \\\n 1082|             [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1083|            self.session_id,\n 1084|            crate::conn::log_stamp(),\n...\n 1092|            // Deferred: in-memory drop ONLY (the reap closure forbids I/O under the\n*1093|            // shared sessions lock); converge_perch_stamps writes the release off-lock.\n 1094|            StampMode::Deferred => self.controller = None,\n 1095|        }\n 1096|        true\n...\n 1137|        let writer =\n*1138|            thread::spawn(move || viewer_writer(writer_send, session_id, initial, rx, writer_evicted));\n 1139|        self.viewers.insert(\n 1140|            vid,\n 1141|            ViewerSink {\n...\n 1270|                if let Some(sink) = self.viewers.remove(&vid) {\n*1271|                    sink.evicted.store(true, Ordering::Release);\n 1272|                }\n 1273|            }\n 1274|            self.stamp_viewer_count();\n...\n 1283|    /// Generation-validated detach (ADR-0038 Amendment fix 6): `Some(g)` with\n*1284|    /// a controller whose generation differs = a STALE worker's late release —\n 1285|    /// the controller slot is left alone (viewer rows still clean up by ptr).\n 1286|    /// `None` (N-1 / conn-cleanup) = today's ptr-identity behavior.\n 1287|    // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n...\n 1493|        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} conn={conn} {} — controller conn write \\\n*1494|         exceeded its bound; physical conn poisoned + retired (writer released, EOF \\\n 1495|         cleanup follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1496|        crate::conn::log_stamp()\n 1497|    );\n...\n 1789|        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\n*1790|        let writer = thread::spawn(move || input_writer(session, rx));\n 1791|        Arc::new(InputWriter {\n 1792|            tx,\n 1793|            backpressure: AtomicBool::new(false),\n## brain.rs\n 52|    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,\n*53|    KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED,\n 54|    KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,\n 55|    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n 56|    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n...\n 151|    /// The broker's held-streams listing (D4b).\n*152|    NetStreams(NetStreamsReply),\n 153|    /// The broker's hosted-sessions listing (D9-1: session → endpoint).\n 154|    Sessions(SessionsReply),\n 155|    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).\n...\n 790|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n*791|                    return Ok(BrokerEvent::NetStreams(ev));\n 792|                }\n 793|                KIND_SESSIONS_REPLY => {\n 794|                    let ev: SessionsReply = serde_json::from_value(env.payload)\n...\n 900|    /// reader of a session another brain drives, not a logic handoff.\n*901|    pub fn attach(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n 902|        self.session_id = Some(session_id);\n 903|        self.next_seq = from_seq;\n 904|        // #6 (ADR-0031): a COLD attach receives a clean-screen repaint at pseudo-seq\n...\n 923|    /// [`serve_attach`]: crate::attach::serve_attach\n*924|    pub fn attach_as(\n 925|        &mut self,\n 926|        session_id: u64,\n 927|        from_seq: u64,\n...\n 960|    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n*961|    pub fn attach_skip_to_live(&mut self, session_id: u64, by: Option<&str>) -> io::Result<()> {\n 962|        self.session_id = Some(session_id);\n 963|        self.subscribe_with(session_id, u64::MAX, AttachIntent::Viewer, 0, by.map(str::to_string))?;\n 964|        self.session_cursors.insert(session_id, 0);\n...\n 993|    // [impl->REQ-HAZARD-VIEWER-RING-ROLL-SNAP]\n*994|    pub fn attach_as_viewer_snap(\n 995|        &mut self,\n 996|        session_id: u64,\n 997|        from_seq: u64,\n...\n 1435|    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n*1436|    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n 1437|        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438|        loop {\n 1439|            match self.read_event()? {\n*1440|                BrokerEvent::NetStreams(ev) => return Ok(ev),\n 1441|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1442|                _ => continue,\n 1443|            }\n...\n 1479|    // [impl->REQ-STREAM-OPENER-DURABLE]\n*1480|    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n 1481|        self.send(\n*1482|            KIND_NET_STREAM_OPENER,\n 1483|            serde_json::to_value(NetStreamOpenerReq { stream_id })\n 1484|                .expect(\"NetStreamOpenerReq serializes\"),\n 1485|        )?;\n...\n 1487|            match self.read_event()? {\n*1488|                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n 1489|                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n 1490|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n 1491|                    return match reply.opener_b64 {\n...\n 1671|\n*1672|    /// Release this connection's controller/viewer role on `session_id`\n 1673|    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when\n 1674|    /// the viewport ends so the broker clears `driven_by` / `viewer_count`\n 1675|    /// promptly. Best-effort: a dropped connection clears the role anyway.\n...\n 1679|\n*1680|    /// Generation-validated release (ADR-0038 Amendment fix 6): the broker\n 1681|    /// clears the controller only if its generation still matches `gen` — a\n 1682|    /// STALE worker's late detach can never clear a replacement controller.\n 1683|    // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n...\n 1919|        let brain_handle =\n*1920|            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect(\"brain cold_start\"));\n 1921|\n 1922|        let mut server = listener.accept().expect(\"accept the brain\");\n 1923|        crate::transport::recv_hello(&mut server, Role::Brain).expect(\"stub recv_hello\");\n...\n 1931|        let (feed, frames) = sync_channel::<Envelope>(64);\n*1932|        let server_thread = thread::spawn(move || {\n 1933|            while let Ok(env) = frames.recv() {\n 1934|                if write_frame(&mut send, &env).is_err() {\n 1935|                    break;"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs, crates/spt-daemon/src/broker.rs, crates/spt-daemon/src/brain.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":56,"fileCount":3,"files":["crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/brain.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":20},{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/src/brain.rs","count":16}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/src/\n## nethost.rs\n 130│/// either, but only a POISON (real write failure on the conn) cancels the\n*131│/// stream's lease; an overflow is a slow-but-healthy brain being detached.\n 132│// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 133│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 134│enum SeatFail {\n    │...\n 174│    /// re-serve \"got seq 134 want 6\" CI red.)\n*175│    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\n*176│    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\n 177│    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\n 178│    /// a traffic-quiet stream where no producer enqueue would ever run\n 179│    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\n*180│    /// left the lease alive until the next append; the T4 forwarder starved).\n 181│    /// A LATE cancel from a superseded writer — poisoned OR healthily\n 182│    /// displaced — is harmless by construction: every install mints a fresh\n*183│    /// lease Arc, so an old writer cancels only its own generation's Arc.\n 184│    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 185│    fn install(\n 186│        sub: SharedSend,\n 187│        replay: Vec<Envelope>,\n*188│        lease: Option<Arc<StreamLease>>,\n 189│    ) -> SubscriberSeat {\n 190│        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\n 191│        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\n    │...\n 195│        let writer_poisoned = Arc::clone(&poisoned);\n*196│        std::thread::spawn(move || {\n 197│            let poison = |halt_reason: &str| {\n*198│                writer_poisoned.store(true, Ordering::Release);\n*199│                if let Some(l) = &lease {\n 200│                    l.cancel();\n 201│                }\n 202│                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\n    │...\n 208│                    poison(\"replay write failed\");\n*209│                    writer_done.store(true, Ordering::Release);\n 210│                    return;\n 211│                }\n 212│            }\n    │...\n 219│            }\n*220│            writer_done.store(true, Ordering::Release);\n 221│        });\n 222│        SubscriberSeat {\n 223│            sub,\n    │...\n 253│\n*254│/// A stream's serve lease (ADR-0038 Amendment fix 4; built here so the\n 255│/// DAEMON-LIFECYCLE C2 work consumes it rather than growing a second\n 256│/// mechanism): canceled when the stream's subscriber seat POISONS, which\n 257│/// makes every later `send_stream` on the stream refuse — the paired\n    │...\n 261│#[derive(Default)]\n*262│pub struct StreamLease {\n 263│    canceled: std::sync::atomic::AtomicBool,\n 264│}\n 265│\n*266│impl StreamLease {\n*267│    /// Cancel the lease (subscriber poison; C2 will add its own callers).\n 268│    pub fn cancel(&self) {\n*269│        self.canceled.store(true, Ordering::Release);\n 270│    }\n 271│\n*272│    /// Whether the lease was canceled.\n 273│    pub fn is_canceled(&self) -> bool {\n 274│        self.canceled.load(Ordering::Acquire)\n 275│    }\n    │...\n 334│// [impl->REQ-DAEMON-2]\n*335│struct StreamLog {\n 336│    stream_id: u64,\n 337│    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n 338│    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n    │...\n 351│    subscriber: Option<SubscriberSeat>,\n*352│    /// The stream's serve lease (Amendment fix 4): canceled on subscriber\n 353│    /// POISON so the paired serve/wire worker's sends refuse.\n*354│    lease: Arc<StreamLease>,\n 355│    /// Read side finished cleanly (peer called finish / conn ended).\n 356│    finished: bool,\n 357│    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n## broker.rs\n   66│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n  *67│    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n   68│    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n   69│    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n   70│};\n     │...\n  200│\n *201│/// RAII release of a single-flight wake claim (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP):\n  202│/// the broker holds an endpoint id in [`Broker::wake_inflight`] for the duration of a\n *203│/// spawn so a concurrent duplicate wake stands down; the guard releases it on EVERY\n  204│/// exit (success, an early `?` spawn/drain error, or a panic), so a failed spawn never\n  205│/// strands the endpoint permanently claimed.\n  206│struct WakeClaimGuard<'a> {\n     │...\n  282│/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n *283│/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n  284│/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n  285│/// buggy translation binary can therefore stall operator input for at most this\n  286│/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n     │...\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     │...\n  307│/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n *308│/// alive; the watchdog's job is ANTI-STALL (release the operator floor), not\n  309│/// execution-verification. Terminating a healthy binary over one eaten `{commit}`\n  310│/// was B6's exact root: the checkpoint clear boundary self-sends a FIRE-with-\n  311│/// nothing-armed that answers zero records + no bare `{commit}`, so EVERY `/clear`\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  800│\n *801│    fn append(&mut self, bytes: &[u8]) -> Option<u64> {\n  802│        let seq = self.next_seq;\n  803│        self.next_seq += 1;\n  804│        self.ring.push_back((seq, bytes.to_vec()));\n     │...\n  823│                    if let Some(sink) = self.viewers.remove(&vid) {\n *824│                        sink.evicted.store(true, Ordering::Release);\n  825│                    }\n  826│                }\n  827│                self.stamp_viewer_count();\n     │...\n  947│        let writer_blocked = Arc::clone(&write_blocked_since);\n *948│        let writer = thread::spawn(move || {\n  949│            controller_writer(sid, writer_send, initial, rx, dt, epoch, epoch_gate, writer_blocked)\n  950│        });\n  951│        self.controller = Some(ControllerSink {\n     │...\n 1012│        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n*1013│        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1014│        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1015│        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n 1016│        self.stall_evict_controller(Instant::now(), StampMode::Deferred)\n     │...\n 1048│    /// controller conn is black-holed reads wedged here until the W2 stall-evict\n*1049│    /// releases it (or the conn EOFs); a cleanly-closed conn is simply absent. A\n 1050│    /// REMOTE operator's wedged conn (`by: Some`) is the W2 stall-evict concern, NOT\n 1051│    /// an old-gen brain conn, so it is EXCLUDED — a stuck remote viewer must never\n 1052│    /// gate a brain promotion. Pure over the injected `now`/`deadline` (no thread).\n     │...\n 1059│    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n*1060│    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1061│    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1062│    /// record the evict for observability (broker log now; W3 persists the stderr,\n 1063│    /// the tally surfaces on `daemon status`). The take/reattach path and the\n     │...\n 1080│            \"BRAIN_SUBSCRIBER_STALL_EVICT:{} conn={conn} {} — controller writer blocked > {}ms \\\n*1081│             on a suspended/black-holed brain conn; detached, driven_by released \\\n 1082│             [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1083│            self.session_id,\n 1084│            crate::conn::log_stamp(),\n     │...\n 1092│            // Deferred: in-memory drop ONLY (the reap closure forbids I/O under the\n*1093│            // shared sessions lock); converge_perch_stamps writes the release off-lock.\n 1094│            StampMode::Deferred => self.controller = None,\n 1095│        }\n 1096│        true\n     │...\n 1137│        let writer =\n*1138│            thread::spawn(move || viewer_writer(writer_send, session_id, initial, rx, writer_evicted));\n 1139│        self.viewers.insert(\n 1140│            vid,\n 1141│            ViewerSink {\n     │...\n 1270│                if let Some(sink) = self.viewers.remove(&vid) {\n*1271│                    sink.evicted.store(true, Ordering::Release);\n 1272│                }\n 1273│            }\n 1274│            self.stamp_viewer_count();\n     │...\n 1283│    /// Generation-validated detach (ADR-0038 Amendment fix 6): `Some(g)` with\n*1284│    /// a controller whose generation differs = a STALE worker's late release —\n 1285│    /// the controller slot is left alone (viewer rows still clean up by ptr).\n 1286│    /// `None` (N-1 / conn-cleanup) = today's ptr-identity behavior.\n 1287│    // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n     │...\n 1493│        \"BRAIN_SUBSCRIBER_STALL_EVICT:{sid} conn={conn} {} — controller conn write \\\n*1494│         exceeded its bound; physical conn poisoned + retired (writer released, EOF \\\n 1495│         cleanup follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1496│        crate::conn::log_stamp()\n 1497│    );\n     │...\n 1789│        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\n*1790│        let writer = thread::spawn(move || input_writer(session, rx));\n 1791│        Arc::new(InputWriter {\n 1792│            tx,\n 1793│            backpressure: AtomicBool::new(false),\n## brain.rs\n   52│    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,\n  *53│    KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED,\n   54│    KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,\n   55│    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n   56│    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n     │...\n  151│    /// The broker's held-streams listing (D4b).\n *152│    NetStreams(NetStreamsReply),\n  153│    /// The broker's hosted-sessions listing (D9-1: session → endpoint).\n  154│    Sessions(SessionsReply),\n  155│    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).\n     │...\n  790│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n *791│                    return Ok(BrokerEvent::NetStreams(ev));\n  792│                }\n  793│                KIND_SESSIONS_REPLY => {\n  794│                    let ev: SessionsReply = serde_json::from_value(env.payload)\n     │...\n  900│    /// reader of a session another brain drives, not a logic handoff.\n *901│    pub fn attach(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n  902│        self.session_id = Some(session_id);\n  903│        self.next_seq = from_seq;\n  904│        // #6 (ADR-0031): a COLD attach receives a clean-screen repaint at pseudo-seq\n     │...\n  923│    /// [`serve_attach`]: crate::attach::serve_attach\n *924│    pub fn attach_as(\n  925│        &mut self,\n  926│        session_id: u64,\n  927│        from_seq: u64,\n     │...\n  960│    // [impl->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n *961│    pub fn attach_skip_to_live(&mut self, session_id: u64, by: Option<&str>) -> io::Result<()> {\n  962│        self.session_id = Some(session_id);\n  963│        self.subscribe_with(session_id, u64::MAX, AttachIntent::Viewer, 0, by.map(str::to_string))?;\n  964│        self.session_cursors.insert(session_id, 0);\n     │...\n  993│    // [impl->REQ-HAZARD-VIEWER-RING-ROLL-SNAP]\n *994│    pub fn attach_as_viewer_snap(\n  995│        &mut self,\n  996│        session_id: u64,\n  997│        from_seq: u64,\n     │...\n 1435│    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n*1436│    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n 1437│        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438│        loop {\n 1439│            match self.read_event()? {\n*1440│                BrokerEvent::NetStreams(ev) => return Ok(ev),\n 1441│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1442│                _ => continue,\n 1443│            }\n     │...\n 1479│    // [impl->REQ-STREAM-OPENER-DURABLE]\n*1480│    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n 1481│        self.send(\n*1482│            KIND_NET_STREAM_OPENER,\n 1483│            serde_json::to_value(NetStreamOpenerReq { stream_id })\n 1484│                .expect(\"NetStreamOpenerReq serializes\"),\n 1485│        )?;\n     │...\n 1487│            match self.read_event()? {\n*1488│                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n 1489│                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n 1490│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n 1491│                    return match reply.opener_b64 {\n     │...\n 1671│\n*1672│    /// Release this connection's controller/viewer role on `session_id`\n 1673│    /// (REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when\n 1674│    /// the viewport ends so the broker clears `driven_by` / `viewer_count`\n 1675│    /// promptly. Best-effort: a dropped connection clears the role anyway.\n     │...\n 1679│\n*1680│    /// Generation-validated release (ADR-0038 Amendment fix 6): the broker\n 1681│    /// clears the controller only if its generation still matches `gen` — a\n 1682│    /// STALE worker's late detach can never clear a replacement controller.\n 1683│    // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n     │...\n 1919│        let brain_handle =\n*1920│            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect(\"brain cold_start\"));\n 1921│\n 1922│        let mut server = listener.accept().expect(\"accept the brain\");\n 1923│        crate::transport::recv_hello(&mut server, Role::Brain).expect(\"stub recv_hello\");\n     │...\n 1931│        let (feed, frames) = sync_channel::<Envelope>(64);\n*1932│        let server_thread = thread::spawn(move || {\n 1933│            while let Ok(env) = frames.recv() {\n 1934│                if write_frame(&mut send, &env).is_err() {\n 1935│                    break;"},"isError":false,"timestamp":1784271515096}}
{"type":"message","id":"c71318ec","parentId":"b8cc68f9","timestamp":"2026-07-17T06:58:45.421Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning line-exact dispatch loop review**"},{"type":"toolCall","id":"call_QGh15lg2yzX2CrCkjpwBFfLx|fc_089095e18118ee7d016a59d2a54fec8190aaccda452ace5347","name":"read","arguments":{"i":"Reading dispatcher claim loop","path":"crates/spt-daemon/src/dispatch.rs","selector":"290-560"}},{"type":"toolCall","id":"call_jAvTsucBRr2Jj5GRYJjoOQYa|fc_089095e18118ee7d016a59d2a54ff881908c0251a05d804062","name":"read","arguments":{"i":"Reading dispatcher worker path","path":"crates/spt-daemon/src/dispatch.rs","selector":"560-900"}},{"type":"toolCall","id":"call_8xCrhJfQtJ65faZXRSBeDrBH|fc_089095e18118ee7d016a59d2a54ffc8190a0fcec89b0ed4a04","name":"read","arguments":{"i":"Reading stream log core","path":"crates/spt-daemon/src/nethost.rs","selector":"100-430"}},{"type":"toolCall","id":"call_EupbMqvbajeeWBMYg1Dc2Gfi|fc_089095e18118ee7d016a59d2a5500481908b10794f8a8546ce","name":"read","arguments":{"i":"Reading stream subscriber methods","path":"crates/spt-daemon/src/nethost.rs","selector":"430-760"}},{"type":"toolCall","id":"call_ZFBhEqUPzP9gdRrfdyHUmgFc|fc_089095e18118ee7d016a59d2a550088190ad4e3c3b0b5c8cb4","name":"read","arguments":{"i":"Reading lifecycle broker commands","path":"crates/spt-daemon/src/broker.rs","selector":"4000-4320"}},{"type":"toolCall","id":"call_kDUeDBlpwoj94L7gH34YrnCK|fc_089095e18118ee7d016a59d2a5500c8190b4cc00bd6689a682","name":"read","arguments":{"i":"Reading redispatch remaining tests","path":"crates/spt-daemon/tests/redispatch.rs","selector":"300-621"}},{"type":"toolCall","id":"call_7FH8dJ1IO3b43nSR8JhtSfJs|fc_089095e18118ee7d016a59d2a550148190b4fe25ba56552bea","name":"read","arguments":{"i":"Reading stall test cases","path":"crates/spt-daemon/tests/redispatch_stall.rs","selector":"300-799"}},{"type":"toolCall","id":"call_heesQYyVQVwgtPCAFMc52So9|fc_089095e18118ee7d016a59d2a550188190baf0005c2f161e18","name":"read","arguments":{"i":"Reading release notes details","path":"CHANGELOG.md","selector":"1-70"}},{"type":"toolCall","id":"call_kok0SQv8Xlnr9diJKDqKhx5C|fc_089095e18118ee7d016a59d2a5501c819089b547962ecaa53b","name":"read","arguments":{"i":"Reading triage probes","path":"docs/REDISPATCH-STALL-TRIAGE.md","selector":"1-150"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":29818,"output":368,"cacheRead":39424,"cacheWrite":0,"totalTokens":69610,"reasoningTokens":27,"cost":{"input":0.14909,"output":0.01104,"cacheRead":0.019712,"cacheWrite":0,"total":0.179842}},"stopReason":"toolUse","timestamp":1784271515142,"responseId":"resp_089095e18118ee7d016a59d29ba3f081908b9ca63b3ab750a4","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d29eaadc8190957d4f00cb5f23ec","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdKl0A8brCgF__boAFWlYECGXOgyiGAVmfSap3UJI_TU-itbYi0Rr2Z3n4ChM134Lc7czTRP0p4nTIfa55iZs3hxG3YJKDp7P8aFxxjmy3W30Ov5UxLPot5uGQ0ZDYpt7K1ELQ_GyIVMtejsnIsXQ5Jwp62q-pVwjxTm84AS5h5RmN7hh0o754CwViqTv-E2XbjMRK_4fSIE4UWg03UlQLGPN1jercCbB8k6m8FGrMNAsm7kDNHATjZZcCOjEZ7ae2hcFHyjlhF24Fe2fz6TXUA18zs-upTf713wrqKpb4A7fdPnFAsTLDJuenYOlM-mrws4EDUPmVUIjkDhspfcScAGIIOAFnmSAePFaimpFhxinhjhcroxT03VUi0bxs59pI7ZKjmoJP_pQ61X2m9XhelSAldyepN2R0d8vxslT5pVdTp4_Az9SFRe7nQ-7gKHsBOLmx6eIBTqrbOIZsD2s3-gPTuaTwcdjtbyxywul5muhqx_CX3nFUPtvMM52jqDOjQ55B9udjPrlxqinlHTG95cL6ZxdKK7GiMbAkr3au7S9LmtYrkb50h5PNbstMoQKBeVGVhwA6q3O4Q0ZQ9ocZa3ACoX8o-OB6_STEwAEitAmRKD-yiIrFSuQRSLMG3gqWIjH4WhDYoFkV8pXERkEnNg6gKkEYVMJ44GDuNL_HU-xZr0tYqac-fl77CQMU5HBRLe-GCfd2PmU0DuGpDMsAzbrdTkZnXi1uwiJkyY0p3fHzddEVxELIP27TL0yJdcgx_runtvPbPRL10WuUqrow7-3RSVw7VA2PIVq4e_LA_3M9jo4hGGwTe3uLLmJGynjC7-euVS4txH_mNU1mbeia9Y-hNtCEY4eCnWPbjgmCOOUaYbzduL38hFEz-_KDrKice6xVGJ_ukTotEjhr_q39hTC5FFpj1abIbQ5T9vyKVArjt4l3KoTpKmMhX1GQvGs4OwfIrLvVxIymzC08RgJzoGzWjpGUXsxr3mfIrWPpq5UTCRykQ2-HkH9ZOVyzZVcYvF71uxtBbLI43nPPy4vCd1wg==","summary":[{"type":"summary_text","text":"**Planning line-exact dispatch loop review**"}]},{"id":"fc_089095e18118ee7d016a59d2a54fec8190aaccda452ace5347","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatcher claim loop\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"290-560\"}","call_id":"call_QGh15lg2yzX2CrCkjpwBFfLx","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a54ff881908c0251a05d804062","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatcher worker path\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"560-900\"}","call_id":"call_jAvTsucBRr2Jj5GRYJjoOQYa","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a54ffc8190a0fcec89b0ed4a04","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream log core\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"100-430\"}","call_id":"call_8xCrhJfQtJ65faZXRSBeDrBH","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a5500481908b10794f8a8546ce","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream subscriber methods\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"430-760\"}","call_id":"call_EupbMqvbajeeWBMYg1Dc2Gfi","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a550088190ad4e3c3b0b5c8cb4","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading lifecycle broker commands\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"4000-4320\"}","call_id":"call_ZFBhEqUPzP9gdRrfdyHUmgFc","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a5500c8190b4cc00bd6689a682","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch remaining tests\",\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"selector\":\"300-621\"}","call_id":"call_kDUeDBlpwoj94L7gH34YrnCK","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a550148190b4fe25ba56552bea","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stall test cases\",\"path\":\"crates/spt-daemon/tests/redispatch_stall.rs\",\"selector\":\"300-799\"}","call_id":"call_7FH8dJ1IO3b43nSR8JhtSfJs","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a550188190baf0005c2f161e18","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release notes details\",\"path\":\"CHANGELOG.md\",\"selector\":\"1-70\"}","call_id":"call_heesQYyVQVwgtPCAFMc52So9","name":"read"},{"id":"fc_089095e18118ee7d016a59d2a5501c819089b547962ecaa53b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading triage probes\",\"path\":\"docs/REDISPATCH-STALL-TRIAGE.md\",\"selector\":\"1-150\"}","call_id":"call_kok0SQv8Xlnr9diJKDqKhx5C","name":"read"}]},"duration":10278.774899989367,"ttft":5781.6052999943495,"contextSnapshot":{"promptTokens":69242,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_QGh15lg2yzX2CrCkjpwBFfLx|fc_089095e18118ee7d016a59d2a54fec8190aaccda452ace5347","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatcher claim loop"},"id":"c66013f8","parentId":"c71318ec","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_jAvTsucBRr2Jj5GRYJjoOQYa|fc_089095e18118ee7d016a59d2a54ff881908c0251a05d804062","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatcher worker path"},"id":"859aa337","parentId":"c66013f8","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_8xCrhJfQtJ65faZXRSBeDrBH|fc_089095e18118ee7d016a59d2a54ffc8190a0fcec89b0ed4a04","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream log core"},"id":"4e712932","parentId":"859aa337","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_EupbMqvbajeeWBMYg1Dc2Gfi|fc_089095e18118ee7d016a59d2a5500481908b10794f8a8546ce","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream subscriber methods"},"id":"381923ec","parentId":"4e712932","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ZFBhEqUPzP9gdRrfdyHUmgFc|fc_089095e18118ee7d016a59d2a550088190ad4e3c3b0b5c8cb4","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading lifecycle broker commands"},"id":"6ed95e94","parentId":"381923ec","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_kDUeDBlpwoj94L7gH34YrnCK|fc_089095e18118ee7d016a59d2a5500c8190b4cc00bd6689a682","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs"},"intent":"Reading redispatch remaining tests"},"id":"b37906e8","parentId":"6ed95e94","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_7FH8dJ1IO3b43nSR8JhtSfJs|fc_089095e18118ee7d016a59d2a550148190b4fe25ba56552bea","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"crates/spt-daemon/tests/redispatch_stall.rs"},"intent":"Reading stall test cases"},"id":"756ba854","parentId":"b37906e8","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_heesQYyVQVwgtPCAFMc52So9|fc_089095e18118ee7d016a59d2a550188190baf0005c2f161e18","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"CHANGELOG.md"},"intent":"Reading release notes details"},"id":"8cf5263c","parentId":"756ba854","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_kok0SQv8Xlnr9diJKDqKhx5C|fc_089095e18118ee7d016a59d2a5501c819089b547962ecaa53b","toolName":"read","startedAt":"2026-07-17T06:58:45.422Z","args":{"path":"docs/REDISPATCH-STALL-TRIAGE.md"},"intent":"Reading triage probes"},"id":"8b689faa","parentId":"8cf5263c","timestamp":"2026-07-17T06:58:45.422Z"}
{"type":"message","id":"d1ecd93c","parentId":"8b689faa","timestamp":"2026-07-17T06:58:45.427Z","message":{"role":"toolResult","toolCallId":"call_kok0SQv8Xlnr9diJKDqKhx5C|fc_089095e18118ee7d016a59d2a5501c819089b547962ecaa53b","toolName":"read","content":[{"type":"text","text":"# REDISPATCH-STALL — wave triage (doyle, 2026-07-16)\n\n<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n\n**Wave:** stop the v0.34 field stalls — REDISPATCH-TRUTH W1's claim retry and\nthe broad `Err(_)` opener fallback repeatedly drive `StreamLog::attach`'s\ndiscard-errors replay-under-mutex into serial 15s subscriber-poison windows\nthat compose into 17–62s PTY/RC freezes. Counter-63 fast-follow after\nMESH-RECOVERY's counter 62.\n\n**Canonical RCA:** hertz 2026-07-16, doyle seam-verified the same day; repo\nsummary `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`.\n**Decision record:** ADR-0038 **Amendment (2026-07-16)**.\n**Hazard:** KNOWN-HAZARDS 7.43.\n**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n17–62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all\n15,000–15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach\nrecords/generation; per-stream, not broker-wide (unrelated conns progressed\nmid-poison — broker-wide theory falsified).\n\n## Seam census (verified current, main @7f5ead8)\n\n| Seam | Location | Defect |\n|---|---|---|\n| Broad opener fallback | `spt-daemon/src/dispatch.rs:414` (`first_line`, `Err(_) => {}` arm) | comment intends old-broker-only; catches transport timeout/EOF/poison → manufactures a throwaway peek subscriber per retry |\n| Replay-under-mutex, errors discarded | `spt-daemon/src/nethost.rs:322-346` (`StreamLog::attach`) | installs subscriber, replays entire retained ring under the per-stream `Mutex`; `let _ = sub.write(&frame)` continues past failure; poisoned subscriber stays installed (same shape: `push` :288, `finish` :316; `PresenceLog::attach` :451-465) |\n| Retry × fallback composition | `dispatch.rs` claim retry (500ms/1s, ×3) + the fallback above | each retry re-attaches a fresh peek subscriber → serial 15s poison windows under the lock |\n| Unbounded forwarding legs | attach worker serve/wire pairing (nethost/serve_attach) | a poisoned brain subscriber leaves its paired serve/wire worker running |\n| Stale-worker detach | serve/detach path (`detach_if` by ptr identity only) | no ownership/generation validation before attach/detach — a stale worker can clear a replacement controller (the UNFINISHED-stale steal shape, kin emphasys C2 raw-close no-FIN viewports) |\n| Dispatcher hygiene | `dispatch.rs` per-stream `thread::spawn`, claims/sessions locks near I/O | unbounded worker fan-out on cold enumeration; lock-across-I/O kin KH 7.12 |\n\n## Fix set (P0, accepted as filed — hertz design, doyle-ruled)\n\n1. **Fallback narrowed** (`REQ-DISPATCH-FALLBACK-CIRCUIT`): ring-peek fallback\n   ONLY on the explicit UnsupportedVerb/old-broker answer; transport\n   timeout/EOF/poison = `Failed` → bounded requeue, never a second replay\n   subscriber.\n2. **Replay halts + failed subscriber removed**\n   (`REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE`): replay stops at the FIRST write\n   failure and the failed subscriber is REMOVED — attach replay, append, and\n   finish, all three sites.\n3. **Writes OFF the StreamLog mutex** (same REQ): bounded per-subscriber\n   writer queue — enqueue-under-lock, I/O outside; overflow = detach that\n   subscriber + resume-from-cursor. Never a producer stall.\n4. **Bounded, cancelable attach forwarding** (same REQ): a poisoned brain\n   subscriber cancels its paired serve/wire worker. COORDINATE at build with\n   DAEMON-LIFECYCLE C2 leases — same neighborhood, no double-build.\n5. **Classified retries + circuit breaker**\n   (`REQ-DISPATCH-FALLBACK-CIRCUIT`): pre-setup transient may retry;\n   deadline-poisoned replay = circuit-broken global backoff; a replacement\n   subscriber is never installed until the prior one is fully gone. NOT a\n   revert to v0.33 abandonment.\n6. **Terminal-exclusion pre-serve + ownership/generation validation**\n   (`REQ-SERVE-OWNERSHIP-GENERATION`): a stale worker never detaches a\n   replacement controller; covers the UNFINISHED-stale-row steal shape D1/D1b\n   cannot see.\n7. **Bounded worker pool + batched cold enumeration**\n   (`REQ-DISPATCH-HYGIENE-TELEMETRY`): no claim locks held during I/O.\n8. **Sessions-lock discipline** (same REQ): clone-then-drop the sessions lock\n   before `detach_if`/`info.json` I/O (KH 7.12 kin).\n9. **Keyed stage telemetry** (same REQ): per\n   gen/stream/family/endpoint/attempt/conn;\n   replay/poison/cancel/worker-outcome events + gauges + PTY high-water/RC\n   cursor.\n\n## Test matrix (seven legs, production-path)\n\n1. **T1 poisoned-replay halt:** wedge a subscriber conn; `StreamLog::attach`\n   replay halts at the first failed write, subscriber removed, later\n   append/finish never re-feed it (unit at the StreamLog seam + poison int).\n2. **T2 fallback narrowing:** opener query answering transport\n   timeout/EOF/poison → `Failed`/requeue with NO peek subscriber created;\n   only the explicit unsupported-verb answer falls back (unit + int).\n3. **T3 writer-queue off-lock:** producer appends never block on subscriber\n   I/O past the enqueue; overflow detaches + resumes-from-cursor gapless\n   (unit; int: wedged subscriber, producer latency stays flat).\n4. **T4 forwarding cancel:** brain-subscriber poison cancels the paired\n   serve/wire worker — no orphan forwarding leg survives (int).\n5. **T5 circuit breaker:** deadline-poisoned replay trips the global backoff;\n   no reinstall until the prior subscriber is fully gone; the stream RECOVERS\n   after the breaker window (the not-abandonment discriminator) (unit + int).\n6. **T6 stale-ownership steal:** UNFINISHED-stale attach row + live current\n   controller; restart the dispatcher; the stale worker neither takes nor\n   clears the replacement controller (ownership/generation validated) (int).\n7. **T7 mixed-image N-1:** new brain vs old broker across the fallback\n   window with REAL streams carrying traffic — handshake-only N-1 is\n   INSUFFICIENT (doyle rider); prove fallback fires only there and serves\n   correctly (int).\n\n**Binding on every leg:** redispatch D1/D1b stay green (the retire/steal\nregressions are substrate, not victims).\n\n**Missing-coverage census (fair, rides the wave):** D3 never poisons an\ninstalled subscriber; `conn_blackhole` exercises conns, not StreamLog\nsubscribers; `daemon_refresh` doesn't cover cross-node attach continuity —\nthe matrix above closes these, don't re-count them as new scope.\n\n## Gate rulings (pre-made, binding)\n\n1. **Production-path only.** Poison/replay regressions ride the real\n   `run_dispatch_loop` + `StreamLog` + serve_attach seams; hand-fed\n   subscribers prove nothing about the composition.\n2. **D1/D1b green every leg** — no exceptions, any red = stop.\n3. **No deadline widening.** The 15s bound is correct; the fix removes the\n   serial composition, never hides it behind a bigger number.\n4. **Circuit breaker ≠ abandonment.** T5 must show recovery after the breaker\n   window; a stream that never recovers is a regression to v0.33 burn-claim.\n5. **Replay discipline is all-sites.** attach + append + finish (and\n   `PresenceLog` mirrors) — a fix that patches attach only fails the gate.\n6. **T7 uses real streams.** Handshake-only mixed-image is insufficient.\n7. **New daemon-tree e2es enter the HEAVY list at birth** (3 identical\n   strings: ci.yml Phase A + Phase B + nextest.toml).\n8. **Coordinate fix 4 with DAEMON-LIFECYCLE C2** — if leases land here,\n   the C2 build consumes them; no parallel second mechanism.\n9. **Gate mechanics:** isolated worktree of the gate sha + fresh\n   `CARGO_TARGET_DIR` + nextest + workspace clippy + `traceable-reqs check`;\n   shared-seam sweep (StreamLog/dispatch touch sync/update/attach workers —\n   grep + run every seam-sibling suite); both CI runners green.\n\n## Explicitly out of scope (do not build)\n\n- `update --restart`'s 30s rc reconnect loop — distinct and bounded, not this.\n- DAEMON-LIFECYCLE C1/C3 (hybrid-online classification, poll-vs-reap race) —\n  own cluster; only the C2-adjacent bounded-forwarding seam coordinates here.\n- Unbounded Whole-brain read reshaping beyond what fixes 3/4 require —\n  observability lands via fix 9; deeper read-path work is its own seed.\n\n## Field discriminators (hertz, after release)\n\n1. **Poison-window census:** the 15,000–15,154 ms `CONN_WRITE_POISONED`\n   fingerprint disappears from live logs; stream-sub-attach per generation\n   drops from ~4361 to O(active streams).\n2. **Stall census:** no 17–62s DISPATCH tails across a daemon refresh + one\n   week of live traffic.\n3. **Next steal catch (if any):** capture the stolen row's `finished` +\n   `retired` flags — the discriminating observable between the finished\n   (D1/D1b-covered) and unfinished-stale (fix 6) shapes.\n\n## Lane\n\ndoyle triaged (this doc) → todlando builds (branch\n`build/redispatch-stall-w1`, all five REQs, flip `required_stages` at build\nstart per [[traceable-per-wave-activation]]) → doyle gates (rulings above) →\ndeployah releases (counter 63 fast-follow, bump-in-PR, counter from published\nmetadata) → hertz field-verify (discriminators above; they own the incident\nlogs).\n"}],"details":{"displayContent":{"text":"# REDISPATCH-STALL — wave triage (doyle, 2026-07-16)\n\n<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->\n\n**Wave:** stop the v0.34 field stalls — REDISPATCH-TRUTH W1's claim retry and\nthe broad `Err(_)` opener fallback repeatedly drive `StreamLog::attach`'s\ndiscard-errors replay-under-mutex into serial 15s subscriber-poison windows\nthat compose into 17–62s PTY/RC freezes. Counter-63 fast-follow after\nMESH-RECOVERY's counter 62.\n\n**Canonical RCA:** hertz 2026-07-16, doyle seam-verified the same day; repo\nsummary `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`.\n**Decision record:** ADR-0038 **Amendment (2026-07-16)**.\n**Hazard:** KNOWN-HAZARDS 7.43.\n**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n17–62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all\n15,000–15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach\nrecords/generation; per-stream, not broker-wide (unrelated conns progressed\nmid-poison — broker-wide theory falsified).\n\n## Seam census (verified current, main @7f5ead8)\n\n| Seam | Location | Defect |\n|---|---|---|\n| Broad opener fallback | `spt-daemon/src/dispatch.rs:414` (`first_line`, `Err(_) => {}` arm) | comment intends old-broker-only; catches transport timeout/EOF/poison → manufactures a throwaway peek subscriber per retry |\n| Replay-under-mutex, errors discarded | `spt-daemon/src/nethost.rs:322-346` (`StreamLog::attach`) | installs subscriber, replays entire retained ring under the per-stream `Mutex`; `let _ = sub.write(&frame)` continues past failure; poisoned subscriber stays installed (same shape: `push` :288, `finish` :316; `PresenceLog::attach` :451-465) |\n| Retry × fallback composition | `dispatch.rs` claim retry (500ms/1s, ×3) + the fallback above | each retry re-attaches a fresh peek subscriber → serial 15s poison windows under the lock |\n| Unbounded forwarding legs | attach worker serve/wire pairing (nethost/serve_attach) | a poisoned brain subscriber leaves its paired serve/wire worker running |\n| Stale-worker detach | serve/detach path (`detach_if` by ptr identity only) | no ownership/generation validation before attach/detach — a stale worker can clear a replacement controller (the UNFINISHED-stale steal shape, kin emphasys C2 raw-close no-FIN viewports) |\n| Dispatcher hygiene | `dispatch.rs` per-stream `thread::spawn`, claims/sessions locks near I/O | unbounded worker fan-out on cold enumeration; lock-across-I/O kin KH 7.12 |\n\n## Fix set (P0, accepted as filed — hertz design, doyle-ruled)\n\n1. **Fallback narrowed** (`REQ-DISPATCH-FALLBACK-CIRCUIT`): ring-peek fallback\n   ONLY on the explicit UnsupportedVerb/old-broker answer; transport\n   timeout/EOF/poison = `Failed` → bounded requeue, never a second replay\n   subscriber.\n2. **Replay halts + failed subscriber removed**\n   (`REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE`): replay stops at the FIRST write\n   failure and the failed subscriber is REMOVED — attach replay, append, and\n   finish, all three sites.\n3. **Writes OFF the StreamLog mutex** (same REQ): bounded per-subscriber\n   writer queue — enqueue-under-lock, I/O outside; overflow = detach that\n   subscriber + resume-from-cursor. Never a producer stall.\n4. **Bounded, cancelable attach forwarding** (same REQ): a poisoned brain\n   subscriber cancels its paired serve/wire worker. COORDINATE at build with\n   DAEMON-LIFECYCLE C2 leases — same neighborhood, no double-build.\n5. **Classified retries + circuit breaker**\n   (`REQ-DISPATCH-FALLBACK-CIRCUIT`): pre-setup transient may retry;\n   deadline-poisoned replay = circuit-broken global backoff; a replacement\n   subscriber is never installed until the prior one is fully gone. NOT a\n   revert to v0.33 abandonment.\n6. **Terminal-exclusion pre-serve + ownership/generation validation**\n   (`REQ-SERVE-OWNERSHIP-GENERATION`): a stale worker never detaches a\n   replacement controller; covers the UNFINISHED-stale-row steal shape D1/D1b\n   cannot see.\n7. **Bounded worker pool + batched cold enumeration**\n   (`REQ-DISPATCH-HYGIENE-TELEMETRY`): no claim locks held during I/O.\n8. **Sessions-lock discipline** (same REQ): clone-then-drop the sessions lock\n   before `detach_if`/`info.json` I/O (KH 7.12 kin).\n9. **Keyed stage telemetry** (same REQ): per\n   gen/stream/family/endpoint/attempt/conn;\n   replay/poison/cancel/worker-outcome events + gauges + PTY high-water/RC\n   cursor.\n\n## Test matrix (seven legs, production-path)\n\n1. **T1 poisoned-replay halt:** wedge a subscriber conn; `StreamLog::attach`\n   replay halts at the first failed write, subscriber removed, later\n   append/finish never re-feed it (unit at the StreamLog seam + poison int).\n2. **T2 fallback narrowing:** opener query answering transport\n   timeout/EOF/poison → `Failed`/requeue with NO peek subscriber created;\n   only the explicit unsupported-verb answer falls back (unit + int).\n3. **T3 writer-queue off-lock:** producer appends never block on subscriber\n   I/O past the enqueue; overflow detaches + resumes-from-cursor gapless\n   (unit; int: wedged subscriber, producer latency stays flat).\n4. **T4 forwarding cancel:** brain-subscriber poison cancels the paired\n   serve/wire worker — no orphan forwarding leg survives (int).\n5. **T5 circuit breaker:** deadline-poisoned replay trips the global backoff;\n   no reinstall until the prior subscriber is fully gone; the stream RECOVERS\n   after the breaker window (the not-abandonment discriminator) (unit + int).\n6. **T6 stale-ownership steal:** UNFINISHED-stale attach row + live current\n   controller; restart the dispatcher; the stale worker neither takes nor\n   clears the replacement controller (ownership/generation validated) (int).\n7. **T7 mixed-image N-1:** new brain vs old broker across the fallback\n   window with REAL streams carrying traffic — handshake-only N-1 is\n   INSUFFICIENT (doyle rider); prove fallback fires only there and serves\n   correctly (int).\n\n**Binding on every leg:** redispatch D1/D1b stay green (the retire/steal\nregressions are substrate, not victims).\n\n**Missing-coverage census (fair, rides the wave):** D3 never poisons an\ninstalled subscriber; `conn_blackhole` exercises conns, not StreamLog\nsubscribers; `daemon_refresh` doesn't cover cross-node attach continuity —\nthe matrix above closes these, don't re-count them as new scope.\n\n## Gate rulings (pre-made, binding)\n\n1. **Production-path only.** Poison/replay regressions ride the real\n   `run_dispatch_loop` + `StreamLog` + serve_attach seams; hand-fed\n   subscribers prove nothing about the composition.\n2. **D1/D1b green every leg** — no exceptions, any red = stop.\n3. **No deadline widening.** The 15s bound is correct; the fix removes the\n   serial composition, never hides it behind a bigger number.\n4. **Circuit breaker ≠ abandonment.** T5 must show recovery after the breaker\n   window; a stream that never recovers is a regression to v0.33 burn-claim.\n5. **Replay discipline is all-sites.** attach + append + finish (and\n   `PresenceLog` mirrors) — a fix that patches attach only fails the gate.\n6. **T7 uses real streams.** Handshake-only mixed-image is insufficient.\n7. **New daemon-tree e2es enter the HEAVY list at birth** (3 identical\n   strings: ci.yml Phase A + Phase B + nextest.toml).\n8. **Coordinate fix 4 with DAEMON-LIFECYCLE C2** — if leases land here,\n   the C2 build consumes them; no parallel second mechanism.\n9. **Gate mechanics:** isolated worktree of the gate sha + fresh\n   `CARGO_TARGET_DIR` + nextest + workspace clippy + `traceable-reqs check`;\n   shared-seam sweep (StreamLog/dispatch touch sync/update/attach workers —\n   grep + run every seam-sibling suite); both CI runners green.\n\n## Explicitly out of scope (do not build)\n\n- `update --restart`'s 30s rc reconnect loop — distinct and bounded, not this.\n- DAEMON-LIFECYCLE C1/C3 (hybrid-online classification, poll-vs-reap race) —\n  own cluster; only the C2-adjacent bounded-forwarding seam coordinates here.\n- Unbounded Whole-brain read reshaping beyond what fixes 3/4 require —\n  observability lands via fix 9; deeper read-path work is its own seed.\n\n## Field discriminators (hertz, after release)\n\n1. **Poison-window census:** the 15,000–15,154 ms `CONN_WRITE_POISONED`\n   fingerprint disappears from live logs; stream-sub-attach per generation\n   drops from ~4361 to O(active streams).\n2. **Stall census:** no 17–62s DISPATCH tails across a daemon refresh + one\n   week of live traffic.\n3. **Next steal catch (if any):** capture the stolen row's `finished` +\n   `retired` flags — the discriminating observable between the finished\n   (D1/D1b-covered) and unfinished-stale (fix 6) shapes.\n\n## Lane\n\ndoyle triaged (this doc) → todlando builds (branch\n`build/redispatch-stall-w1`, all five REQs, flip `required_stages` at build\nstart per [[traceable-per-wave-activation]]) → doyle gates (rulings above) →\ndeployah releases (counter 63 fast-follow, bump-in-PR, counter from published\nmetadata) → hertz field-verify (discriminators above; they own the incident\nlogs).\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\REDISPATCH-STALL-TRIAGE.md"}}},"isError":false,"timestamp":1784271525427}}
{"type":"message","id":"33a45401","parentId":"d1ecd93c","timestamp":"2026-07-17T06:58:45.435Z","message":{"role":"toolResult","toolCallId":"call_kDUeDBlpwoj94L7gH34YrnCK|fc_089095e18118ee7d016a59d2a5500c8190b4cc00bd6689a682","toolName":"read","content":[{"type":"text","text":"fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n…\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n// finished-UNRETIRED orderings the Served-report path can never see: the\n// pre-existing backlog and the brain-down-detach window. s1 is attached and\n// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n// operator detaches (the row finishes with no dispatcher alive; the conn\n// stays open, so the conn-close sweep does not fire either). gen2 starts and\n// serves a fresh current attach: the finished s1 row must be retired\n// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\n// made this replay PERFECTLY: seeded attach_as steals the current\n// controller, replayed input re-types, replayed EOF clears). The current\n// viewport keeps flowing across several polls, sees no EOF, and gen2's log\n// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\n// ever being served (retire-on-sight drains the backlog).\n// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n#[test]\nfn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd1b-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // s1: attached + driven under gen1 (real serve, opener pinned, input in\n    // the ring — everything a replay needs to steal).\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n\n    // The brain-down-detach window: kill gen1 FIRST (no worker can report\n    // Served), THEN detach — the row finishes unretired on a live conn.\n    gen1.kill();\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1 while brain down\");\n    {\n        let end = Instant::now() + Duration::from_secs(10);\n        loop {\n            let finished = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .any(|s| s.stream_id == s1_a && s.finished);\n            if finished {\n                break;\n            }\n            assert!(Instant::now() < end, \"s1 never read as finished on A\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    }\n\n    // gen2 + a fresh CURRENT attach. Pre-fix, gen2's replay of s1 steals and\n    // clears this controller within its first poll cycles.\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n\n    // Several seconds of live driving across many 50ms dispatcher polls — the\n    // window in which a stale replay would land its steal.\n    for round in 0..3u32 {\n        let marker = format!(\"CUR-{round}\\r\");\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 11 + u64::from(round))\n            .expect(\"input s2\");\n        let eof = render_until(\n            &mut operator,\n            s2,\n            &mut dec2,\n            &mut cur2,\n            &mut view2,\n            format!(\"CUR-{round}\").as_bytes(),\n            Duration::from_secs(20),\n        );\n        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\n        thread::sleep(Duration::from_millis(700));\n    }\n\n    // Retire-on-sight drained the finished row WITHOUT serving it.\n    {\n        let end = Instant::now() + Duration::from_secs(15);\n        while peer_stream_ids(&mut probe).contains(&s1_a) {\n            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    }\n    let gen2_log = gen2.stderr_log();\n    assert!(\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n        \"gen2 replayed the finished-unretired stream {s1_a}: {gen2_log}\"\n    );\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n// discriminator (gate round 1 counter-check): a request/reply stream reads\n// `finished=true` the moment its requester FINs with the request — sync's\n// `request_sync` does exactly that — so a finished row must NOT be blanket-\n// terminal. The request here arrives while NO dispatcher generation is\n// alive (the refresh window), sits finished-unserved, and the next\n// generation must still SERVE it: the pull completes with a reply. A bare\n// `s.finished` claim filter fails this test (context sync dead across every\n// brain cycle).\n#[test]\nfn finished_pending_sync_request_still_serves_across_generation_start() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    // NO generation alive: the pull's Request+FIN lands and sits finished.\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\n        .expect(\"requester context store\");\n    let scratch = dir.path().join(\"scratch-b\");\n\n    let puller = thread::spawn(move || {\n        spt_daemon::sync::request_sync(\n            &mut operator,\n            conn.conn_id,\n            &[\"p-nothing\".to_string()],\n            MintedOp::new(Minter::Cli, 2),\n            &cs,\n            &scratch,\n        )\n    });\n\n    // The row is on A, finished (the requester FIN'd with its Request),\n    // and UNSERVED — exactly the shape a blanket finished-filter kills.\n    let s_a = {\n        let end = Instant::now() + Duration::from_secs(10);\n        loop {\n            if let Some(s) = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .find(|s| !s.initiated_locally && s.finished)\n            {\n                break s.stream_id;\n            }\n            assert!(Instant::now() < end, \"the finished pending pull never appeared on A\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    };\n\n    // The generation starts AFTER the request finished — and must serve it.\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n    let report = puller\n        .join()\n        .expect(\"puller thread\")\n        .expect(\"the finished pending pull must be SERVED, not skipped as terminal\");\n    assert!(\n        report.applied.is_empty(),\n        \"an unknown ref applies nothing (the completed reply itself is the assertion): {report:?}\"\n    );\n    let _ = s_a;\n\n    gen.kill();\n}\n\n// [int->REQ-STREAM-OPENER-DURABLE] D2 — the abandoned-active-stream leg: an\n// ACTIVE attach outlives its opener in the bounded ring (test-rig ring cap\n// via SPT_STREAM_RING_CHUNKS — production sizing untouched), the dispatcher\n// generation restarts, and DURABLE classification (the broker-pinned opener,\n// never ring seq 0) resumes the SAME operator stream: post-restart input\n// renders. Pre-fix this classified Unknown/Failed with the claim burned —\n// the stream was permanently abandoned.\n#[test]\nfn opener_evicted_active_stream_resumes_after_dispatcher_restart() {\n    init_home();\n    // Shrink the ring BEFORE the broker starts (read once at NetShared::new).\n    // nextest = one process per test: no cross-test env bleed.\n    std::env::set_var(\"SPT_STREAM_RING_CHUNKS\", \"8\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd2-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\n    send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");\n    let mut dec = AttachDecoder::new();\n    let (mut cur, mut view) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n\n    // Roll the opener out of the (8-chunk) ring: enough spaced input records\n    // that the floor advances past seq 0.\n    for i in 0..40u64 {\n        send_attach_input(&mut operator, s1, format!(\"f{i}\\r\").as_bytes(), 20 + i)\n            .expect(\"flood input\");\n        thread::sleep(Duration::from_millis(10));\n    }\n    let end = Instant::now() + Duration::from_secs(20);\n    loop {\n        let rolled = probe\n            .net_streams()\n            .expect(\"net-streams\")\n            .streams\n            .iter()\n            .any(|s| s.stream_id == s1_a && s.floor_seq > 0);\n        if rolled {\n            break;\n        }\n        assert!(Instant::now() < end, \"the ring never rolled past the opener\");\n        thread::sleep(Duration::from_millis(50));\n    }\n    // The durable fact is still there — this is what gen2 will classify from.\n    let pinned = probe\n        .net_stream_opener(s1_a)\n        .expect(\"opener query answers\");\n    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\n\n    // Restart the dispatcher generation; recovery must classify from the\n    // pinned opener (ring seq 0 is GONE) and resume THIS operator stream.\n    gen1.kill();\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n\n    send_attach_input(&mut operator, s1, b\"MARK-B\\r\", 100).expect(\"input post\");\n    let saw_eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-B\", Duration::from_secs(30));\n    assert!(!saw_eof, \"the resumed stream must not be torn down\");\n    let gen2_log = gen2.stderr_log();\n    assert!(\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:Unknown\")),\n        \"gen2 classified the rolled-opener stream Unknown — durable classification failed: {gen2_log}\"\n    );\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-DISPATCH-CLAIM-RETRY] D3 — one injected transient worker-start\n// failure (the SPT_DISPATCH_INJECT_TRANSIENT_ONCE test valve, armed on the\n// generation's env): the FIRST worker attempt fails before setup, the claim\n// requeues (bounded backoff), and the retry serves the stream — the operator\n// still gets the viewport, exactly one controller. Pre-fix the one-shot\n// claimed-set burn abandoned the stream permanently on that first failure.\n#[test]\nfn transient_worker_start_failure_recovers_via_claim_retry() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd3-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(\n        &name_a,\n        dir.path().join(\"gen1.log\"),\n        &[(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\", \"1\")],\n    );\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\n    send_attach_input(&mut operator, s1, b\"RETRY-MARK\\r\", 10).expect(\"input\");\n\n    // Through the injected failure + the claim retry: the marker renders.\n    let mut dec = AttachDecoder::new();\n    let (mut cur, mut view) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"RETRY-MARK\", Duration::from_secs(30));\n\n    // The injection actually fired (this is what distinguishes \"recovered\n    // through retry\" from \"nothing ever failed\").\n    let log = gen1.stderr_log();\n    assert!(\n        log.contains(\"injected transient worker-start failure\"),\n        \"the transient-failure valve never fired — the retry path was not exercised: {log}\"\n    );\n\n    gen1.kill();\n    let _ = probe.kill_session();\n}\n"}],"details":{"displayContent":{"text":"fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n…\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\r\n// finished-UNRETIRED orderings the Served-report path can never see: the\r\n// pre-existing backlog and the brain-down-detach window. s1 is attached and\r\n// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\r\n// operator detaches (the row finishes with no dispatcher alive; the conn\r\n// stays open, so the conn-close sweep does not fire either). gen2 starts and\r\n// serves a fresh current attach: the finished s1 row must be retired\r\n// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\r\n// made this replay PERFECTLY: seeded attach_as steals the current\r\n// controller, replayed input re-types, replayed EOF clears). The current\r\n// viewport keeps flowing across several polls, sees no EOF, and gen2's log\r\n// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\r\n// ever being served (retire-on-sight drains the backlog).\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE]\r\n#[test]\r\nfn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1b-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // s1: attached + driven under gen1 (real serve, opener pinned, input in\r\n    // the ring — everything a replay needs to steal).\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n\r\n    // The brain-down-detach window: kill gen1 FIRST (no worker can report\r\n    // Served), THEN detach — the row finishes unretired on a live conn.\r\n    gen1.kill();\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1 while brain down\");\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            let finished = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .any(|s| s.stream_id == s1_a && s.finished);\r\n            if finished {\r\n                break;\r\n            }\r\n            assert!(Instant::now() < end, \"s1 never read as finished on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n\r\n    // gen2 + a fresh CURRENT attach. Pre-fix, gen2's replay of s1 steals and\r\n    // clears this controller within its first poll cycles.\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n\r\n    // Several seconds of live driving across many 50ms dispatcher polls — the\r\n    // window in which a stale replay would land its steal.\r\n    for round in 0..3u32 {\r\n        let marker = format!(\"CUR-{round}\\r\");\r\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 11 + u64::from(round))\r\n            .expect(\"input s2\");\r\n        let eof = render_until(\r\n            &mut operator,\r\n            s2,\r\n            &mut dec2,\r\n            &mut cur2,\r\n            &mut view2,\r\n            format!(\"CUR-{round}\").as_bytes(),\r\n            Duration::from_secs(20),\r\n        );\r\n        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\r\n        thread::sleep(Duration::from_millis(700));\r\n    }\r\n\r\n    // Retire-on-sight drained the finished row WITHOUT serving it.\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(15);\r\n        while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"gen2 replayed the finished-unretired stream {s1_a}: {gen2_log}\"\r\n    );\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\r\n// discriminator (gate round 1 counter-check): a request/reply stream reads\r\n// `finished=true` the moment its requester FINs with the request — sync's\r\n// `request_sync` does exactly that — so a finished row must NOT be blanket-\r\n// terminal. The request here arrives while NO dispatcher generation is\r\n// alive (the refresh window), sits finished-unserved, and the next\r\n// generation must still SERVE it: the pull completes with a reply. A bare\r\n// `s.finished` claim filter fails this test (context sync dead across every\r\n// brain cycle).\r\n#[test]\r\nfn finished_pending_sync_request_still_serves_across_generation_start() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    // NO generation alive: the pull's Request+FIN lands and sits finished.\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\r\n        .expect(\"requester context store\");\r\n    let scratch = dir.path().join(\"scratch-b\");\r\n\r\n    let puller = thread::spawn(move || {\r\n        spt_daemon::sync::request_sync(\r\n            &mut operator,\r\n            conn.conn_id,\r\n            &[\"p-nothing\".to_string()],\r\n            MintedOp::new(Minter::Cli, 2),\r\n            &cs,\r\n            &scratch,\r\n        )\r\n    });\r\n\r\n    // The row is on A, finished (the requester FIN'd with its Request),\r\n    // and UNSERVED — exactly the shape a blanket finished-filter kills.\r\n    let s_a = {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            if let Some(s) = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .find(|s| !s.initiated_locally && s.finished)\r\n            {\r\n                break s.stream_id;\r\n            }\r\n            assert!(Instant::now() < end, \"the finished pending pull never appeared on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    };\r\n\r\n    // The generation starts AFTER the request finished — and must serve it.\r\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\r\n    let report = puller\r\n        .join()\r\n        .expect(\"puller thread\")\r\n        .expect(\"the finished pending pull must be SERVED, not skipped as terminal\");\r\n    assert!(\r\n        report.applied.is_empty(),\r\n        \"an unknown ref applies nothing (the completed reply itself is the assertion): {report:?}\"\r\n    );\r\n    let _ = s_a;\r\n\r\n    gen.kill();\r\n}\r\n\r\n// [int->REQ-STREAM-OPENER-DURABLE] D2 — the abandoned-active-stream leg: an\r\n// ACTIVE attach outlives its opener in the bounded ring (test-rig ring cap\r\n// via SPT_STREAM_RING_CHUNKS — production sizing untouched), the dispatcher\r\n// generation restarts, and DURABLE classification (the broker-pinned opener,\r\n// never ring seq 0) resumes the SAME operator stream: post-restart input\r\n// renders. Pre-fix this classified Unknown/Failed with the claim burned —\r\n// the stream was permanently abandoned.\r\n#[test]\r\nfn opener_evicted_active_stream_resumes_after_dispatcher_restart() {\r\n    init_home();\r\n    // Shrink the ring BEFORE the broker starts (read once at NetShared::new).\r\n    // nextest = one process per test: no cross-test env bleed.\r\n    std::env::set_var(\"SPT_STREAM_RING_CHUNKS\", \"8\");\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd2-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\r\n    send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");\r\n    let mut dec = AttachDecoder::new();\r\n    let (mut cur, mut view) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n\r\n    // Roll the opener out of the (8-chunk) ring: enough spaced input records\r\n    // that the floor advances past seq 0.\r\n    for i in 0..40u64 {\r\n        send_attach_input(&mut operator, s1, format!(\"f{i}\\r\").as_bytes(), 20 + i)\r\n            .expect(\"flood input\");\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    let end = Instant::now() + Duration::from_secs(20);\r\n    loop {\r\n        let rolled = probe\r\n            .net_streams()\r\n            .expect(\"net-streams\")\r\n            .streams\r\n            .iter()\r\n            .any(|s| s.stream_id == s1_a && s.floor_seq > 0);\r\n        if rolled {\r\n            break;\r\n        }\r\n        assert!(Instant::now() < end, \"the ring never rolled past the opener\");\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n    // The durable fact is still there — this is what gen2 will classify from.\r\n    let pinned = probe\r\n        .net_stream_opener(s1_a)\r\n        .expect(\"opener query answers\");\r\n    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\r\n\r\n    // Restart the dispatcher generation; recovery must classify from the\r\n    // pinned opener (ring seq 0 is GONE) and resume THIS operator stream.\r\n    gen1.kill();\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n\r\n    send_attach_input(&mut operator, s1, b\"MARK-B\\r\", 100).expect(\"input post\");\r\n    let saw_eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-B\", Duration::from_secs(30));\r\n    assert!(!saw_eof, \"the resumed stream must not be torn down\");\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:Unknown\")),\r\n        \"gen2 classified the rolled-opener stream Unknown — durable classification failed: {gen2_log}\"\r\n    );\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-DISPATCH-CLAIM-RETRY] D3 — one injected transient worker-start\r\n// failure (the SPT_DISPATCH_INJECT_TRANSIENT_ONCE test valve, armed on the\r\n// generation's env): the FIRST worker attempt fails before setup, the claim\r\n// requeues (bounded backoff), and the retry serves the stream — the operator\r\n// still gets the viewport, exactly one controller. Pre-fix the one-shot\r\n// claimed-set burn abandoned the stream permanently on that first failure.\r\n#[test]\r\nfn transient_worker_start_failure_recovers_via_claim_retry() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd3-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(\r\n        &name_a,\r\n        dir.path().join(\"gen1.log\"),\r\n        &[(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\", \"1\")],\r\n    );\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\r\n    send_attach_input(&mut operator, s1, b\"RETRY-MARK\\r\", 10).expect(\"input\");\r\n\r\n    // Through the injected failure + the claim retry: the marker renders.\r\n    let mut dec = AttachDecoder::new();\r\n    let (mut cur, mut view) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"RETRY-MARK\", Duration::from_secs(30));\r\n\r\n    // The injection actually fired (this is what distinguishes \"recovered\r\n    // through retry\" from \"nothing ever failed\").\r\n    let log = gen1.stderr_log();\r\n    assert!(\r\n        log.contains(\"injected transient worker-start failure\"),\r\n        \"the transient-failure valve never fired — the retry path was not exercised: {log}\"\r\n    );\r\n\r\n    gen1.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n","startLine":222,"lineNumbers":[222,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,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch.rs"}}},"isError":false,"timestamp":1784271525435}}
{"type":"message","id":"3a260d3c","parentId":"33a45401","timestamp":"2026-07-17T06:58:45.445Z","message":{"role":"toolResult","toolCallId":"call_7FH8dJ1IO3b43nSR8JhtSfJs|fc_089095e18118ee7d016a59d2a550148190b4fe25ba56552bea","toolName":"read","content":[{"type":"text","text":"fn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n…\n    'attach: loop {\n…\n        loop {\n…\n            match f.read_event() {\n…\n                Err(e) => panic!(\"recovery subscriber read failed: {e}\"),\n            }\n        }\n    }\n}\n\nconst FLOOD_CHUNK: usize = 32 * 1024;\nconst FLOOD_PRE: usize = 48; // chunks before the wedged subscriber attaches\nconst FLOOD_POST: usize = 48; // chunks appended WHILE the subscriber is wedged\n\n// [int->REQ-HAZARD-REDISPATCH-STALL]\n// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n// production broker subscribe path: a REAL subscriber conn that attaches with\n// a full ring and never reads — its seat's replay wedges on the named-pipe\n// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat\n// is removed, later appends never re-feed it, and the PRODUCER — B's ack'd\n// sends through A's read pump — stays flat throughout (pre-fix: the replay\n// ran under the stream mutex with discarded errors, one 400ms poison window\n// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh\n// subscriber replays everything gapless from the cursor — no abandonment.\n#[test]\nfn wedged_subscriber_never_stalls_the_producer_and_recovery_replays_gapless() {\n    init_home();\n    // Shrink the bounded write so the poison window is 400ms, not 15s — the\n    // knob grants wall-clock only, never relaxes an assertion (the\n    // conn_blackhole_lifecycle discipline). nextest: process-per-test.\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    let opener = b\"{\\\"hello\\\":1}\\n\";\n    let (mut operator, sid_b, sid_a) = open_traffic_stream(&name_a, &name_b, &mut probe, opener);\n\n    // Fill the ring BEFORE the wedged subscriber attaches — its attach replay\n    // is then the exact field engine's first gear.\n    let chunk = vec![0x42u8; FLOOD_CHUNK];\n    let mut op = 10u64;\n    for _ in 0..FLOOD_PRE {\n        operator\n            .net_stream_send(sid_b, &chunk, Some(MintedOp::new(Minter::Cli, op)), false)\n            .expect(\"pre-flood send\");\n        op += 1;\n    }\n\n    // The WEDGED subscriber: a real brain conn that subscribes from 0 and\n    // never reads a byte. The replay enqueues; its seat writer wedges on the\n    // pipe write and poisons at the 400ms bound. Held alive for the whole\n    // test (never dropped = the client never \"hangs up\" — the field shape).\n    let mut wedged = connect_retry(&name_a);\n    wedged\n        .net_stream_subscribe(sid_a, 0)\n        .expect(\"wedged subscribe send\");\n    thread::sleep(Duration::from_millis(1200)); // > deadline: the seat has poisoned\n\n    // T3: the producer stays FLAT while the poisoned seat halts. Every ack'd\n    // send round-trips B→A (QUIC transfer + ring append) — pre-fix each\n    // append serialized behind the wedged write's poison window.\n    let flood_started = Instant::now();\n    for _ in 0..FLOOD_POST {\n        let one = Instant::now();\n        operator\n            .net_stream_send(sid_b, &chunk, Some(MintedOp::new(Minter::Cli, op)), false)\n            .expect(\"post-wedge send\");\n        op += 1;\n        assert!(\n            one.elapsed() < Duration::from_secs(5),\n            \"one producer send stalled {}ms behind the wedged subscriber — the \\\n             poison window is composing into the producer path again\",\n            one.elapsed().as_millis()\n        );\n    }\n    let flood_elapsed = flood_started.elapsed();\n    assert!(\n        flood_elapsed < Duration::from_secs(25),\n        \"the post-wedge flood took {flood_elapsed:?} — serial poison windows are \\\n         back (pre-fix: ~400ms per retained frame under the stream mutex)\"\n    );\n\n    // T1 recovery: a fresh subscriber replays EVERYTHING gapless — the\n    // poisoned seat was removed (not left installed re-fed forever) and the\n    // ring lost nothing to the halt.\n    let want = opener.len() + (FLOOD_PRE + FLOOD_POST) * FLOOD_CHUNK;\n    let got = drain_gapless(&name_a, sid_a, want, Duration::from_secs(60));\n    assert_eq!(&got[..opener.len()], opener, \"the opener bytes lead the replay\");\n\n    drop(wedged);\n}\n\n// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n// T4 (forwarding cancel): a REAL forwarding leg — ack'd `net_stream_send`\n// ticks on the stream's send half — keeps serving while the subscriber is\n// healthy; when the subscriber seat POISONS (the same wedged-conn incident as\n// T1), the stream's serve lease cancels and the forwarder's NEXT send is\n// REFUSED (\"lease canceled\") — the leg terminates instead of feeding an\n// orphan forever. A new subscriber generation then renews the lease and a\n// fresh send serves again (recovery, the C2-lease seam this wave builds).\n#[test]\nfn poisoned_subscriber_cancels_the_paired_forwarding_leg_then_recovery_serves() {\n    init_home();\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    let opener = b\"{\\\"hello\\\":2}\\n\";\n    let (mut operator, _sid_b, sid_a) = open_traffic_stream(&name_a, &name_b, &mut probe, opener);\n\n    // The forwarding leg: an A-side conn tick-sending on the stream (ack'd, so\n    // a broker-side refusal surfaces as Err). Reports (ok_ticks, final_err).\n    let fw_name = name_a.clone();\n    let (fw_tx, fw_rx) = std::sync::mpsc::channel::<(u32, String)>();\n    let forwarder = thread::spawn(move || {\n        let mut fw = connect_retry(&fw_name);\n        let mut ok = 0u32;\n        for i in 0..600u64 {\n            match fw.net_stream_send(sid_a, b\"tick\\n\", Some(MintedOp::new(Minter::Cli, 100 + i)), false)\n            {\n                Ok(_) => ok += 1,\n                Err(e) => {\n                    let _ = fw_tx.send((ok, e.to_string()));\n                    return;\n                }\n            }\n            thread::sleep(Duration::from_millis(100));\n        }\n        let _ = fw_tx.send((ok, String::new()));\n    });\n\n    // Let the leg prove itself healthy, then wedge: fill the ring and attach\n    // the never-reading subscriber — replay wedge → poison → lease cancel.\n    thread::sleep(Duration::from_millis(500));\n    let chunk = vec![0x41u8; FLOOD_CHUNK];\n    for i in 0..FLOOD_PRE as u64 {\n        operator\n            .net_stream_send(_sid_b, &chunk, Some(MintedOp::new(Minter::Cli, 10 + i)), false)\n            .expect(\"flood send\");\n    }\n    let mut wedged = connect_retry(&name_a);\n    wedged\n        .net_stream_subscribe(sid_a, 0)\n        .expect(\"wedged subscribe send\");\n\n    // The forwarder must terminate on the refusal — bounded, no orphan leg.\n    let (ok_ticks, err) = fw_rx\n        .recv_timeout(Duration::from_secs(20))\n        .expect(\"the forwarding leg never terminated — an orphan leg survived the poison\");\n    forwarder.join().expect(\"forwarder joins\");\n    assert!(ok_ticks >= 1, \"the leg served while the subscriber was healthy\");\n    assert!(\n        err.contains(\"lease canceled\"),\n        \"the leg must terminate on the LEASE refusal (fix 4), got: {err:?}\"\n    );\n\n    // Recovery: a new subscriber generation renews the lease; a fresh send\n    // serves again — one poison never permanently dead-ends the stream.\n    // Subscribe the new generation from the ring TAIL (empty replay): the\n    // recovery contract under test is the LEASE renewal, not the replay (T1\n    // proves that) — and a from-0 subscriber that never drains the 1.5MB\n    // flood would legitimately wedge and re-cancel, testing nothing.\n    let renewed = {\n        let end = Instant::now() + Duration::from_secs(20);\n        let mut probe_op = 900u64;\n        loop {\n            assert!(Instant::now() < end, \"the lease never renewed for a new generation\");\n            let tail = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .find(|s| s.stream_id == sid_a)\n                .map(|s| s.next_seq)\n                .unwrap_or(0);\n            let mut f = connect_retry(&name_a);\n            f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");\n            // Give the fire-and-forget subscribe a beat to process, then\n            // probe the lease with a real send on a separate conn.\n            thread::sleep(Duration::from_millis(300));\n            let mut s = connect_retry(&name_a);\n            let sent = s.net_stream_send(\n                sid_a,\n                b\"recovered\\n\",\n                Some(MintedOp::new(Minter::Cli, probe_op)),\n                false,\n            );\n            probe_op += 1;\n            match sent {\n                Ok(_) => break true,\n                Err(e) if e.to_string().contains(\"lease canceled\")\n                    || e.to_string().contains(\"subscriber busy\") =>\n                {\n                    thread::sleep(Duration::from_millis(300));\n                }\n                Err(e) => panic!(\"unexpected recovery send failure: {e}\"),\n            }\n        }\n    };\n    assert!(renewed);\n    drop(wedged);\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T2 (fallback narrowing): a stream whose opener never pins (bytes, no\n// newline) is a TRANSIENT to the dispatcher — the claim requeues bounded and\n// goes terminal after exactly MAX_DISPATCH_ATTEMPTS failures, and NO peek\n// subscriber is ever created. Pre-fix the broad `Err(_)` arm fell through to\n// the ring peek, which subscribed and blocked forever reading a newline that\n// never comes — the claim stayed InFlight and the failure never bounded (and\n// each such retry manufactured a fresh replay subscriber on the conn).\n#[test]\nfn opener_transient_is_bounded_requeue_never_a_peek_subscriber() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    // 200 bytes, NO newline: the opener capture buffers forever (never pins).\n    let (_operator, _sid_b, sid_a) =\n        open_traffic_stream(&name_a, &name_b, &mut probe, &[b'x'; 200]);\n\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n\n    // Exactly MAX_DISPATCH_ATTEMPTS (3) bounded failures, each the honest\n    // opener-not-pinned transient... (backoffs 0.5s + 1s ≈ all three within\n    // a few seconds)\n    let needle = format!(\"DISPATCH:{sid_a}:Failed\");\n    let end = Instant::now() + Duration::from_secs(30);\n    loop {\n        let log = gen.stderr_log();\n        let n = log.matches(&needle).count();\n        if n >= 3 {\n            assert!(\n                log.contains(\"opener not pinned\"),\n                \"the failures must be the honest opener transient, got: {log}\"\n            );\n            break;\n        }\n        assert!(\n            Instant::now() < end,\n            \"the claim never reached its bounded 3 failures — a peek subscriber \\\n             is blocking the worker forever again (the pre-fix broad fallback); \\\n             saw {n}: {log}\"\n        );\n        thread::sleep(Duration::from_millis(100));\n    }\n    // ...then TERMINAL: no fourth attempt ever lands (no hot loop, and no\n    // peek subscriber left behind re-feeding).\n    thread::sleep(Duration::from_secs(3));\n    let log = gen.stderr_log();\n    assert_eq!(\n        log.matches(&needle).count(),\n        3,\n        \"the spent budget must be terminal — further attempts mean the claim \\\n         re-entered past its bound: {log}\"\n    );\n\n    gen.kill();\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T5 (circuit breaker + recovery): ONE seat-blocked worker failure (the\n// injected \"subscriber busy\" valve — the real string a poisoned replay\n// surfaces; the seat mechanics themselves are proven with real wedged conns\n// in T1/T3/T4) trips the GLOBAL breaker, requeues the claim WITHOUT burning\n// the transient budget, pauses claiming for the window, and the SAME stream\n// — a REAL sync pull carrying traffic — recovers and serves on the paced\n// retry. The not-abandonment discriminator: pre-Amendment semantics either\n// burned the claim terminal (v0.33) or hammered fresh replay subscribers.\n#[test]\nfn seat_blocked_failure_trips_the_breaker_and_the_stream_recovers() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen = Generation::spawn(\n        &name_a,\n        dir.path().join(\"gen.log\"),\n        &[(\"SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE\", \"1\")],\n    );\n\n    // A REAL request/reply pull: blocks until the dispatcher actually serves.\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\n        .expect(\"requester context store\");\n    let scratch = dir.path().join(\"scratch-b\");\n    let started = Instant::now();\n    let report = spt_daemon::sync::request_sync(\n        &mut operator,\n        conn.conn_id,\n        &[\"p-nothing\".to_string()],\n        MintedOp::new(Minter::Cli, 2),\n        &cs,\n        &scratch,\n    )\n    .expect(\"the stream must RECOVER through the breaker-paced retry (not abandonment)\");\n    let elapsed = started.elapsed();\n    assert!(report.applied.is_empty(), \"unknown ref applies nothing: {report:?}\");\n\n    // Pacing happened: the retry could not land before the 2s base window.\n    assert!(\n        elapsed >= Duration::from_millis(1900),\n        \"the pull completed in {elapsed:?} — the seat-blocked retry was not \\\n         paced by the breaker window\"\n    );\n\n    let log = gen.stderr_log();\n    assert!(\n        log.contains(\"injected subscriber busy failure\"),\n        \"the seat-blocked valve never fired — the breaker path was not exercised: {log}\"\n    );\n    assert!(\n        log.contains(\"event=breaker-trip\") && log.contains(\"window_ms=2000\"),\n        \"the trip must land the keyed breaker-trip record with the base window: {log}\"\n    );\n    // Budget NOT burned: exactly one Failed outcome, then the recovery serve\n    // (Served outcomes log no DISPATCH line).\n    let failed_lines = log.matches(\":Failed\").count();\n    assert_eq!(\n        failed_lines, 1,\n        \"a seat-blocked failure must requeue budget-free — more than one Failed \\\n         means it burned transient attempts (or never recovered): {log}\"\n    );\n\n    gen.kill();\n}\n\n// [int->REQ-SERVE-OWNERSHIP-GENERATION]\n// T6 (stale-ownership steal): an UNFINISHED stale attach row — the viewport\n// died raw with no FIN (its brain generation was killed mid-serve), which\n// finished-row retirement (D1/D1b) definitionally cannot see — plus a LIVE\n// replacement controller from the same operator identity on the same\n// session. The fresh dispatcher generation replays BOTH rows; the stale\n// row's replayed Request carries an OLDER attach generation than the\n// replacement and must neither TAKE nor CLEAR it: post-restart input keeps\n// rendering across several dispatcher polls with no EOF (pre-fix: the\n// replayed Request silently re-took the same-identity controller and the\n// stale worker's terminal detach cleared the replacement — the frozen-PTY /\n// steal composition).\n#[test]\nfn stale_unfinished_attach_replay_neither_takes_nor_clears_the_replacement_controller() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rds6-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // s1: attached + driven under gen1 — opener pinned, Request + input in\n    // the ring, everything a stale replay needs to steal.\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"S1-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"S1-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n\n    // The raw-close: kill gen1 mid-serve. NO detach follows — s1 stays\n    // UNFINISHED (and unretired) on a live conn: invisible to the\n    // finished-row discriminator, eligible for redispatch.\n    gen1.kill();\n    {\n        // Sanity: the row is genuinely unfinished (the T6 precondition that\n        // separates this from D1b's finished-unretired shape).\n        let rows = probe.net_streams().expect(\"net-streams\").streams;\n        let row = rows.iter().find(|s| s.stream_id == s1_a).expect(\"s1 row\");\n        assert!(!row.finished, \"T6 precondition: the stale row must be UNFINISHED\");\n    }\n\n    // The REPLACEMENT: same operator identity, same session, NEWER attach\n    // generation (client-minted wall-ms). gen2 replays s1 (unfinished →\n    // served) AND serves s2 — the stale Request's older generation must\n    // refuse against the newer controller whichever order the workers land.\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n\n    // Several seconds of live driving across many 50ms dispatcher polls —\n    // the window in which the stale replay lands (and must bounce off).\n    for round in 0..3u32 {\n        let marker = format!(\"CUR-{round}\\r\");\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 20 + u64::from(round))\n            .expect(\"input s2\");\n        let eof = render_until(\n            &mut operator,\n            s2,\n            &mut dec2,\n            &mut cur2,\n            &mut view2,\n            format!(\"CUR-{round}\").as_bytes(),\n            Duration::from_secs(20),\n        );\n        assert!(\n            !eof,\n            \"round {round}: the replacement controller's stream saw an EOF — the \\\n             stale generation's worker cleared it (the fix-6 steal)\"\n        );\n        thread::sleep(Duration::from_millis(700));\n    }\n    let painted = screen_of(&view2);\n    assert!(painted.contains(\"CUR-2\"), \"the replacement viewport kept flowing: {painted:?}\");\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T7 (mixed-image N-1, REAL traffic): a NEW dispatcher against an OLD broker\n// that answers the opener verb with its literal forward-compat reply\n// (\"unknown command kind …\", byte-identical via the test valve — the stable\n// KH-2.3 wire fact). The ring-peek fallback fires on exactly that answer and\n// SERVES CORRECTLY: a real attach with real PTY traffic classifies through\n// the peek and the operator's input renders (doyle rider: handshake-only N-1\n// is insufficient — the stream must carry traffic). The narrowing half\n// (transport errors never reach the peek) is T2.\n#[test]\nfn old_broker_unknown_verb_answer_falls_back_to_the_peek_and_serves_real_traffic() {\n    init_home();\n    // The N-1 broker image: the opener verb answers like a broker that\n    // predates it. Set BEFORE bind; nextest = process-per-test.\n    std::env::set_var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\", \"1\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    // The valve is live: the opener query answers exactly the old-broker\n    // unknown-verb error (the precondition that makes this leg N-1).\n    let opener_err = probe\n        .net_stream_opener(1)\n        .expect_err(\"the N-1 valve must answer the opener verb with an error\");\n    assert!(\n        opener_err.to_string().contains(\"unknown command kind\"),\n        \"byte-identical old-broker answer required, got: {opener_err}\"\n    );\n\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rds7-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\n    send_attach_input(&mut operator, s1, b\"N1-MARK\\r\", 10).expect(\"input\");\n\n    // Served through the peek: classification had ONLY the ring to read.\n    let mut dec = AttachDecoder::new();\n    let (mut cur, mut view) = (0u64, Vec::new());\n    let eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"N1-MARK\", Duration::from_secs(30));\n    assert!(!eof, \"the N-1 fallback serve must not tear the stream down\");\n\n    // And it served FIRST TRY — the fallback is a clean path, not a\n    // fail-then-retry accident.\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n    let log = gen.stderr_log();\n    assert!(\n        !log.contains(&format!(\"DISPATCH:{s1_a}:Failed\")),\n        \"the unsupported-verb answer must route straight to the peek, never \\\n         through Failed/requeue: {log}\"\n    );\n\n    gen.kill();\n    let _ = probe.kill_session();\n}\n"}],"details":{"displayContent":{"text":"fn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {\n…\n    'attach: loop {\n…\n        loop {\n…\n            match f.read_event() {\n…\n                Err(e) => panic!(\"recovery subscriber read failed: {e}\"),\n            }\n        }\n    }\n}\n\nconst FLOOD_CHUNK: usize = 32 * 1024;\nconst FLOOD_PRE: usize = 48; // chunks before the wedged subscriber attaches\nconst FLOOD_POST: usize = 48; // chunks appended WHILE the subscriber is wedged\n\n// [int->REQ-HAZARD-REDISPATCH-STALL]\n// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n// production broker subscribe path: a REAL subscriber conn that attaches with\n// a full ring and never reads — its seat's replay wedges on the named-pipe\n// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat\n// is removed, later appends never re-feed it, and the PRODUCER — B's ack'd\n// sends through A's read pump — stays flat throughout (pre-fix: the replay\n// ran under the stream mutex with discarded errors, one 400ms poison window\n// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh\n// subscriber replays everything gapless from the cursor — no abandonment.\n#[test]\nfn wedged_subscriber_never_stalls_the_producer_and_recovery_replays_gapless() {\n    init_home();\n    // Shrink the bounded write so the poison window is 400ms, not 15s — the\n    // knob grants wall-clock only, never relaxes an assertion (the\n    // conn_blackhole_lifecycle discipline). nextest: process-per-test.\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    let opener = b\"{\\\"hello\\\":1}\\n\";\n    let (mut operator, sid_b, sid_a) = open_traffic_stream(&name_a, &name_b, &mut probe, opener);\n\n    // Fill the ring BEFORE the wedged subscriber attaches — its attach replay\n    // is then the exact field engine's first gear.\n    let chunk = vec![0x42u8; FLOOD_CHUNK];\n    let mut op = 10u64;\n    for _ in 0..FLOOD_PRE {\n        operator\n            .net_stream_send(sid_b, &chunk, Some(MintedOp::new(Minter::Cli, op)), false)\n            .expect(\"pre-flood send\");\n        op += 1;\n    }\n\n    // The WEDGED subscriber: a real brain conn that subscribes from 0 and\n    // never reads a byte. The replay enqueues; its seat writer wedges on the\n    // pipe write and poisons at the 400ms bound. Held alive for the whole\n    // test (never dropped = the client never \"hangs up\" — the field shape).\n    let mut wedged = connect_retry(&name_a);\n    wedged\n        .net_stream_subscribe(sid_a, 0)\n        .expect(\"wedged subscribe send\");\n    thread::sleep(Duration::from_millis(1200)); // > deadline: the seat has poisoned\n\n    // T3: the producer stays FLAT while the poisoned seat halts. Every ack'd\n    // send round-trips B→A (QUIC transfer + ring append) — pre-fix each\n    // append serialized behind the wedged write's poison window.\n    let flood_started = Instant::now();\n    for _ in 0..FLOOD_POST {\n        let one = Instant::now();\n        operator\n            .net_stream_send(sid_b, &chunk, Some(MintedOp::new(Minter::Cli, op)), false)\n            .expect(\"post-wedge send\");\n        op += 1;\n        assert!(\n            one.elapsed() < Duration::from_secs(5),\n            \"one producer send stalled {}ms behind the wedged subscriber — the \\\n             poison window is composing into the producer path again\",\n            one.elapsed().as_millis()\n        );\n    }\n    let flood_elapsed = flood_started.elapsed();\n    assert!(\n        flood_elapsed < Duration::from_secs(25),\n        \"the post-wedge flood took {flood_elapsed:?} — serial poison windows are \\\n         back (pre-fix: ~400ms per retained frame under the stream mutex)\"\n    );\n\n    // T1 recovery: a fresh subscriber replays EVERYTHING gapless — the\n    // poisoned seat was removed (not left installed re-fed forever) and the\n    // ring lost nothing to the halt.\n    let want = opener.len() + (FLOOD_PRE + FLOOD_POST) * FLOOD_CHUNK;\n    let got = drain_gapless(&name_a, sid_a, want, Duration::from_secs(60));\n    assert_eq!(&got[..opener.len()], opener, \"the opener bytes lead the replay\");\n\n    drop(wedged);\n}\n\n// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n// T4 (forwarding cancel): a REAL forwarding leg — ack'd `net_stream_send`\n// ticks on the stream's send half — keeps serving while the subscriber is\n// healthy; when the subscriber seat POISONS (the same wedged-conn incident as\n// T1), the stream's serve lease cancels and the forwarder's NEXT send is\n// REFUSED (\"lease canceled\") — the leg terminates instead of feeding an\n// orphan forever. A new subscriber generation then renews the lease and a\n// fresh send serves again (recovery, the C2-lease seam this wave builds).\n#[test]\nfn poisoned_subscriber_cancels_the_paired_forwarding_leg_then_recovery_serves() {\n    init_home();\n    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    let opener = b\"{\\\"hello\\\":2}\\n\";\n    let (mut operator, _sid_b, sid_a) = open_traffic_stream(&name_a, &name_b, &mut probe, opener);\n\n    // The forwarding leg: an A-side conn tick-sending on the stream (ack'd, so\n    // a broker-side refusal surfaces as Err). Reports (ok_ticks, final_err).\n    let fw_name = name_a.clone();\n    let (fw_tx, fw_rx) = std::sync::mpsc::channel::<(u32, String)>();\n    let forwarder = thread::spawn(move || {\n        let mut fw = connect_retry(&fw_name);\n        let mut ok = 0u32;\n        for i in 0..600u64 {\n            match fw.net_stream_send(sid_a, b\"tick\\n\", Some(MintedOp::new(Minter::Cli, 100 + i)), false)\n            {\n                Ok(_) => ok += 1,\n                Err(e) => {\n                    let _ = fw_tx.send((ok, e.to_string()));\n                    return;\n                }\n            }\n            thread::sleep(Duration::from_millis(100));\n        }\n        let _ = fw_tx.send((ok, String::new()));\n    });\n\n    // Let the leg prove itself healthy, then wedge: fill the ring and attach\n    // the never-reading subscriber — replay wedge → poison → lease cancel.\n    thread::sleep(Duration::from_millis(500));\n    let chunk = vec![0x41u8; FLOOD_CHUNK];\n    for i in 0..FLOOD_PRE as u64 {\n        operator\n            .net_stream_send(_sid_b, &chunk, Some(MintedOp::new(Minter::Cli, 10 + i)), false)\n            .expect(\"flood send\");\n    }\n    let mut wedged = connect_retry(&name_a);\n    wedged\n        .net_stream_subscribe(sid_a, 0)\n        .expect(\"wedged subscribe send\");\n\n    // The forwarder must terminate on the refusal — bounded, no orphan leg.\n    let (ok_ticks, err) = fw_rx\n        .recv_timeout(Duration::from_secs(20))\n        .expect(\"the forwarding leg never terminated — an orphan leg survived the poison\");\n    forwarder.join().expect(\"forwarder joins\");\n    assert!(ok_ticks >= 1, \"the leg served while the subscriber was healthy\");\n    assert!(\n        err.contains(\"lease canceled\"),\n        \"the leg must terminate on the LEASE refusal (fix 4), got: {err:?}\"\n    );\n\n    // Recovery: a new subscriber generation renews the lease; a fresh send\n    // serves again — one poison never permanently dead-ends the stream.\n    // Subscribe the new generation from the ring TAIL (empty replay): the\n    // recovery contract under test is the LEASE renewal, not the replay (T1\n    // proves that) — and a from-0 subscriber that never drains the 1.5MB\n    // flood would legitimately wedge and re-cancel, testing nothing.\n    let renewed = {\n        let end = Instant::now() + Duration::from_secs(20);\n        let mut probe_op = 900u64;\n        loop {\n            assert!(Instant::now() < end, \"the lease never renewed for a new generation\");\n            let tail = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .find(|s| s.stream_id == sid_a)\n                .map(|s| s.next_seq)\n                .unwrap_or(0);\n            let mut f = connect_retry(&name_a);\n            f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");\n            // Give the fire-and-forget subscribe a beat to process, then\n            // probe the lease with a real send on a separate conn.\n            thread::sleep(Duration::from_millis(300));\n            let mut s = connect_retry(&name_a);\n            let sent = s.net_stream_send(\n                sid_a,\n                b\"recovered\\n\",\n                Some(MintedOp::new(Minter::Cli, probe_op)),\n                false,\n            );\n            probe_op += 1;\n            match sent {\n                Ok(_) => break true,\n                Err(e) if e.to_string().contains(\"lease canceled\")\n                    || e.to_string().contains(\"subscriber busy\") =>\n                {\n                    thread::sleep(Duration::from_millis(300));\n                }\n                Err(e) => panic!(\"unexpected recovery send failure: {e}\"),\n            }\n        }\n    };\n    assert!(renewed);\n    drop(wedged);\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T2 (fallback narrowing): a stream whose opener never pins (bytes, no\n// newline) is a TRANSIENT to the dispatcher — the claim requeues bounded and\n// goes terminal after exactly MAX_DISPATCH_ATTEMPTS failures, and NO peek\n// subscriber is ever created. Pre-fix the broad `Err(_)` arm fell through to\n// the ring peek, which subscribed and blocked forever reading a newline that\n// never comes — the claim stayed InFlight and the failure never bounded (and\n// each such retry manufactured a fresh replay subscriber on the conn).\n#[test]\nfn opener_transient_is_bounded_requeue_never_a_peek_subscriber() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n    let mut probe = connect_retry(&name_a);\n\n    // 200 bytes, NO newline: the opener capture buffers forever (never pins).\n    let (_operator, _sid_b, sid_a) =\n        open_traffic_stream(&name_a, &name_b, &mut probe, &[b'x'; 200]);\n\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n\n    // Exactly MAX_DISPATCH_ATTEMPTS (3) bounded failures, each the honest\n    // opener-not-pinned transient... (backoffs 0.5s + 1s ≈ all three within\n    // a few seconds)\n    let needle = format!(\"DISPATCH:{sid_a}:Failed\");\n    let end = Instant::now() + Duration::from_secs(30);\n    loop {\n        let log = gen.stderr_log();\n        let n = log.matches(&needle).count();\n        if n >= 3 {\n            assert!(\n                log.contains(\"opener not pinned\"),\n                \"the failures must be the honest opener transient, got: {log}\"\n            );\n            break;\n        }\n        assert!(\n            Instant::now() < end,\n            \"the claim never reached its bounded 3 failures — a peek subscriber \\\n             is blocking the worker forever again (the pre-fix broad fallback); \\\n             saw {n}: {log}\"\n        );\n        thread::sleep(Duration::from_millis(100));\n    }\n    // ...then TERMINAL: no fourth attempt ever lands (no hot loop, and no\n    // peek subscriber left behind re-feeding).\n    thread::sleep(Duration::from_secs(3));\n    let log = gen.stderr_log();\n    assert_eq!(\n        log.matches(&needle).count(),\n        3,\n        \"the spent budget must be terminal — further attempts mean the claim \\\n         re-entered past its bound: {log}\"\n    );\n\n    gen.kill();\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T5 (circuit breaker + recovery): ONE seat-blocked worker failure (the\n// injected \"subscriber busy\" valve — the real string a poisoned replay\n// surfaces; the seat mechanics themselves are proven with real wedged conns\n// in T1/T3/T4) trips the GLOBAL breaker, requeues the claim WITHOUT burning\n// the transient budget, pauses claiming for the window, and the SAME stream\n// — a REAL sync pull carrying traffic — recovers and serves on the paced\n// retry. The not-abandonment discriminator: pre-Amendment semantics either\n// burned the claim terminal (v0.33) or hammered fresh replay subscribers.\n#[test]\nfn seat_blocked_failure_trips_the_breaker_and_the_stream_recovers() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen = Generation::spawn(\n        &name_a,\n        dir.path().join(\"gen.log\"),\n        &[(\"SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE\", \"1\")],\n    );\n\n    // A REAL request/reply pull: blocks until the dispatcher actually serves.\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\n        .expect(\"requester context store\");\n    let scratch = dir.path().join(\"scratch-b\");\n    let started = Instant::now();\n    let report = spt_daemon::sync::request_sync(\n        &mut operator,\n        conn.conn_id,\n        &[\"p-nothing\".to_string()],\n        MintedOp::new(Minter::Cli, 2),\n        &cs,\n        &scratch,\n    )\n    .expect(\"the stream must RECOVER through the breaker-paced retry (not abandonment)\");\n    let elapsed = started.elapsed();\n    assert!(report.applied.is_empty(), \"unknown ref applies nothing: {report:?}\");\n\n    // Pacing happened: the retry could not land before the 2s base window.\n    assert!(\n        elapsed >= Duration::from_millis(1900),\n        \"the pull completed in {elapsed:?} — the seat-blocked retry was not \\\n         paced by the breaker window\"\n    );\n\n    let log = gen.stderr_log();\n    assert!(\n        log.contains(\"injected subscriber busy failure\"),\n        \"the seat-blocked valve never fired — the breaker path was not exercised: {log}\"\n    );\n    assert!(\n        log.contains(\"event=breaker-trip\") && log.contains(\"window_ms=2000\"),\n        \"the trip must land the keyed breaker-trip record with the base window: {log}\"\n    );\n    // Budget NOT burned: exactly one Failed outcome, then the recovery serve\n    // (Served outcomes log no DISPATCH line).\n    let failed_lines = log.matches(\":Failed\").count();\n    assert_eq!(\n        failed_lines, 1,\n        \"a seat-blocked failure must requeue budget-free — more than one Failed \\\n         means it burned transient attempts (or never recovered): {log}\"\n    );\n\n    gen.kill();\n}\n\n// [int->REQ-SERVE-OWNERSHIP-GENERATION]\n// T6 (stale-ownership steal): an UNFINISHED stale attach row — the viewport\n// died raw with no FIN (its brain generation was killed mid-serve), which\n// finished-row retirement (D1/D1b) definitionally cannot see — plus a LIVE\n// replacement controller from the same operator identity on the same\n// session. The fresh dispatcher generation replays BOTH rows; the stale\n// row's replayed Request carries an OLDER attach generation than the\n// replacement and must neither TAKE nor CLEAR it: post-restart input keeps\n// rendering across several dispatcher polls with no EOF (pre-fix: the\n// replayed Request silently re-took the same-identity controller and the\n// stale worker's terminal detach cleared the replacement — the frozen-PTY /\n// steal composition).\n#[test]\nfn stale_unfinished_attach_replay_neither_takes_nor_clears_the_replacement_controller() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rds6-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // s1: attached + driven under gen1 — opener pinned, Request + input in\n    // the ring, everything a stale replay needs to steal.\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"S1-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"S1-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n\n    // The raw-close: kill gen1 mid-serve. NO detach follows — s1 stays\n    // UNFINISHED (and unretired) on a live conn: invisible to the\n    // finished-row discriminator, eligible for redispatch.\n    gen1.kill();\n    {\n        // Sanity: the row is genuinely unfinished (the T6 precondition that\n        // separates this from D1b's finished-unretired shape).\n        let rows = probe.net_streams().expect(\"net-streams\").streams;\n        let row = rows.iter().find(|s| s.stream_id == s1_a).expect(\"s1 row\");\n        assert!(!row.finished, \"T6 precondition: the stale row must be UNFINISHED\");\n    }\n\n    // The REPLACEMENT: same operator identity, same session, NEWER attach\n    // generation (client-minted wall-ms). gen2 replays s1 (unfinished →\n    // served) AND serves s2 — the stale Request's older generation must\n    // refuse against the newer controller whichever order the workers land.\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n\n    // Several seconds of live driving across many 50ms dispatcher polls —\n    // the window in which the stale replay lands (and must bounce off).\n    for round in 0..3u32 {\n        let marker = format!(\"CUR-{round}\\r\");\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 20 + u64::from(round))\n            .expect(\"input s2\");\n        let eof = render_until(\n            &mut operator,\n            s2,\n            &mut dec2,\n            &mut cur2,\n            &mut view2,\n            format!(\"CUR-{round}\").as_bytes(),\n            Duration::from_secs(20),\n        );\n        assert!(\n            !eof,\n            \"round {round}: the replacement controller's stream saw an EOF — the \\\n             stale generation's worker cleared it (the fix-6 steal)\"\n        );\n        thread::sleep(Duration::from_millis(700));\n    }\n    let painted = screen_of(&view2);\n    assert!(painted.contains(\"CUR-2\"), \"the replacement viewport kept flowing: {painted:?}\");\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-DISPATCH-FALLBACK-CIRCUIT]\n// T7 (mixed-image N-1, REAL traffic): a NEW dispatcher against an OLD broker\n// that answers the opener verb with its literal forward-compat reply\n// (\"unknown command kind …\", byte-identical via the test valve — the stable\n// KH-2.3 wire fact). The ring-peek fallback fires on exactly that answer and\n// SERVES CORRECTLY: a real attach with real PTY traffic classifies through\n// the peek and the operator's input renders (doyle rider: handshake-only N-1\n// is insufficient — the stream must carry traffic). The narrowing half\n// (transport errors never reach the peek) is T2.\n#[test]\nfn old_broker_unknown_verb_answer_falls_back_to_the_peek_and_serves_real_traffic() {\n    init_home();\n    // The N-1 broker image: the opener verb answers like a broker that\n    // predates it. Set BEFORE bind; nextest = process-per-test.\n    std::env::set_var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\", \"1\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    // The valve is live: the opener query answers exactly the old-broker\n    // unknown-verb error (the precondition that makes this leg N-1).\n    let opener_err = probe\n        .net_stream_opener(1)\n        .expect_err(\"the N-1 valve must answer the opener verb with an error\");\n    assert!(\n        opener_err.to_string().contains(\"unknown command kind\"),\n        \"byte-identical old-broker answer required, got: {opener_err}\"\n    );\n\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rds7-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\n    send_attach_input(&mut operator, s1, b\"N1-MARK\\r\", 10).expect(\"input\");\n\n    // Served through the peek: classification had ONLY the ring to read.\n    let mut dec = AttachDecoder::new();\n    let (mut cur, mut view) = (0u64, Vec::new());\n    let eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"N1-MARK\", Duration::from_secs(30));\n    assert!(!eof, \"the N-1 fallback serve must not tear the stream down\");\n\n    // And it served FIRST TRY — the fallback is a clean path, not a\n    // fail-then-retry accident.\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n    let log = gen.stderr_log();\n    assert!(\n        !log.contains(&format!(\"DISPATCH:{s1_a}:Failed\")),\n        \"the unsupported-verb answer must route straight to the peek, never \\\n         through Failed/requeue: {log}\"\n    );\n\n    gen.kill();\n    let _ = probe.kill_session();\n}\n","startLine":257,"lineNumbers":[257,null,259,null,265,null,272,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,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch_stall.rs"}}},"isError":false,"timestamp":1784271525445}}
{"type":"message","id":"dffc0ee3","parentId":"3a260d3c","timestamp":"2026-07-17T06:58:45.455Z","message":{"role":"toolResult","toolCallId":"call_QGh15lg2yzX2CrCkjpwBFfLx|fc_089095e18118ee7d016a59d2a54fec8190aaccda452ace5347","toolName":"read","content":[{"type":"text","text":"fn dispatch_event(\n…\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}\n\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n/// set at dispatcher start makes exactly ONE worker fail before setup —\n/// proving a transient worker-start failure requeues and recovers through\n/// the REAL claim path. Never set in production; the latch arms once per\n/// process and disarms on first take.\nfn take_injected_transient() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// TEST-RIG-ONLY (the T5 breaker regression): `SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE`\n/// makes exactly ONE worker fail with the seat-blocked answer (\"subscriber\n/// busy\" — the string a real poisoned replay surfaces) — proving the GLOBAL\n/// breaker trips, claiming pauses, and the SAME stream recovers on the paced\n/// retry through the REAL claim path. The seat-poison mechanics themselves\n/// are proven with real wedged conns (T1/T3/T4); this valve isolates the\n/// loop's RESPONSE to the classification. Same latch discipline as the\n/// transient valve.\nfn take_injected_seatblocked() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\n/// worker (policies/stores re-load from the canonical paths per request —\n/// the access-gate stance, config edits take effect without a restart).\n#[derive(Debug, Clone)]\npub struct DispatchPaths {\n    /// The perch tree (WAN message funnel target).\n    pub owlery: PathBuf,\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\n    pub releases: PathBuf,\n    /// Transient bundle/artifact scratch.\n    pub scratch: PathBuf,\n}\n\nimpl DispatchPaths {\n    /// The production layout under [`perch::spt_home`].\n    pub fn production() -> DispatchPaths {\n        let home = perch::spt_home();\n        DispatchPaths {\n            owlery: perch::owlery_dir(),\n            releases: home.join(\"releases\"),\n            scratch: home.join(\"scratch\").join(\"net\"),\n        }\n    }\n}\n\n/// Watch the broker's stream table and dispatch every **peer-initiated**\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\n/// (the daemon spawns it on its own); workers detach (each ends with its\n/// stream).\npub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n…\n                    }\n…\n                };\n…\n            });\n        }\n…\n    }\n…\n}\n\n[Showing lines 289-563 of 1558. Use :564 to continue]"}],"details":{"truncation":{"content":") -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}\n\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n/// set at dispatcher start makes exactly ONE worker fail before setup —\n/// proving a transient worker-start failure requeues and recovers through\n/// the REAL claim path. Never set in production; the latch arms once per\n/// process and disarms on first take.\nfn take_injected_transient() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// TEST-RIG-ONLY (the T5 breaker regression): `SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE`\n/// makes exactly ONE worker fail with the seat-blocked answer (\"subscriber\n/// busy\" — the string a real poisoned replay surfaces) — proving the GLOBAL\n/// breaker trips, claiming pauses, and the SAME stream recovers on the paced\n/// retry through the REAL claim path. The seat-poison mechanics themselves\n/// are proven with real wedged conns (T1/T3/T4); this valve isolates the\n/// loop's RESPONSE to the classification. Same latch discipline as the\n/// transient valve.\nfn take_injected_seatblocked() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\n/// worker (policies/stores re-load from the canonical paths per request —\n/// the access-gate stance, config edits take effect without a restart).\n#[derive(Debug, Clone)]\npub struct DispatchPaths {\n    /// The perch tree (WAN message funnel target).\n    pub owlery: PathBuf,\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\n    pub releases: PathBuf,\n    /// Transient bundle/artifact scratch.\n    pub scratch: PathBuf,\n}\n\nimpl DispatchPaths {\n    /// The production layout under [`perch::spt_home`].\n    pub fn production() -> DispatchPaths {\n        let home = perch::spt_home();\n        DispatchPaths {\n            owlery: perch::owlery_dir(),\n            releases: home.join(\"releases\"),\n            scratch: home.join(\"scratch\").join(\"net\"),\n        }\n    }\n}\n\n/// Watch the broker's stream table and dispatch every **peer-initiated**\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\n/// (the daemon spawns it on its own); workers detach (each ends with its\n/// stream).\npub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),","truncated":true,"truncatedBy":"lines","totalLines":1270,"totalBytes":13337,"outputLines":275,"outputBytes":13337,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn dispatch_event(\n…\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}\n\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n/// set at dispatcher start makes exactly ONE worker fail before setup —\n/// proving a transient worker-start failure requeues and recovers through\n/// the REAL claim path. Never set in production; the latch arms once per\n/// process and disarms on first take.\nfn take_injected_transient() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// TEST-RIG-ONLY (the T5 breaker regression): `SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE`\n/// makes exactly ONE worker fail with the seat-blocked answer (\"subscriber\n/// busy\" — the string a real poisoned replay surfaces) — proving the GLOBAL\n/// breaker trips, claiming pauses, and the SAME stream recovers on the paced\n/// retry through the REAL claim path. The seat-poison mechanics themselves\n/// are proven with real wedged conns (T1/T3/T4); this valve isolates the\n/// loop's RESPONSE to the classification. Same latch discipline as the\n/// transient valve.\nfn take_injected_seatblocked() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_SEATBLOCKED_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\n/// worker (policies/stores re-load from the canonical paths per request —\n/// the access-gate stance, config edits take effect without a restart).\n#[derive(Debug, Clone)]\npub struct DispatchPaths {\n    /// The perch tree (WAN message funnel target).\n    pub owlery: PathBuf,\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\n    pub releases: PathBuf,\n    /// Transient bundle/artifact scratch.\n    pub scratch: PathBuf,\n}\n\nimpl DispatchPaths {\n    /// The production layout under [`perch::spt_home`].\n    pub fn production() -> DispatchPaths {\n        let home = perch::spt_home();\n        DispatchPaths {\n            owlery: perch::owlery_dir(),\n            releases: home.join(\"releases\"),\n            scratch: home.join(\"scratch\").join(\"net\"),\n        }\n    }\n}\n\n/// Watch the broker's stream table and dispatch every **peer-initiated**\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\n/// (the daemon spawns it on its own); workers detach (each ends with its\n/// stream).\npub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n…\n                    }\n…\n                };\n…\n            });\n        }\n…\n    }\n…\n}","startLine":281,"lineNumbers":[281,null,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,null,567,null,569,null,575,576,null,578,null,580]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":13337,"outputLines":275,"outputBytes":13337,"shownRange":{"start":289,"end":563},"nextOffset":564}}},"isError":false,"timestamp":1784271525455}}
{"type":"message","id":"8d3d00b5","parentId":"dffc0ee3","timestamp":"2026-07-17T06:58:45.467Z","message":{"role":"toolResult","toolCallId":"call_jAvTsucBRr2Jj5GRYJjoOQYa|fc_089095e18118ee7d016a59d2a54ff881908c0251a05d804062","toolName":"read","content":[{"type":"text","text":"pub fn run_dispatch_loop(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        for s in reply.streams {\n…\n            thread::spawn(move || {\n…\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n…\n                    {\n…\n                        eprintln!(\n…\n                            dispatch_event(\n…\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n…\n        }\n…\n    };\n…\n}\n\n[Showing lines 559-903 of 1558. Use :904 to continue]"}],"details":{"truncation":{"content":"                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {","truncated":true,"truncatedBy":"lines","totalLines":1000,"totalBytes":14868,"outputLines":345,"outputBytes":14868,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn run_dispatch_loop(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        for s in reply.streams {\n…\n            thread::spawn(move || {\n…\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n…\n                    {\n…\n                        eprintln!(\n…\n                            dispatch_event(\n…\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n…\n        }\n…\n    };\n…\n}","startLine":429,"lineNumbers":[429,null,435,null,455,null,464,null,498,null,548,549,null,551,null,554,null,556,null,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,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,901,902,903,null,927,null,962,null,968]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":14868,"outputLines":345,"outputBytes":14868,"shownRange":{"start":559,"end":903},"nextOffset":904}}},"isError":false,"timestamp":1784271525467}}
{"type":"message","id":"71bcc521","parentId":"8d3d00b5","timestamp":"2026-07-17T06:58:45.493Z","message":{"role":"toolResult","toolCallId":"call_heesQYyVQVwgtPCAFMc52So9|fc_089095e18118ee7d016a59d2a550188190baf0005c2f161e18","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.36.0] - 2026-07-16\n\nA stability release for attached sessions. A daemon update no longer disrupts\nactive sessions, and a session stuck by a bad connection now recovers on its own.\n\n### Fixed\n\n- Attached sessions no longer freeze after a daemon update or refresh.\n  Previously, a single bad viewer connection could hang an attached session for\n  up to a minute; a stuck viewer can no longer freeze other sessions.\n\n- Improved the stability of controlled sessions across a daemon restart.\n  Previously, session controllers could mix up and drop their attached sessions.\n\n- A session left stuck by a bad connection now recovers on reconnect, instead of\n  staying unresponsive.\n\n### Internal\n\n- Improved logging granularity for attached endpoints.\n\n## [0.35.0] - 2026-07-16\n\nA subnet resilience and visibility release. A node that briefly cannot reach a\npeer no longer isolates itself, and node status now shows whether your peers are\nactually reachable.\n\n### Added\n\n- **Peer reachability in `spt daemon status` and `spt subnet status`.** Both now\n  report how your node is really doing on its subnets: how many peers are\n  currently reachable, when a peer was last reached successfully, and when the\n  node last accepted a registry update from the subnet. When every peer is\n  unreachable the status reads **DEGRADED** and names the stage that is failing,\n  instead of staying green while the node is cut off. Dial failures in the daemon\n  log now carry the failing stage and a timestamp.\n\n### Fixed\n\n- **A node no longer strands itself from its subnets after a brief failure to\n  reach a peer.** Previously a single failed dial could delete the node's only\n  cached route to a peer — even while a valid address for that peer sat in the\n  subnet roster — leaving the node quietly isolated with its status still green.\n  Routes are now kept and marked unreachable rather than deleted, and address\n  resolution falls back to the roster, so the node recovers on its own with no\n  manual state surgery.\n\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\n  address has come to claim a different peer's identity, the daemon now detects\n  and repairs it from the subnet roster when it starts — and says so loudly in\n  the log — rather than carrying the bad entry forward.\n\n## [0.34.0] - 2026-07-16\n\nA stability fix for live remote sessions during a daemon update.\n\n### Fixed\n\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\n  A daemon cycle could previously replay already-finished output onto a live\n  remote terminal: old text re-typed itself, control of the session was stolen,\n…\n  the remote sessions you are attached to keep running cleanly across an update.\n\n…\nFirst public release of `spt`.\n\n[Showing lines 1-73 of 1147. Use :74 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.36.0] - 2026-07-16\r\n\r\nA stability release for attached sessions. A daemon update no longer disrupts\r\nactive sessions, and a session stuck by a bad connection now recovers on its own.\r\n\r\n### Fixed\r\n\r\n- Attached sessions no longer freeze after a daemon update or refresh.\r\n  Previously, a single bad viewer connection could hang an attached session for\r\n  up to a minute; a stuck viewer can no longer freeze other sessions.\r\n\r\n- Improved the stability of controlled sessions across a daemon restart.\r\n  Previously, session controllers could mix up and drop their attached sessions.\r\n\r\n- A session left stuck by a bad connection now recovers on reconnect, instead of\r\n  staying unresponsive.\r\n\r\n### Internal\r\n\r\n- Improved logging granularity for attached endpoints.\r\n\r\n## [0.35.0] - 2026-07-16\r\n\r\nA subnet resilience and visibility release. A node that briefly cannot reach a\r\npeer no longer isolates itself, and node status now shows whether your peers are\r\nactually reachable.\r\n\r\n### Added\r\n\r\n- **Peer reachability in `spt daemon status` and `spt subnet status`.** Both now\r\n  report how your node is really doing on its subnets: how many peers are\r\n  currently reachable, when a peer was last reached successfully, and when the\r\n  node last accepted a registry update from the subnet. When every peer is\r\n  unreachable the status reads **DEGRADED** and names the stage that is failing,\r\n  instead of staying green while the node is cut off. Dial failures in the daemon\r\n  log now carry the failing stage and a timestamp.\r\n\r\n### Fixed\r\n\r\n- **A node no longer strands itself from its subnets after a brief failure to\r\n  reach a peer.** Previously a single failed dial could delete the node's only\r\n  cached route to a peer — even while a valid address for that peer sat in the\r\n  subnet roster — leaving the node quietly isolated with its status still green.\r\n  Routes are now kept and marked unreachable rather than deleted, and address\r\n  resolution falls back to the roster, so the node recovers on its own with no\r\n  manual state surgery.\r\n\r\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\r\n  address has come to claim a different peer's identity, the daemon now detects\r\n  and repairs it from the subnet roster when it starts — and says so loudly in\r\n  the log — rather than carrying the bad entry forward.\r\n\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r","truncated":true,"truncatedBy":"lines","totalLines":1147,"totalBytes":3208,"outputLines":73,"outputBytes":3208,"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.36.0] - 2026-07-16\r\n\r\nA stability release for attached sessions. A daemon update no longer disrupts\r\nactive sessions, and a session stuck by a bad connection now recovers on its own.\r\n\r\n### Fixed\r\n\r\n- Attached sessions no longer freeze after a daemon update or refresh.\r\n  Previously, a single bad viewer connection could hang an attached session for\r\n  up to a minute; a stuck viewer can no longer freeze other sessions.\r\n\r\n- Improved the stability of controlled sessions across a daemon restart.\r\n  Previously, session controllers could mix up and drop their attached sessions.\r\n\r\n- A session left stuck by a bad connection now recovers on reconnect, instead of\r\n  staying unresponsive.\r\n\r\n### Internal\r\n\r\n- Improved logging granularity for attached endpoints.\r\n\r\n## [0.35.0] - 2026-07-16\r\n\r\nA subnet resilience and visibility release. A node that briefly cannot reach a\r\npeer no longer isolates itself, and node status now shows whether your peers are\r\nactually reachable.\r\n\r\n### Added\r\n\r\n- **Peer reachability in `spt daemon status` and `spt subnet status`.** Both now\r\n  report how your node is really doing on its subnets: how many peers are\r\n  currently reachable, when a peer was last reached successfully, and when the\r\n  node last accepted a registry update from the subnet. When every peer is\r\n  unreachable the status reads **DEGRADED** and names the stage that is failing,\r\n  instead of staying green while the node is cut off. Dial failures in the daemon\r\n  log now carry the failing stage and a timestamp.\r\n\r\n### Fixed\r\n\r\n- **A node no longer strands itself from its subnets after a brief failure to\r\n  reach a peer.** Previously a single failed dial could delete the node's only\r\n  cached route to a peer — even while a valid address for that peer sat in the\r\n  subnet roster — leaving the node quietly isolated with its status still green.\r\n  Routes are now kept and marked unreachable rather than deleted, and address\r\n  resolution falls back to the roster, so the node recovers on its own with no\r\n  manual state surgery.\r\n\r\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\r\n  address has come to claim a different peer's identity, the daemon now detects\r\n  and repairs it from the subnet roster when it starts — and says so loudly in\r\n  the log — rather than carrying the bad entry forward.\r\n\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r\n…\n  the remote sessions you are attached to keep running cleanly across an update.\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,null,76,77,null,1146]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CHANGELOG.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1147,"totalBytes":3208,"outputLines":73,"outputBytes":3208,"shownRange":{"start":1,"end":73},"nextOffset":74}}},"isError":false,"timestamp":1784271525493}}
{"type":"message","id":"e200ac38","parentId":"71bcc521","timestamp":"2026-07-17T06:58:45.512Z","message":{"role":"toolResult","toolCallId":"call_8xCrhJfQtJ65faZXRSBeDrBH|fc_089095e18118ee7d016a59d2a54ffc8190a0fcec89b0ed4a04","toolName":"read","content":[{"type":"text","text":"pub struct NetConfig {\n…\n    /// Live source of this node's subnet credentials for the connect-time\n    /// seed-proof (Mesh-D2, REQ-MESH-1). `Some` enables the mutual proof at\n    /// every dial + accept (fail → drop); `None` disables it entirely — both\n    /// peers must agree, or the side expecting the control stream would hang.\n    /// Production wires `Some(SubnetStore::load …)`; mechanics-only tests that\n    /// don't exercise membership pass `None`.\n    pub membership: Option<MembershipSource>,\n}\n\n/// Default bounded stream-read ring depth (chunks) — same sizing rationale as\n/// the PTY `OutputLog`: large enough that a brain-restart window never evicts\n/// unreplayed chunks in practice; bounded so an unsubscribed stream can't\n/// buffer without limit.\nconst DEFAULT_STREAM_RING_CHUNKS: usize = 4096;\n\n/// Max bytes pulled per stream read (one ring chunk).\nconst STREAM_READ_CHUNK: usize = 64 * 1024;\n\n/// Max bytes buffered while hunting the opener's first newline (ADR-0038 B).\n/// A protocol opener is one small NDJSON line; a stream whose first 64KiB has\n/// no newline is not speaking an NDJSON protocol — capture gives up\n/// (`Oversize`) and classification treats it as unclassifiable, bounded.\nconst OPENER_PIN_MAX: usize = 64 * 1024;\n\n/// How many frames a subscriber's writer queue holds before the subscriber is\n/// judged hopelessly behind and DETACHED (ADR-0038 Amendment fix 3): overflow\n/// is a subscriber problem, never a producer stall — the detached brain\n/// resumes from its cursor (the existing gapless-resume contract).\nconst SUBSCRIBER_QUEUE_CAP: usize = 1024;\n\n/// Why a subscriber seat rejected a frame — the caller removes the seat on\n/// either, but only a POISON (real write failure on the conn) cancels the\n/// stream's lease; an overflow is a slow-but-healthy brain being detached.\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum SeatFail {\n    /// The writer thread died on a failed conn write (the 15s bounded-write\n    /// poison class) — halt-at-first-failure already happened writer-side.\n    Poisoned,\n    /// The bounded queue is full — the subscriber cannot keep up.\n    Overflow,\n}\n\n/// One installed subscriber behind a bounded writer queue (ADR-0038 Amendment\n/// fixes 2+3, REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE): frames ENQUEUE under the\n/// log mutex; the conn I/O — the deadline-bounded `SharedSend::write` that can\n/// take 15s against a wedged conn — runs on this seat's OWN writer thread,\n/// OUTSIDE every lock. The field stall was exactly the old shape's serial\n/// replay-under-mutex against a poisoned conn (33 windows, all ~15s).\n///\n/// Writer discipline: the thread HALTS at the FIRST failed write (no\n/// discarded-errors iteration) and marks the seat poisoned; the next enqueue\n/// sees it and the caller removes the seat. Dropping the seat drops the queue\n/// sender — the writer drains what it holds and exits.\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\nstruct SubscriberSeat {\n    sub: SharedSend,\n    tx: std::sync::mpsc::SyncSender<Envelope>,\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\n    /// and a replacement may install (the no-reinstall-until-gone rule,\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\n    done: Arc<std::sync::atomic::AtomicBool>,\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\n}\n\nimpl SubscriberSeat {\n    /// Install a seat: spawn its writer thread and hand back the handle.\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\n    /// touching the live queue, so replay-then-live seq order holds by\n    /// construction even while producers enqueue concurrently. (Gate follow-\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\n    /// the mutex — a concurrent append could slot a live frame into the\n    /// middle of the replay and break the brain's strict gap check: the xfer\n    /// re-serve \"got seq 134 want 6\" CI red.)\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\n    /// a traffic-quiet stream where no producer enqueue would ever run\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\n    /// left the lease alive until the next append; the T4 forwarder starved).\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\n    /// displaced — is harmless by construction: every install mints a fresh\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn install(\n        sub: SharedSend,\n        replay: Vec<Envelope>,\n        lease: Option<Arc<StreamLease>>,\n    ) -> SubscriberSeat {\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\n        let writer_sub = Arc::clone(&sub);\n        let writer_done = Arc::clone(&done);\n        let writer_poisoned = Arc::clone(&poisoned);\n        std::thread::spawn(move || {\n            let poison = |halt_reason: &str| {\n                writer_poisoned.store(true, Ordering::Release);\n                if let Some(l) = &lease {\n                    l.cancel();\n                }\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\n            };\n            // Replay first — halt at the first failed write (Amendment\n            // fix 2), never iterate past a poisoned conn.\n            for frame in replay {\n                if writer_sub.write(&frame).is_err() {\n                    poison(\"replay write failed\");\n                    writer_done.store(true, Ordering::Release);\n                    return;\n                }\n            }\n            // Then live frames, same halt discipline.\n            while let Ok(frame) = rx.recv() {\n                if writer_sub.write(&frame).is_err() {\n                    poison(\"live write failed\");\n                    break;\n                }\n            }\n            writer_done.store(true, Ordering::Release);\n        });\n        SubscriberSeat {\n            sub,\n            tx,\n            done,\n            poisoned,\n        }\n    }\n\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\n    /// push). `Err` means the seat must be removed.\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\n        if self.poisoned.load(Ordering::Acquire) {\n            return Err(SeatFail::Poisoned);\n        }\n        match self.tx.try_send(frame) {\n            Ok(()) => Ok(()),\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\n            Err(std::sync::mpsc::TrySendError::Disconnected(_)) => Err(SeatFail::Poisoned),\n        }\n    }\n\n    /// Whether this seat belongs to `sub` (detach correlation).\n    fn is(&self, sub: &SharedSend) -> bool {\n        Arc::ptr_eq(&self.sub, sub)\n    }\n\n    /// Whether the writer thread has fully exited — the reinstall gate.\n    fn gone(&self) -> bool {\n        self.done.load(Ordering::Acquire)\n    }\n}\n\n/// A stream's serve lease (ADR-0038 Amendment fix 4; built here so the\n/// DAEMON-LIFECYCLE C2 work consumes it rather than growing a second\n/// mechanism): canceled when the stream's subscriber seat POISONS, which\n/// makes every later `send_stream` on the stream refuse — the paired\n/// serve/wire worker discovers the cancellation at its next forward and\n/// terminates instead of feeding an orphan leg forever.\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n#[derive(Default)]\npub struct StreamLease {\n    canceled: std::sync::atomic::AtomicBool,\n}\n\nimpl StreamLease {\n    /// Cancel the lease (subscriber poison; C2 will add its own callers).\n    pub fn cancel(&self) {\n        self.canceled.store(true, Ordering::Release);\n    }\n\n    /// Whether the lease was canceled.\n    pub fn is_canceled(&self) -> bool {\n        self.canceled.load(Ordering::Acquire)\n    }\n}\n\n/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\n/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\n/// ring and held until the stream row goes away. Recovery classifies from\n/// this fact, never from ring seq 0 (which a bounded ring evicts under\n/// traffic — the abandoned-active-stream leg of the hertz RCA).\nenum OpenerCapture {\n    /// Still hunting the first `\\n` — holds every byte seen so far.\n    Buffering(Vec<u8>),\n    /// The complete first line (newline excluded), immutable from here on.\n    Pinned(Vec<u8>),\n    /// No newline within [`OPENER_PIN_MAX`] — not an NDJSON opener; give up\n    /// bounded (classification reads this as \"no opener\").\n    Oversize,\n}\n\n/// Transport backing a held connection. A real `Quic` connection (the only\n/// kind an inbound handshake can produce), or a broker-minted `Loopback` —\n/// an in-process connection with NO iroh handshake whose identity IS this node,\n/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side\n/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake\n/// (a remote always presents its own key and cannot claim to be us). This is\n/// what lets a LOCAL `spt rc` ride the byte-identical `serve_attach` pump (M12\n/// W1.5, B1) — QUIC self-dial is refused by iroh (\"Connecting to ourself is not\n/// supported\"), so local attach rides a broker loopback conn instead.\nenum ConnKind {\n    /// A real QUIC connection — kept alive by ownership here; dropping it closes\n    /// the connection. Cloned (cheap handle) for stream opens.\n    Quic(Connection),\n    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\n    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n    /// there is no accept loop and no closed-watcher (it lives for the host).\n    Loopback,\n}\n\n/// One live connection the broker holds (QUIC or loopback).\nstruct ConnEntry {\n    /// The transport backing this connection.\n    kind: ConnKind,\n    /// The remote node's stable name (hex pubkey). For QUIC, proven by the\n    /// handshake; for loopback, THIS node's own id (the broker-minted identity).\n    remote_id_hex: String,\n    /// The subnets the peer proved current-epoch seed-knowledge of at connect\n    /// (Mesh-D2, REQ-MESH-1). Empty when seed-proof is disabled (`membership:\n    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;\n    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run\n    /// in parallel — belt + suspenders, no regression).\n    proven_subnets: HashSet<String>,\n}\n\n/// A bounded, sequenced read log for one QUIC stream — the PTY `OutputLog`\n/// discipline applied to the network (D4b): the stream's single read pump is\n/// the only producer; a brain connection attaches as the subscriber; every\n/// subscriber send happens under the enclosing `Mutex`, so live chunks and a\n/// replay can never interleave out of `seq` order. Chunks produced while no\n/// brain is attached sit in the ring and replay on the next subscribe — a\n/// brain restart loses nothing (Spike #3 invariant B/D).\n// [impl->REQ-DAEMON-2]\nstruct StreamLog {\n    stream_id: u64,\n    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n    /// count — QUIC may coalesce several sender writes into one receiver\n    /// `read_chunk` (or split one), so `next_seq` can be < the number of logical\n    /// chunks the sender issued for the SAME bytes. Correctness (byte-exact +\n    /// exactly-once) holds via these transport seqs + the recv cursor regardless;\n    /// any test asserting `next_seq == logical-chunk-count` is wrong (the Windows\n    /// coalescing flake) and must gate on BYTES instead.\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    /// The installed subscriber, behind its bounded writer queue (ADR-0038\n    /// Amendment fixes 2+3): producers ENQUEUE here; conn I/O never runs\n    /// under this log's mutex.\n    subscriber: Option<SubscriberSeat>,\n    /// The stream's serve lease (Amendment fix 4): canceled on subscriber\n    /// POISON so the paired serve/wire worker's sends refuse.\n    lease: Arc<StreamLease>,\n    /// Read side finished cleanly (peer called finish / conn ended).\n    finished: bool,\n    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must\n    /// NOT drop bytes — CONTEXT:262 \"tunneled protocols cannot drop frames, so\n    /// congestion surfaces as lag, never loss.\" So a retentive log never cap-evicts\n    /// on append (no loss), and the read pump REFUSES to pull the next chunk once\n    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the\n    /// transport's native flow-control then lags the *sender*. A chunk leaves the\n    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is\n    /// woken to resume via the entry's `room` notifier. Ordinary streams keep\n    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an\n    /// unbounded ring) and are drained by subscribe, never the sync cursor.\n    retentive_cap: usize,\n    /// The pinned first NDJSON line (ADR-0038 B) — one bounded side fact per\n    /// stream, independent of the ring's eviction. Fed by [`append`](Self::append).\n    opener: OpenerCapture,\n}\n\nimpl StreamLog {\n    fn new(stream_id: u64, cap_chunks: usize) -> Self {\n        StreamLog {\n            stream_id,\n            next_seq: 0,\n            ring: VecDeque::new(),\n            cap_chunks,\n            subscriber: None,\n            lease: Arc::new(StreamLease::default()),\n            finished: false,\n            retentive_cap: 0,\n            opener: OpenerCapture::Buffering(Vec::new()),\n        }\n    }\n\n    /// The pinned opener line, if the first `\\n` has arrived (ADR-0038 B).\n    /// `None` while still buffering or when capture gave up (`Oversize`).\n    fn opener_line(&self) -> Option<Vec<u8>> {\n        match &self.opener {\n            OpenerCapture::Pinned(line) => Some(line.clone()),\n            _ => None,\n        }\n    }\n\n    /// The oldest retained ring seq — the safe subscribe cursor after the ring\n    /// rolled (a `from_seq` below this floor makes the brain's gap check\n    /// reject the resume). `next_seq` when the ring is empty.\n    fn floor_seq(&self) -> u64 {\n        self.ring.front().map(|(s, _)| *s).unwrap_or(self.next_seq)\n    }\n\n    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.\n    fn set_retentive(&mut self, cap: usize) {\n        self.retentive_cap = cap;\n    }\n\n    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,\n    /// live-send to the current subscriber. Read-pump only. A non-retentive log\n    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log\n    /// never evicts — bytes leave only via [`drain`](Self::drain).\n    fn append(&mut self, bytes: &[u8]) {\n        // Opener capture (ADR-0038 B): pin the first NDJSON line as it streams\n        // past, before ring bounds can ever evict it. One-shot: `Pinned` and\n        // `Oversize` are terminal states.\n        // [impl->REQ-STREAM-OPENER-DURABLE]\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n                    buf.extend_from_slice(&bytes[..nl]);\n                    let line = std::mem::take(buf);\n                    self.opener = if line.len() > OPENER_PIN_MAX {\n                        OpenerCapture::Oversize\n                    } else {\n                        OpenerCapture::Pinned(line)\n                    };\n                }\n                None => {\n                    buf.extend_from_slice(bytes);\n                    if buf.len() > OPENER_PIN_MAX {\n                        self.opener = OpenerCapture::Oversize;\n                    }\n                }\n            }\n        }\n…\n    }\n…\n}\n\n[Showing lines 99-433 of 2865. Use :434 to continue]"}],"details":{"truncation":{"content":"    /// Live source of this node's subnet credentials for the connect-time\r\n    /// seed-proof (Mesh-D2, REQ-MESH-1). `Some` enables the mutual proof at\r\n    /// every dial + accept (fail → drop); `None` disables it entirely — both\r\n    /// peers must agree, or the side expecting the control stream would hang.\r\n    /// Production wires `Some(SubnetStore::load …)`; mechanics-only tests that\r\n    /// don't exercise membership pass `None`.\r\n    pub membership: Option<MembershipSource>,\r\n}\r\n\r\n/// Default bounded stream-read ring depth (chunks) — same sizing rationale as\r\n/// the PTY `OutputLog`: large enough that a brain-restart window never evicts\r\n/// unreplayed chunks in practice; bounded so an unsubscribed stream can't\r\n/// buffer without limit.\r\nconst DEFAULT_STREAM_RING_CHUNKS: usize = 4096;\r\n\r\n/// Max bytes pulled per stream read (one ring chunk).\r\nconst STREAM_READ_CHUNK: usize = 64 * 1024;\r\n\r\n/// Max bytes buffered while hunting the opener's first newline (ADR-0038 B).\r\n/// A protocol opener is one small NDJSON line; a stream whose first 64KiB has\r\n/// no newline is not speaking an NDJSON protocol — capture gives up\r\n/// (`Oversize`) and classification treats it as unclassifiable, bounded.\r\nconst OPENER_PIN_MAX: usize = 64 * 1024;\r\n\r\n/// How many frames a subscriber's writer queue holds before the subscriber is\r\n/// judged hopelessly behind and DETACHED (ADR-0038 Amendment fix 3): overflow\r\n/// is a subscriber problem, never a producer stall — the detached brain\r\n/// resumes from its cursor (the existing gapless-resume contract).\r\nconst SUBSCRIBER_QUEUE_CAP: usize = 1024;\r\n\r\n/// Why a subscriber seat rejected a frame — the caller removes the seat on\r\n/// either, but only a POISON (real write failure on the conn) cancels the\r\n/// stream's lease; an overflow is a slow-but-healthy brain being detached.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum SeatFail {\r\n    /// The writer thread died on a failed conn write (the 15s bounded-write\r\n    /// poison class) — halt-at-first-failure already happened writer-side.\r\n    Poisoned,\r\n    /// The bounded queue is full — the subscriber cannot keep up.\r\n    Overflow,\r\n}\r\n\r\n/// One installed subscriber behind a bounded writer queue (ADR-0038 Amendment\r\n/// fixes 2+3, REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE): frames ENQUEUE under the\r\n/// log mutex; the conn I/O — the deadline-bounded `SharedSend::write` that can\r\n/// take 15s against a wedged conn — runs on this seat's OWN writer thread,\r\n/// OUTSIDE every lock. The field stall was exactly the old shape's serial\r\n/// replay-under-mutex against a poisoned conn (33 windows, all ~15s).\r\n///\r\n/// Writer discipline: the thread HALTS at the FIRST failed write (no\r\n/// discarded-errors iteration) and marks the seat poisoned; the next enqueue\r\n/// sees it and the caller removes the seat. Dropping the seat drops the queue\r\n/// sender — the writer drains what it holds and exits.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\nstruct SubscriberSeat {\r\n    sub: SharedSend,\r\n    tx: std::sync::mpsc::SyncSender<Envelope>,\r\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\r\n    /// and a replacement may install (the no-reinstall-until-gone rule,\r\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\r\n    done: Arc<std::sync::atomic::AtomicBool>,\r\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\r\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\r\n}\r\n\r\nimpl SubscriberSeat {\r\n    /// Install a seat: spawn its writer thread and hand back the handle.\r\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\r\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\r\n    /// touching the live queue, so replay-then-live seq order holds by\r\n    /// construction even while producers enqueue concurrently. (Gate follow-\r\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\r\n    /// the mutex — a concurrent append could slot a live frame into the\r\n    /// middle of the replay and break the brain's strict gap check: the xfer\r\n    /// re-serve \"got seq 134 want 6\" CI red.)\r\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\r\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\r\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\r\n    /// a traffic-quiet stream where no producer enqueue would ever run\r\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\r\n    /// left the lease alive until the next append; the T4 forwarder starved).\r\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\r\n    /// displaced — is harmless by construction: every install mints a fresh\r\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn install(\r\n        sub: SharedSend,\r\n        replay: Vec<Envelope>,\r\n        lease: Option<Arc<StreamLease>>,\r\n    ) -> SubscriberSeat {\r\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\r\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let writer_sub = Arc::clone(&sub);\r\n        let writer_done = Arc::clone(&done);\r\n        let writer_poisoned = Arc::clone(&poisoned);\r\n        std::thread::spawn(move || {\r\n            let poison = |halt_reason: &str| {\r\n                writer_poisoned.store(true, Ordering::Release);\r\n                if let Some(l) = &lease {\r\n                    l.cancel();\r\n                }\r\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\r\n            };\r\n            // Replay first — halt at the first failed write (Amendment\r\n            // fix 2), never iterate past a poisoned conn.\r\n            for frame in replay {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"replay write failed\");\r\n                    writer_done.store(true, Ordering::Release);\r\n                    return;\r\n                }\r\n            }\r\n            // Then live frames, same halt discipline.\r\n            while let Ok(frame) = rx.recv() {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"live write failed\");\r\n                    break;\r\n                }\r\n            }\r\n            writer_done.store(true, Ordering::Release);\r\n        });\r\n        SubscriberSeat {\r\n            sub,\r\n            tx,\r\n            done,\r\n            poisoned,\r\n        }\r\n    }\r\n\r\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\r\n    /// push). `Err` means the seat must be removed.\r\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\r\n        if self.poisoned.load(Ordering::Acquire) {\r\n            return Err(SeatFail::Poisoned);\r\n        }\r\n        match self.tx.try_send(frame) {\r\n            Ok(()) => Ok(()),\r\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\r\n            Err(std::sync::mpsc::TrySendError::Disconnected(_)) => Err(SeatFail::Poisoned),\r\n        }\r\n    }\r\n\r\n    /// Whether this seat belongs to `sub` (detach correlation).\r\n    fn is(&self, sub: &SharedSend) -> bool {\r\n        Arc::ptr_eq(&self.sub, sub)\r\n    }\r\n\r\n    /// Whether the writer thread has fully exited — the reinstall gate.\r\n    fn gone(&self) -> bool {\r\n        self.done.load(Ordering::Acquire)\r\n    }\r\n}\r\n\r\n/// A stream's serve lease (ADR-0038 Amendment fix 4; built here so the\r\n/// DAEMON-LIFECYCLE C2 work consumes it rather than growing a second\r\n/// mechanism): canceled when the stream's subscriber seat POISONS, which\r\n/// makes every later `send_stream` on the stream refuse — the paired\r\n/// serve/wire worker discovers the cancellation at its next forward and\r\n/// terminates instead of feeding an orphan leg forever.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n#[derive(Default)]\r\npub struct StreamLease {\r\n    canceled: std::sync::atomic::AtomicBool,\r\n}\r\n\r\nimpl StreamLease {\r\n    /// Cancel the lease (subscriber poison; C2 will add its own callers).\r\n    pub fn cancel(&self) {\r\n        self.canceled.store(true, Ordering::Release);\r\n    }\r\n\r\n    /// Whether the lease was canceled.\r\n    pub fn is_canceled(&self) -> bool {\r\n        self.canceled.load(Ordering::Acquire)\r\n    }\r\n}\r\n\r\n/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\r\n/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\r\n/// ring and held until the stream row goes away. Recovery classifies from\r\n/// this fact, never from ring seq 0 (which a bounded ring evicts under\r\n/// traffic — the abandoned-active-stream leg of the hertz RCA).\r\nenum OpenerCapture {\r\n    /// Still hunting the first `\\n` — holds every byte seen so far.\r\n    Buffering(Vec<u8>),\r\n    /// The complete first line (newline excluded), immutable from here on.\r\n    Pinned(Vec<u8>),\r\n    /// No newline within [`OPENER_PIN_MAX`] — not an NDJSON opener; give up\r\n    /// bounded (classification reads this as \"no opener\").\r\n    Oversize,\r\n}\r\n\r\n/// Transport backing a held connection. A real `Quic` connection (the only\r\n/// kind an inbound handshake can produce), or a broker-minted `Loopback` —\r\n/// an in-process connection with NO iroh handshake whose identity IS this node,\r\n/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side\r\n/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake\r\n/// (a remote always presents its own key and cannot claim to be us). This is\r\n/// what lets a LOCAL `spt rc` ride the byte-identical `serve_attach` pump (M12\r\n/// W1.5, B1) — QUIC self-dial is refused by iroh (\"Connecting to ourself is not\r\n/// supported\"), so local attach rides a broker loopback conn instead.\r\nenum ConnKind {\r\n    /// A real QUIC connection — kept alive by ownership here; dropping it closes\r\n    /// the connection. Cloned (cheap handle) for stream opens.\r\n    Quic(Connection),\r\n    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\r\n    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\r\n    /// there is no accept loop and no closed-watcher (it lives for the host).\r\n    Loopback,\r\n}\r\n\r\n/// One live connection the broker holds (QUIC or loopback).\r\nstruct ConnEntry {\r\n    /// The transport backing this connection.\r\n    kind: ConnKind,\r\n    /// The remote node's stable name (hex pubkey). For QUIC, proven by the\r\n    /// handshake; for loopback, THIS node's own id (the broker-minted identity).\r\n    remote_id_hex: String,\r\n    /// The subnets the peer proved current-epoch seed-knowledge of at connect\r\n    /// (Mesh-D2, REQ-MESH-1). Empty when seed-proof is disabled (`membership:\r\n    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;\r\n    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run\r\n    /// in parallel — belt + suspenders, no regression).\r\n    proven_subnets: HashSet<String>,\r\n}\r\n\r\n/// A bounded, sequenced read log for one QUIC stream — the PTY `OutputLog`\r\n/// discipline applied to the network (D4b): the stream's single read pump is\r\n/// the only producer; a brain connection attaches as the subscriber; every\r\n/// subscriber send happens under the enclosing `Mutex`, so live chunks and a\r\n/// replay can never interleave out of `seq` order. Chunks produced while no\r\n/// brain is attached sit in the ring and replay on the next subscribe — a\r\n/// brain restart loses nothing (Spike #3 invariant B/D).\r\n// [impl->REQ-DAEMON-2]\r\nstruct StreamLog {\r\n    stream_id: u64,\r\n    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\r\n    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\r\n    /// count — QUIC may coalesce several sender writes into one receiver\r\n    /// `read_chunk` (or split one), so `next_seq` can be < the number of logical\r\n    /// chunks the sender issued for the SAME bytes. Correctness (byte-exact +\r\n    /// exactly-once) holds via these transport seqs + the recv cursor regardless;\r\n    /// any test asserting `next_seq == logical-chunk-count` is wrong (the Windows\r\n    /// coalescing flake) and must gate on BYTES instead.\r\n    next_seq: u64,\r\n    ring: VecDeque<(u64, Vec<u8>)>,\r\n    cap_chunks: usize,\r\n    /// The installed subscriber, behind its bounded writer queue (ADR-0038\r\n    /// Amendment fixes 2+3): producers ENQUEUE here; conn I/O never runs\r\n    /// under this log's mutex.\r\n    subscriber: Option<SubscriberSeat>,\r\n    /// The stream's serve lease (Amendment fix 4): canceled on subscriber\r\n    /// POISON so the paired serve/wire worker's sends refuse.\r\n    lease: Arc<StreamLease>,\r\n    /// Read side finished cleanly (peer called finish / conn ended).\r\n    finished: bool,\r\n    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\r\n    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must\r\n    /// NOT drop bytes — CONTEXT:262 \"tunneled protocols cannot drop frames, so\r\n    /// congestion surfaces as lag, never loss.\" So a retentive log never cap-evicts\r\n    /// on append (no loss), and the read pump REFUSES to pull the next chunk once\r\n    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the\r\n    /// transport's native flow-control then lags the *sender*. A chunk leaves the\r\n    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is\r\n    /// woken to resume via the entry's `room` notifier. Ordinary streams keep\r\n    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an\r\n    /// unbounded ring) and are drained by subscribe, never the sync cursor.\r\n    retentive_cap: usize,\r\n    /// The pinned first NDJSON line (ADR-0038 B) — one bounded side fact per\r\n    /// stream, independent of the ring's eviction. Fed by [`append`](Self::append).\r\n    opener: OpenerCapture,\r\n}\r\n\r\nimpl StreamLog {\r\n    fn new(stream_id: u64, cap_chunks: usize) -> Self {\r\n        StreamLog {\r\n            stream_id,\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_chunks,\r\n            subscriber: None,\r\n            lease: Arc::new(StreamLease::default()),\r\n            finished: false,\r\n            retentive_cap: 0,\r\n            opener: OpenerCapture::Buffering(Vec::new()),\r\n        }\r\n    }\r\n\r\n    /// The pinned opener line, if the first `\\n` has arrived (ADR-0038 B).\r\n    /// `None` while still buffering or when capture gave up (`Oversize`).\r\n    fn opener_line(&self) -> Option<Vec<u8>> {\r\n        match &self.opener {\r\n            OpenerCapture::Pinned(line) => Some(line.clone()),\r\n            _ => None,\r\n        }\r\n    }\r\n\r\n    /// The oldest retained ring seq — the safe subscribe cursor after the ring\r\n    /// rolled (a `from_seq` below this floor makes the brain's gap check\r\n    /// reject the resume). `next_seq` when the ring is empty.\r\n    fn floor_seq(&self) -> u64 {\r\n        self.ring.front().map(|(s, _)| *s).unwrap_or(self.next_seq)\r\n    }\r\n\r\n    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.\r\n    fn set_retentive(&mut self, cap: usize) {\r\n        self.retentive_cap = cap;\r\n    }\r\n\r\n    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,\r\n    /// live-send to the current subscriber. Read-pump only. A non-retentive log\r\n    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log\r\n    /// never evicts — bytes leave only via [`drain`](Self::drain).\r\n    fn append(&mut self, bytes: &[u8]) {\r\n        // Opener capture (ADR-0038 B): pin the first NDJSON line as it streams\r\n        // past, before ring bounds can ever evict it. One-shot: `Pinned` and\r\n        // `Oversize` are terminal states.\r\n        // [impl->REQ-STREAM-OPENER-DURABLE]\r\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\r\n            match bytes.iter().position(|&b| b == b'\\n') {\r\n                Some(nl) => {\r\n                    buf.extend_from_slice(&bytes[..nl]);\r\n                    let line = std::mem::take(buf);\r\n                    self.opener = if line.len() > OPENER_PIN_MAX {\r\n                        OpenerCapture::Oversize\r\n                    } else {\r\n                        OpenerCapture::Pinned(line)\r\n                    };\r\n                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r","truncated":true,"truncatedBy":"lines","totalLines":2767,"totalBytes":16701,"outputLines":335,"outputBytes":16701,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub struct NetConfig {\n…\n    /// Live source of this node's subnet credentials for the connect-time\r\n    /// seed-proof (Mesh-D2, REQ-MESH-1). `Some` enables the mutual proof at\r\n    /// every dial + accept (fail → drop); `None` disables it entirely — both\r\n    /// peers must agree, or the side expecting the control stream would hang.\r\n    /// Production wires `Some(SubnetStore::load …)`; mechanics-only tests that\r\n    /// don't exercise membership pass `None`.\r\n    pub membership: Option<MembershipSource>,\r\n}\r\n\r\n/// Default bounded stream-read ring depth (chunks) — same sizing rationale as\r\n/// the PTY `OutputLog`: large enough that a brain-restart window never evicts\r\n/// unreplayed chunks in practice; bounded so an unsubscribed stream can't\r\n/// buffer without limit.\r\nconst DEFAULT_STREAM_RING_CHUNKS: usize = 4096;\r\n\r\n/// Max bytes pulled per stream read (one ring chunk).\r\nconst STREAM_READ_CHUNK: usize = 64 * 1024;\r\n\r\n/// Max bytes buffered while hunting the opener's first newline (ADR-0038 B).\r\n/// A protocol opener is one small NDJSON line; a stream whose first 64KiB has\r\n/// no newline is not speaking an NDJSON protocol — capture gives up\r\n/// (`Oversize`) and classification treats it as unclassifiable, bounded.\r\nconst OPENER_PIN_MAX: usize = 64 * 1024;\r\n\r\n/// How many frames a subscriber's writer queue holds before the subscriber is\r\n/// judged hopelessly behind and DETACHED (ADR-0038 Amendment fix 3): overflow\r\n/// is a subscriber problem, never a producer stall — the detached brain\r\n/// resumes from its cursor (the existing gapless-resume contract).\r\nconst SUBSCRIBER_QUEUE_CAP: usize = 1024;\r\n\r\n/// Why a subscriber seat rejected a frame — the caller removes the seat on\r\n/// either, but only a POISON (real write failure on the conn) cancels the\r\n/// stream's lease; an overflow is a slow-but-healthy brain being detached.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum SeatFail {\r\n    /// The writer thread died on a failed conn write (the 15s bounded-write\r\n    /// poison class) — halt-at-first-failure already happened writer-side.\r\n    Poisoned,\r\n    /// The bounded queue is full — the subscriber cannot keep up.\r\n    Overflow,\r\n}\r\n\r\n/// One installed subscriber behind a bounded writer queue (ADR-0038 Amendment\r\n/// fixes 2+3, REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE): frames ENQUEUE under the\r\n/// log mutex; the conn I/O — the deadline-bounded `SharedSend::write` that can\r\n/// take 15s against a wedged conn — runs on this seat's OWN writer thread,\r\n/// OUTSIDE every lock. The field stall was exactly the old shape's serial\r\n/// replay-under-mutex against a poisoned conn (33 windows, all ~15s).\r\n///\r\n/// Writer discipline: the thread HALTS at the FIRST failed write (no\r\n/// discarded-errors iteration) and marks the seat poisoned; the next enqueue\r\n/// sees it and the caller removes the seat. Dropping the seat drops the queue\r\n/// sender — the writer drains what it holds and exits.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\nstruct SubscriberSeat {\r\n    sub: SharedSend,\r\n    tx: std::sync::mpsc::SyncSender<Envelope>,\r\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\r\n    /// and a replacement may install (the no-reinstall-until-gone rule,\r\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\r\n    done: Arc<std::sync::atomic::AtomicBool>,\r\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\r\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\r\n}\r\n\r\nimpl SubscriberSeat {\r\n    /// Install a seat: spawn its writer thread and hand back the handle.\r\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\r\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\r\n    /// touching the live queue, so replay-then-live seq order holds by\r\n    /// construction even while producers enqueue concurrently. (Gate follow-\r\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\r\n    /// the mutex — a concurrent append could slot a live frame into the\r\n    /// middle of the replay and break the brain's strict gap check: the xfer\r\n    /// re-serve \"got seq 134 want 6\" CI red.)\r\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\r\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\r\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\r\n    /// a traffic-quiet stream where no producer enqueue would ever run\r\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\r\n    /// left the lease alive until the next append; the T4 forwarder starved).\r\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\r\n    /// displaced — is harmless by construction: every install mints a fresh\r\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn install(\r\n        sub: SharedSend,\r\n        replay: Vec<Envelope>,\r\n        lease: Option<Arc<StreamLease>>,\r\n    ) -> SubscriberSeat {\r\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\r\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let writer_sub = Arc::clone(&sub);\r\n        let writer_done = Arc::clone(&done);\r\n        let writer_poisoned = Arc::clone(&poisoned);\r\n        std::thread::spawn(move || {\r\n            let poison = |halt_reason: &str| {\r\n                writer_poisoned.store(true, Ordering::Release);\r\n                if let Some(l) = &lease {\r\n                    l.cancel();\r\n                }\r\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\r\n            };\r\n            // Replay first — halt at the first failed write (Amendment\r\n            // fix 2), never iterate past a poisoned conn.\r\n            for frame in replay {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"replay write failed\");\r\n                    writer_done.store(true, Ordering::Release);\r\n                    return;\r\n                }\r\n            }\r\n            // Then live frames, same halt discipline.\r\n            while let Ok(frame) = rx.recv() {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"live write failed\");\r\n                    break;\r\n                }\r\n            }\r\n            writer_done.store(true, Ordering::Release);\r\n        });\r\n        SubscriberSeat {\r\n            sub,\r\n            tx,\r\n            done,\r\n            poisoned,\r\n        }\r\n    }\r\n\r\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\r\n    /// push). `Err` means the seat must be removed.\r\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\r\n        if self.poisoned.load(Ordering::Acquire) {\r\n            return Err(SeatFail::Poisoned);\r\n        }\r\n        match self.tx.try_send(frame) {\r\n            Ok(()) => Ok(()),\r\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\r\n            Err(std::sync::mpsc::TrySendError::Disconnected(_)) => Err(SeatFail::Poisoned),\r\n        }\r\n    }\r\n\r\n    /// Whether this seat belongs to `sub` (detach correlation).\r\n    fn is(&self, sub: &SharedSend) -> bool {\r\n        Arc::ptr_eq(&self.sub, sub)\r\n    }\r\n\r\n    /// Whether the writer thread has fully exited — the reinstall gate.\r\n    fn gone(&self) -> bool {\r\n        self.done.load(Ordering::Acquire)\r\n    }\r\n}\r\n\r\n/// A stream's serve lease (ADR-0038 Amendment fix 4; built here so the\r\n/// DAEMON-LIFECYCLE C2 work consumes it rather than growing a second\r\n/// mechanism): canceled when the stream's subscriber seat POISONS, which\r\n/// makes every later `send_stream` on the stream refuse — the paired\r\n/// serve/wire worker discovers the cancellation at its next forward and\r\n/// terminates instead of feeding an orphan leg forever.\r\n// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n#[derive(Default)]\r\npub struct StreamLease {\r\n    canceled: std::sync::atomic::AtomicBool,\r\n}\r\n\r\nimpl StreamLease {\r\n    /// Cancel the lease (subscriber poison; C2 will add its own callers).\r\n    pub fn cancel(&self) {\r\n        self.canceled.store(true, Ordering::Release);\r\n    }\r\n\r\n    /// Whether the lease was canceled.\r\n    pub fn is_canceled(&self) -> bool {\r\n        self.canceled.load(Ordering::Acquire)\r\n    }\r\n}\r\n\r\n/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\r\n/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\r\n/// ring and held until the stream row goes away. Recovery classifies from\r\n/// this fact, never from ring seq 0 (which a bounded ring evicts under\r\n/// traffic — the abandoned-active-stream leg of the hertz RCA).\r\nenum OpenerCapture {\r\n    /// Still hunting the first `\\n` — holds every byte seen so far.\r\n    Buffering(Vec<u8>),\r\n    /// The complete first line (newline excluded), immutable from here on.\r\n    Pinned(Vec<u8>),\r\n    /// No newline within [`OPENER_PIN_MAX`] — not an NDJSON opener; give up\r\n    /// bounded (classification reads this as \"no opener\").\r\n    Oversize,\r\n}\r\n\r\n/// Transport backing a held connection. A real `Quic` connection (the only\r\n/// kind an inbound handshake can produce), or a broker-minted `Loopback` —\r\n/// an in-process connection with NO iroh handshake whose identity IS this node,\r\n/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side\r\n/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake\r\n/// (a remote always presents its own key and cannot claim to be us). This is\r\n/// what lets a LOCAL `spt rc` ride the byte-identical `serve_attach` pump (M12\r\n/// W1.5, B1) — QUIC self-dial is refused by iroh (\"Connecting to ourself is not\r\n/// supported\"), so local attach rides a broker loopback conn instead.\r\nenum ConnKind {\r\n    /// A real QUIC connection — kept alive by ownership here; dropping it closes\r\n    /// the connection. Cloned (cheap handle) for stream opens.\r\n    Quic(Connection),\r\n    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\r\n    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\r\n    /// there is no accept loop and no closed-watcher (it lives for the host).\r\n    Loopback,\r\n}\r\n\r\n/// One live connection the broker holds (QUIC or loopback).\r\nstruct ConnEntry {\r\n    /// The transport backing this connection.\r\n    kind: ConnKind,\r\n    /// The remote node's stable name (hex pubkey). For QUIC, proven by the\r\n    /// handshake; for loopback, THIS node's own id (the broker-minted identity).\r\n    remote_id_hex: String,\r\n    /// The subnets the peer proved current-epoch seed-knowledge of at connect\r\n    /// (Mesh-D2, REQ-MESH-1). Empty when seed-proof is disabled (`membership:\r\n    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;\r\n    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run\r\n    /// in parallel — belt + suspenders, no regression).\r\n    proven_subnets: HashSet<String>,\r\n}\r\n\r\n/// A bounded, sequenced read log for one QUIC stream — the PTY `OutputLog`\r\n/// discipline applied to the network (D4b): the stream's single read pump is\r\n/// the only producer; a brain connection attaches as the subscriber; every\r\n/// subscriber send happens under the enclosing `Mutex`, so live chunks and a\r\n/// replay can never interleave out of `seq` order. Chunks produced while no\r\n/// brain is attached sit in the ring and replay on the next subscribe — a\r\n/// brain restart loses nothing (Spike #3 invariant B/D).\r\n// [impl->REQ-DAEMON-2]\r\nstruct StreamLog {\r\n    stream_id: u64,\r\n    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\r\n    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\r\n    /// count — QUIC may coalesce several sender writes into one receiver\r\n    /// `read_chunk` (or split one), so `next_seq` can be < the number of logical\r\n    /// chunks the sender issued for the SAME bytes. Correctness (byte-exact +\r\n    /// exactly-once) holds via these transport seqs + the recv cursor regardless;\r\n    /// any test asserting `next_seq == logical-chunk-count` is wrong (the Windows\r\n    /// coalescing flake) and must gate on BYTES instead.\r\n    next_seq: u64,\r\n    ring: VecDeque<(u64, Vec<u8>)>,\r\n    cap_chunks: usize,\r\n    /// The installed subscriber, behind its bounded writer queue (ADR-0038\r\n    /// Amendment fixes 2+3): producers ENQUEUE here; conn I/O never runs\r\n    /// under this log's mutex.\r\n    subscriber: Option<SubscriberSeat>,\r\n    /// The stream's serve lease (Amendment fix 4): canceled on subscriber\r\n    /// POISON so the paired serve/wire worker's sends refuse.\r\n    lease: Arc<StreamLease>,\r\n    /// Read side finished cleanly (peer called finish / conn ended).\r\n    finished: bool,\r\n    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\r\n    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must\r\n    /// NOT drop bytes — CONTEXT:262 \"tunneled protocols cannot drop frames, so\r\n    /// congestion surfaces as lag, never loss.\" So a retentive log never cap-evicts\r\n    /// on append (no loss), and the read pump REFUSES to pull the next chunk once\r\n    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the\r\n    /// transport's native flow-control then lags the *sender*. A chunk leaves the\r\n    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is\r\n    /// woken to resume via the entry's `room` notifier. Ordinary streams keep\r\n    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an\r\n    /// unbounded ring) and are drained by subscribe, never the sync cursor.\r\n    retentive_cap: usize,\r\n    /// The pinned first NDJSON line (ADR-0038 B) — one bounded side fact per\r\n    /// stream, independent of the ring's eviction. Fed by [`append`](Self::append).\r\n    opener: OpenerCapture,\r\n}\r\n\r\nimpl StreamLog {\r\n    fn new(stream_id: u64, cap_chunks: usize) -> Self {\r\n        StreamLog {\r\n            stream_id,\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_chunks,\r\n            subscriber: None,\r\n            lease: Arc::new(StreamLease::default()),\r\n            finished: false,\r\n            retentive_cap: 0,\r\n            opener: OpenerCapture::Buffering(Vec::new()),\r\n        }\r\n    }\r\n\r\n    /// The pinned opener line, if the first `\\n` has arrived (ADR-0038 B).\r\n    /// `None` while still buffering or when capture gave up (`Oversize`).\r\n    fn opener_line(&self) -> Option<Vec<u8>> {\r\n        match &self.opener {\r\n            OpenerCapture::Pinned(line) => Some(line.clone()),\r\n            _ => None,\r\n        }\r\n    }\r\n\r\n    /// The oldest retained ring seq — the safe subscribe cursor after the ring\r\n    /// rolled (a `from_seq` below this floor makes the brain's gap check\r\n    /// reject the resume). `next_seq` when the ring is empty.\r\n    fn floor_seq(&self) -> u64 {\r\n        self.ring.front().map(|(s, _)| *s).unwrap_or(self.next_seq)\r\n    }\r\n\r\n    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.\r\n    fn set_retentive(&mut self, cap: usize) {\r\n        self.retentive_cap = cap;\r\n    }\r\n\r\n    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,\r\n    /// live-send to the current subscriber. Read-pump only. A non-retentive log\r\n    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log\r\n    /// never evicts — bytes leave only via [`drain`](Self::drain).\r\n    fn append(&mut self, bytes: &[u8]) {\r\n        // Opener capture (ADR-0038 B): pin the first NDJSON line as it streams\r\n        // past, before ring bounds can ever evict it. One-shot: `Pinned` and\r\n        // `Oversize` are terminal states.\r\n        // [impl->REQ-STREAM-OPENER-DURABLE]\r\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\r\n            match bytes.iter().position(|&b| b == b'\\n') {\r\n                Some(nl) => {\r\n                    buf.extend_from_slice(&bytes[..nl]);\r\n                    let line = std::mem::take(buf);\r\n                    self.opener = if line.len() > OPENER_PIN_MAX {\r\n                        OpenerCapture::Oversize\r\n                    } else {\r\n                        OpenerCapture::Pinned(line)\r\n                    };\r\n                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r\n                    }\n                }\n            }\n        }\n…\n    }\n…\n}","startLine":88,"lineNumbers":[88,null,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,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,null,456,null,587]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":16701,"outputLines":335,"outputBytes":16701,"shownRange":{"start":99,"end":433},"nextOffset":434}}},"isError":false,"timestamp":1784271525512}}
{"type":"message","id":"002e45a9","parentId":"e200ac38","timestamp":"2026-07-17T06:58:45.536Z","message":{"role":"toolResult","toolCallId":"call_EupbMqvbajeeWBMYg1Dc2Gfi|fc_089095e18118ee7d016a59d2a5500481908b10794f8a8546ce","toolName":"read","content":[{"type":"text","text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n…\n                }\n                None => {\n                    buf.extend_from_slice(bytes);\n                    if buf.len() > OPENER_PIN_MAX {\n                        self.opener = OpenerCapture::Oversize;\n                    }\n                }\n            }\n        }\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        if self.retentive_cap == 0 {\n            while self.ring.len() > self.cap_chunks {\n                self.ring.pop_front();\n            }\n        }\n        if let Some(seat) = &self.subscriber {\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\n            // the seat's writer thread, outside this mutex — a wedged\n            // subscriber can no longer park this producer OR the lock.\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n            if let Err(fail) = seat.enqueue(frame) {\n                self.drop_seat(fail);\n            }\n        }\n    }\n\n    /// Remove the current seat after an enqueue failure (halt-at-first-\n    /// failure, all producer sites): a POISON also cancels the stream's\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn drop_seat(&mut self, fail: SeatFail) {\n        if let Some(seat) = self.subscriber.take() {\n            seat.sub.lifecycle_event(\n                \"stream-sub-halt\",\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\n            );\n            if fail == SeatFail::Poisoned {\n                self.lease.cancel();\n            }\n        }\n    }\n\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\n    /// pending yet.\n    fn drain(&mut self) -> (Vec<u8>, bool) {\n        let mut out = Vec::new();\n        while let Some((_, chunk)) = self.ring.pop_front() {\n            out.extend_from_slice(&chunk);\n        }\n        (out, self.finished)\n    }\n\n    /// Mark the read side finished and tell the subscriber (with the final\n    /// cursor, so it can verify completeness).\n    fn finish(&mut self) {\n        self.finished = true;\n        if let Some(seat) = &self.subscriber {\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n            // Enqueue only — same discipline as append (Amendment fix 2:\n            // ALL producer sites halt-and-remove, finish included).\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n            if let Err(fail) = seat.enqueue(frame) {\n                self.drop_seat(fail);\n            }\n        }\n    }\n\n    /// Install `sub` as the subscriber with the replay snapshot (retained\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\n    /// replay-then-live seq order is structural (see\n    /// [`SubscriberSeat::install`]), while every conn write still happens\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\n    /// under the lock with discarded write errors, the exact serial-poison\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\n    ///\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\n    /// a replacement subscriber is never installed until the prior one is\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n        if let Some(prior) = &self.subscriber {\n            // No-reinstall-until-gone applies to the POISON class only\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\n            // 15s bounded write — installing a replacement now would hand the\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\n            // DISPLACED as before (the newer-brain-takes-over contract).\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\n                return Err(io::Error::new(\n                    io::ErrorKind::WouldBlock,\n                    format!(\n                        \"stream {} subscriber busy: prior subscriber still draining\",\n                        self.stream_id\n                    ),\n                ));\n            }\n            // Healthy displacement, prior fully gone, or same sub re-attach.\n            self.subscriber = None;\n        }\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\n        // record — this is one of the two RCA-pinned shared-carrier classes.\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\n        sub.lifecycle_event(\n            \"stream-sub-attach\",\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n        );\n        let mut replay: Vec<Envelope> = self\n            .ring\n            .iter()\n            .filter(|(s, _)| *s >= from_seq)\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\n            .collect();\n        if self.finished {\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\n        }\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\n        // prior writer — poisoned OR healthily displaced — holds only its own\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\n        // brain-swap displacement left the displaced writer and the successor\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\n        // write failure (its conn dying — every daemon refresh) canceled the\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\n        // displaced stream on exactly the post-refresh path this wave calms.\n        // One poison never dead-ends the stream (T5 recovery): the next\n        // generation's mint IS the renewal; `send_stream` reads this current\n        // Arc, old workers' handles stay theirs (C2 consumes via\n        // [`NetHost::stream_lease`]).\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        self.lease = Arc::new(StreamLease::default());\n        let replay_len = replay.len();\n        self.subscriber = Some(SubscriberSeat::install(\n            sub,\n            replay,\n            Some(Arc::clone(&self.lease)),\n        ));\n        Ok(replay_len)\n    }\n\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\n    /// not unsubscribe a newer brain).\n    fn detach_if(&mut self, sub: &SharedSend) {\n        if let Some(cur) = &self.subscriber {\n            if cur.is(sub) {\n                self.subscriber = None;\n            }\n        }\n    }\n}\n\n/// A bounded, sequenced log of connection liveness events — the broker-owned\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\n/// applied to the conn table itself. `register_conn` appends `connected`; each\n/// connection's closed-watcher appends `disconnected` after removing the row.\n/// A brain subscribes with a cursor and replays events produced while it was\n/// down, so presence survives a brain restart exactly like stream reads do.\n// [impl->REQ-EP-4]\nstruct PresenceLog {\n    next_seq: u64,\n    ring: VecDeque<NetPresenceEvent>,\n    cap_events: usize,\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\n    subscriber: Option<SubscriberSeat>,\n}\n\nimpl PresenceLog {\n    fn new(cap_events: usize) -> Self {\n        PresenceLog {\n            next_seq: 0,\n            ring: VecDeque::new(),\n            cap_events,\n            subscriber: None,\n        }\n    }\n\n    /// Append a liveness event: assign the next `seq`, retain it in the bounded\n    /// ring, live-send to the current subscriber. Producers: `register_conn`\n    /// (connected) and the per-conn closed-watchers (disconnected) — every\n    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive\n    /// in strict `seq` order.\n    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {\n        self.push(NetPresenceEvent {\n            seq: 0, // assigned in push\n            kind: kind.to_string(),\n            conn_id,\n            remote_id_hex: remote_id_hex.to_string(),\n            reason: None,\n            remote_addr: serde_json::Value::Null,\n        });\n    }\n\n    /// A `connected` event carrying the connection's OBSERVED remote address\n    /// (REQ-CONV-1): the async-dial write-back seed the blocking `net-dialed`\n    /// reply used to carry — now it rides the presence stream so the pump seeds\n    /// `peer-addrs.json` from a non-blocking dial the same way.\n    // [impl->REQ-CONV-1]\n    fn append_connected(&mut self, conn_id: u64, remote_id_hex: &str, remote_addr: serde_json::Value) {\n        self.push(NetPresenceEvent {\n            seq: 0,\n            kind: PRESENCE_CONNECTED.to_string(),\n            conn_id,\n            remote_id_hex: remote_id_hex.to_string(),\n            reason: None,\n            remote_addr,\n        });\n    }\n\n    /// A `dial-failed` event (PUMP-TRUTH W2): a non-blocking [`KIND_NET_DIAL_SUBMIT`]\n    /// dial failed. No connection opened → the documented `conn_id: 0` sentinel;\n    /// the pump correlates by `remote_id_hex` + `kind`, never `conn_id`.\n    // [impl->REQ-PUMP-PEER-ISOLATION]\n    fn append_dial_failed(&mut self, remote_id_hex: &str, reason: String) {\n        self.push(NetPresenceEvent {\n            seq: 0,\n            kind: PRESENCE_DIAL_FAILED.to_string(),\n            conn_id: 0,\n            remote_id_hex: remote_id_hex.to_string(),\n            reason: Some(reason),\n            remote_addr: serde_json::Value::Null,\n        });\n    }\n\n    /// Assign the next `seq`, retain in the bounded ring, live-send to the\n    /// current subscriber. Every append serializes on the enclosing `Mutex`, so\n    /// subscriber frames arrive in strict `seq` order.\n    fn push(&mut self, mut ev: NetPresenceEvent) {\n        ev.seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back(ev.clone());\n        while self.ring.len() > self.cap_events {\n            self.ring.pop_front();\n        }\n        if let Some(seat) = &self.subscriber {\n            let frame = net_presence_event_envelope(&ev);\n            // Enqueue only; halt-and-remove on failure — the PresenceLog\n            // mirror of the StreamLog discipline (Amendment fix 2).\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n            if let Err(fail) = seat.enqueue(frame) {\n                if let Some(seat) = self.subscriber.take() {\n                    seat.sub\n                        .lifecycle_event(\"presence-sub-halt\", &format!(\"reason={fail:?}\"));\n                }\n            }\n        }\n    }\n\n    /// Attach `sub` as the subscriber, replaying every retained event with\n    /// `seq >= from_seq` before live streaming (the gapless-resume attach).\n    /// The replay snapshot rides the seat's writer thread — replay-then-live\n    /// order structural, conn I/O outside this mutex — same discipline as\n    /// [`StreamLog::begin_attach`]. Returns the replay event count.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n        if let Some(prior) = &self.subscriber {\n            // Poison-class only (see StreamLog::begin_attach): a healthy\n            // prior presence subscriber is displaced by the newer brain.\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\n                return Err(io::Error::new(\n                    io::ErrorKind::WouldBlock,\n                    \"presence subscriber busy: prior subscriber still draining\",\n                ));\n            }\n            self.subscriber = None;\n        }\n        // Attribution (F-039 legs b+d): presence-subscriber role fact + attach\n        // record — the OTHER RCA-pinned shared carrier (the per-dial-failure\n        // presence push rides this conn). [impl->REQ-CONN-POISON-ATTRIBUTION]\n        sub.describe(\"presence-subscriber\");\n        sub.lifecycle_event(\"presence-sub-attach\", &format!(\"from_seq={from_seq}\"));\n        let replay: Vec<Envelope> = self\n            .ring\n            .iter()\n            .filter(|e| e.seq >= from_seq)\n            .map(net_presence_event_envelope)\n            .collect();\n        let replay_len = replay.len();\n        // No lease on the presence channel (it has no paired serve leg).\n        self.subscriber = Some(SubscriberSeat::install(sub, replay, None));\n        Ok(replay_len)\n    }\n\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\n    /// not unsubscribe a newer brain).\n    fn detach_if(&mut self, sub: &SharedSend) {\n        if let Some(cur) = &self.subscriber {\n            if cur.is(sub) {\n                self.subscriber = None;\n            }\n        }\n    }\n}\n\n/// A stream's send half — the same two transports as [`ConnKind`]. The\n/// `serve_attach`/operator pump and the `StreamLog` read discipline are\n/// IDENTICAL across both; only this leaf write differs (QUIC stream vs the\n/// write end of an in-process duplex). That is the M12 W1.5 one-pump invariant\n/// made structural: a loopback attach and a cross-node attach diverge at this\n/// leaf and nowhere above it.\nenum SendHalf {\n    Quic(SendStream),\n    Loopback(WriteHalf<DuplexStream>),\n}\n\nimpl SendHalf {\n    /// Write all `bytes` (no-op on empty). Runs on the host runtime.\n    async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {\n        match self {\n            SendHalf::Quic(s) => s\n                .write_all(bytes)\n                .await\n                .map_err(|e| io::Error::other(e.to_string())),\n            SendHalf::Loopback(w) => w.write_all(bytes).await,\n        }\n    }\n\n    /// Close the send side so the peer's read pump sees a clean end (QUIC\n    /// `finish` / duplex `shutdown`).\n    async fn finish(&mut self) -> io::Result<()> {\n        match self {\n            SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),\n            SendHalf::Loopback(w) => w.shutdown().await,\n        }\n    }\n}\n\n[Showing lines 429-763 of 2865. Use :764 to continue]"}],"details":{"truncation":{"content":"                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        let seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back((seq, bytes.to_vec()));\r\n        if self.retentive_cap == 0 {\r\n            while self.ring.len() > self.cap_chunks {\r\n                self.ring.pop_front();\r\n            }\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\r\n            // the seat's writer thread, outside this mutex — a wedged\r\n            // subscriber can no longer park this producer OR the lock.\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Remove the current seat after an enqueue failure (halt-at-first-\r\n    /// failure, all producer sites): a POISON also cancels the stream's\r\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\r\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn drop_seat(&mut self, fail: SeatFail) {\r\n        if let Some(seat) = self.subscriber.take() {\r\n            seat.sub.lifecycle_event(\r\n                \"stream-sub-halt\",\r\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\r\n            );\r\n            if fail == SeatFail::Poisoned {\r\n                self.lease.cancel();\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Enqueue only — same discipline as append (Amendment fix 2:\r\n            // ALL producer sites halt-and-remove, finish included).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Install `sub` as the subscriber with the replay snapshot (retained\r\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\r\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\r\n    /// replay-then-live seq order is structural (see\r\n    /// [`SubscriberSeat::install`]), while every conn write still happens\r\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\r\n    /// under the lock with discarded write errors, the exact serial-poison\r\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\r\n    ///\r\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\r\n    /// a replacement subscriber is never installed until the prior one is\r\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // No-reinstall-until-gone applies to the POISON class only\r\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\r\n            // 15s bounded write — installing a replacement now would hand the\r\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\r\n            // DISPLACED as before (the newer-brain-takes-over contract).\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    format!(\r\n                        \"stream {} subscriber busy: prior subscriber still draining\",\r\n                        self.stream_id\r\n                    ),\r\n                ));\r\n            }\r\n            // Healthy displacement, prior fully gone, or same sub re-attach.\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        let mut replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|(s, _)| *s >= from_seq)\r\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\r\n            .collect();\r\n        if self.finished {\r\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\r\n        }\r\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\r\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\r\n        // prior writer — poisoned OR healthily displaced — holds only its own\r\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\r\n        // brain-swap displacement left the displaced writer and the successor\r\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\r\n        // write failure (its conn dying — every daemon refresh) canceled the\r\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\r\n        // displaced stream on exactly the post-refresh path this wave calms.\r\n        // One poison never dead-ends the stream (T5 recovery): the next\r\n        // generation's mint IS the renewal; `send_stream` reads this current\r\n        // Arc, old workers' handles stay theirs (C2 consumes via\r\n        // [`NetHost::stream_lease`]).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        self.lease = Arc::new(StreamLease::default());\r\n        let replay_len = replay.len();\r\n        self.subscriber = Some(SubscriberSeat::install(\r\n            sub,\r\n            replay,\r\n            Some(Arc::clone(&self.lease)),\r\n        ));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r\n    cap_events: usize,\r\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\r\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\r\n    subscriber: Option<SubscriberSeat>,\r\n}\r\n\r\nimpl PresenceLog {\r\n    fn new(cap_events: usize) -> Self {\r\n        PresenceLog {\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_events,\r\n            subscriber: None,\r\n        }\r\n    }\r\n\r\n    /// Append a liveness event: assign the next `seq`, retain it in the bounded\r\n    /// ring, live-send to the current subscriber. Producers: `register_conn`\r\n    /// (connected) and the per-conn closed-watchers (disconnected) — every\r\n    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive\r\n    /// in strict `seq` order.\r\n    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0, // assigned in push\r\n            kind: kind.to_string(),\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: None,\r\n            remote_addr: serde_json::Value::Null,\r\n        });\r\n    }\r\n\r\n    /// A `connected` event carrying the connection's OBSERVED remote address\r\n    /// (REQ-CONV-1): the async-dial write-back seed the blocking `net-dialed`\r\n    /// reply used to carry — now it rides the presence stream so the pump seeds\r\n    /// `peer-addrs.json` from a non-blocking dial the same way.\r\n    // [impl->REQ-CONV-1]\r\n    fn append_connected(&mut self, conn_id: u64, remote_id_hex: &str, remote_addr: serde_json::Value) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0,\r\n            kind: PRESENCE_CONNECTED.to_string(),\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: None,\r\n            remote_addr,\r\n        });\r\n    }\r\n\r\n    /// A `dial-failed` event (PUMP-TRUTH W2): a non-blocking [`KIND_NET_DIAL_SUBMIT`]\r\n    /// dial failed. No connection opened → the documented `conn_id: 0` sentinel;\r\n    /// the pump correlates by `remote_id_hex` + `kind`, never `conn_id`.\r\n    // [impl->REQ-PUMP-PEER-ISOLATION]\r\n    fn append_dial_failed(&mut self, remote_id_hex: &str, reason: String) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0,\r\n            kind: PRESENCE_DIAL_FAILED.to_string(),\r\n            conn_id: 0,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: Some(reason),\r\n            remote_addr: serde_json::Value::Null,\r\n        });\r\n    }\r\n\r\n    /// Assign the next `seq`, retain in the bounded ring, live-send to the\r\n    /// current subscriber. Every append serializes on the enclosing `Mutex`, so\r\n    /// subscriber frames arrive in strict `seq` order.\r\n    fn push(&mut self, mut ev: NetPresenceEvent) {\r\n        ev.seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back(ev.clone());\r\n        while self.ring.len() > self.cap_events {\r\n            self.ring.pop_front();\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_presence_event_envelope(&ev);\r\n            // Enqueue only; halt-and-remove on failure — the PresenceLog\r\n            // mirror of the StreamLog discipline (Amendment fix 2).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                if let Some(seat) = self.subscriber.take() {\r\n                    seat.sub\r\n                        .lifecycle_event(\"presence-sub-halt\", &format!(\"reason={fail:?}\"));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the subscriber, replaying every retained event with\r\n    /// `seq >= from_seq` before live streaming (the gapless-resume attach).\r\n    /// The replay snapshot rides the seat's writer thread — replay-then-live\r\n    /// order structural, conn I/O outside this mutex — same discipline as\r\n    /// [`StreamLog::begin_attach`]. Returns the replay event count.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // Poison-class only (see StreamLog::begin_attach): a healthy\r\n            // prior presence subscriber is displaced by the newer brain.\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    \"presence subscriber busy: prior subscriber still draining\",\r\n                ));\r\n            }\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): presence-subscriber role fact + attach\r\n        // record — the OTHER RCA-pinned shared carrier (the per-dial-failure\r\n        // presence push rides this conn). [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(\"presence-subscriber\");\r\n        sub.lifecycle_event(\"presence-sub-attach\", &format!(\"from_seq={from_seq}\"));\r\n        let replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|e| e.seq >= from_seq)\r\n            .map(net_presence_event_envelope)\r\n            .collect();\r\n        let replay_len = replay.len();\r\n        // No lease on the presence channel (it has no paired serve leg).\r\n        self.subscriber = Some(SubscriberSeat::install(sub, replay, None));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A stream's send half — the same two transports as [`ConnKind`]. The\r\n/// `serve_attach`/operator pump and the `StreamLog` read discipline are\r\n/// IDENTICAL across both; only this leaf write differs (QUIC stream vs the\r\n/// write end of an in-process duplex). That is the M12 W1.5 one-pump invariant\r\n/// made structural: a loopback attach and a cross-node attach diverge at this\r\n/// leaf and nowhere above it.\r\nenum SendHalf {\r\n    Quic(SendStream),\r\n    Loopback(WriteHalf<DuplexStream>),\r\n}\r\n\r\nimpl SendHalf {\r\n    /// Write all `bytes` (no-op on empty). Runs on the host runtime.\r\n    async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {\r\n        match self {\r\n            SendHalf::Quic(s) => s\r\n                .write_all(bytes)\r\n                .await\r\n                .map_err(|e| io::Error::other(e.to_string())),\r\n            SendHalf::Loopback(w) => w.write_all(bytes).await,\r\n        }\r\n    }\r\n\r\n    /// Close the send side so the peer's read pump sees a clean end (QUIC\r\n    /// `finish` / duplex `shutdown`).\r\n    async fn finish(&mut self) -> io::Result<()> {\r\n        match self {\r\n            SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),\r\n            SendHalf::Loopback(w) => w.shutdown().await,\r\n        }\r\n    }\r\n}\r","truncated":true,"truncatedBy":"lines","totalLines":2437,"totalBytes":15283,"outputLines":335,"outputBytes":15283,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n…\n                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        let seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back((seq, bytes.to_vec()));\r\n        if self.retentive_cap == 0 {\r\n            while self.ring.len() > self.cap_chunks {\r\n                self.ring.pop_front();\r\n            }\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\r\n            // the seat's writer thread, outside this mutex — a wedged\r\n            // subscriber can no longer park this producer OR the lock.\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Remove the current seat after an enqueue failure (halt-at-first-\r\n    /// failure, all producer sites): a POISON also cancels the stream's\r\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\r\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn drop_seat(&mut self, fail: SeatFail) {\r\n        if let Some(seat) = self.subscriber.take() {\r\n            seat.sub.lifecycle_event(\r\n                \"stream-sub-halt\",\r\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\r\n            );\r\n            if fail == SeatFail::Poisoned {\r\n                self.lease.cancel();\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Enqueue only — same discipline as append (Amendment fix 2:\r\n            // ALL producer sites halt-and-remove, finish included).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Install `sub` as the subscriber with the replay snapshot (retained\r\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\r\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\r\n    /// replay-then-live seq order is structural (see\r\n    /// [`SubscriberSeat::install`]), while every conn write still happens\r\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\r\n    /// under the lock with discarded write errors, the exact serial-poison\r\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\r\n    ///\r\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\r\n    /// a replacement subscriber is never installed until the prior one is\r\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // No-reinstall-until-gone applies to the POISON class only\r\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\r\n            // 15s bounded write — installing a replacement now would hand the\r\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\r\n            // DISPLACED as before (the newer-brain-takes-over contract).\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    format!(\r\n                        \"stream {} subscriber busy: prior subscriber still draining\",\r\n                        self.stream_id\r\n                    ),\r\n                ));\r\n            }\r\n            // Healthy displacement, prior fully gone, or same sub re-attach.\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        let mut replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|(s, _)| *s >= from_seq)\r\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\r\n            .collect();\r\n        if self.finished {\r\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\r\n        }\r\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\r\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\r\n        // prior writer — poisoned OR healthily displaced — holds only its own\r\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\r\n        // brain-swap displacement left the displaced writer and the successor\r\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\r\n        // write failure (its conn dying — every daemon refresh) canceled the\r\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\r\n        // displaced stream on exactly the post-refresh path this wave calms.\r\n        // One poison never dead-ends the stream (T5 recovery): the next\r\n        // generation's mint IS the renewal; `send_stream` reads this current\r\n        // Arc, old workers' handles stay theirs (C2 consumes via\r\n        // [`NetHost::stream_lease`]).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        self.lease = Arc::new(StreamLease::default());\r\n        let replay_len = replay.len();\r\n        self.subscriber = Some(SubscriberSeat::install(\r\n            sub,\r\n            replay,\r\n            Some(Arc::clone(&self.lease)),\r\n        ));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r\n    cap_events: usize,\r\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\r\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\r\n    subscriber: Option<SubscriberSeat>,\r\n}\r\n\r\nimpl PresenceLog {\r\n    fn new(cap_events: usize) -> Self {\r\n        PresenceLog {\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_events,\r\n            subscriber: None,\r\n        }\r\n    }\r\n\r\n    /// Append a liveness event: assign the next `seq`, retain it in the bounded\r\n    /// ring, live-send to the current subscriber. Producers: `register_conn`\r\n    /// (connected) and the per-conn closed-watchers (disconnected) — every\r\n    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive\r\n    /// in strict `seq` order.\r\n    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0, // assigned in push\r\n            kind: kind.to_string(),\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: None,\r\n            remote_addr: serde_json::Value::Null,\r\n        });\r\n    }\r\n\r\n    /// A `connected` event carrying the connection's OBSERVED remote address\r\n    /// (REQ-CONV-1): the async-dial write-back seed the blocking `net-dialed`\r\n    /// reply used to carry — now it rides the presence stream so the pump seeds\r\n    /// `peer-addrs.json` from a non-blocking dial the same way.\r\n    // [impl->REQ-CONV-1]\r\n    fn append_connected(&mut self, conn_id: u64, remote_id_hex: &str, remote_addr: serde_json::Value) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0,\r\n            kind: PRESENCE_CONNECTED.to_string(),\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: None,\r\n            remote_addr,\r\n        });\r\n    }\r\n\r\n    /// A `dial-failed` event (PUMP-TRUTH W2): a non-blocking [`KIND_NET_DIAL_SUBMIT`]\r\n    /// dial failed. No connection opened → the documented `conn_id: 0` sentinel;\r\n    /// the pump correlates by `remote_id_hex` + `kind`, never `conn_id`.\r\n    // [impl->REQ-PUMP-PEER-ISOLATION]\r\n    fn append_dial_failed(&mut self, remote_id_hex: &str, reason: String) {\r\n        self.push(NetPresenceEvent {\r\n            seq: 0,\r\n            kind: PRESENCE_DIAL_FAILED.to_string(),\r\n            conn_id: 0,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            reason: Some(reason),\r\n            remote_addr: serde_json::Value::Null,\r\n        });\r\n    }\r\n\r\n    /// Assign the next `seq`, retain in the bounded ring, live-send to the\r\n    /// current subscriber. Every append serializes on the enclosing `Mutex`, so\r\n    /// subscriber frames arrive in strict `seq` order.\r\n    fn push(&mut self, mut ev: NetPresenceEvent) {\r\n        ev.seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back(ev.clone());\r\n        while self.ring.len() > self.cap_events {\r\n            self.ring.pop_front();\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_presence_event_envelope(&ev);\r\n            // Enqueue only; halt-and-remove on failure — the PresenceLog\r\n            // mirror of the StreamLog discipline (Amendment fix 2).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                if let Some(seat) = self.subscriber.take() {\r\n                    seat.sub\r\n                        .lifecycle_event(\"presence-sub-halt\", &format!(\"reason={fail:?}\"));\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the subscriber, replaying every retained event with\r\n    /// `seq >= from_seq` before live streaming (the gapless-resume attach).\r\n    /// The replay snapshot rides the seat's writer thread — replay-then-live\r\n    /// order structural, conn I/O outside this mutex — same discipline as\r\n    /// [`StreamLog::begin_attach`]. Returns the replay event count.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // Poison-class only (see StreamLog::begin_attach): a healthy\r\n            // prior presence subscriber is displaced by the newer brain.\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    \"presence subscriber busy: prior subscriber still draining\",\r\n                ));\r\n            }\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): presence-subscriber role fact + attach\r\n        // record — the OTHER RCA-pinned shared carrier (the per-dial-failure\r\n        // presence push rides this conn). [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(\"presence-subscriber\");\r\n        sub.lifecycle_event(\"presence-sub-attach\", &format!(\"from_seq={from_seq}\"));\r\n        let replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|e| e.seq >= from_seq)\r\n            .map(net_presence_event_envelope)\r\n            .collect();\r\n        let replay_len = replay.len();\r\n        // No lease on the presence channel (it has no paired serve leg).\r\n        self.subscriber = Some(SubscriberSeat::install(sub, replay, None));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A stream's send half — the same two transports as [`ConnKind`]. The\r\n/// `serve_attach`/operator pump and the `StreamLog` read discipline are\r\n/// IDENTICAL across both; only this leaf write differs (QUIC stream vs the\r\n/// write end of an in-process duplex). That is the M12 W1.5 one-pump invariant\r\n/// made structural: a loopback attach and a cross-node attach diverge at this\r\n/// leaf and nowhere above it.\r\nenum SendHalf {\r\n    Quic(SendStream),\r\n    Loopback(WriteHalf<DuplexStream>),\r\n}\r\n\r\nimpl SendHalf {\r\n    /// Write all `bytes` (no-op on empty). Runs on the host runtime.\r\n    async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {\r\n        match self {\r\n            SendHalf::Quic(s) => s\r\n                .write_all(bytes)\r\n                .await\r\n                .map_err(|e| io::Error::other(e.to_string())),\r\n            SendHalf::Loopback(w) => w.write_all(bytes).await,\r\n        }\r\n    }\r\n\r\n    /// Close the send side so the peer's read pump sees a clean end (QUIC\r\n    /// `finish` / duplex `shutdown`).\r\n    async fn finish(&mut self) -> io::Result<()> {\r\n        match self {\r\n            SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),\r\n            SendHalf::Loopback(w) => w.shutdown().await,\r\n        }\r\n    }\r\n}\r","startLine":374,"lineNumbers":[374,null,414,null,419,420,421,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,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":15283,"outputLines":335,"outputBytes":15283,"shownRange":{"start":429,"end":763},"nextOffset":764}}},"isError":false,"timestamp":1784271525536}}
{"type":"message","id":"d14656ed","parentId":"002e45a9","timestamp":"2026-07-17T06:58:45.583Z","message":{"role":"toolResult","toolCallId":"call_ZFBhEqUPzP9gdRrfdyHUmgFc|fc_089095e18118ee7d016a59d2a550088190ad4e3c3b0b5c8cb4","toolName":"read","content":[{"type":"text","text":"    fn dispatch_net_dial(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n…\n        match req.op_id {\n            None => {\n…\n                let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, None, true, observed),\n                );\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetDial, || {\n                        let (conn_id, _) = host.dial(addr)?;\n                        host.record_dial_op(minter, op, conn_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-dial failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let conn_id = host\n                    .dial_op_conn(minter, op)\n                    .ok_or_else(crate::effect::net_dial_no_longer_held_message)?;\n                let remote = host\n                    .conn_remote_hex(conn_id)\n                    .ok_or(\"dialed connection missing from the conn table\")?;\n                let applied_now = outcome == Outcome::Applied;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),\n                );\n                Ok(())\n            }\n        }\n    }\n\n    /// PUMP-TRUTH W2 (REQ-PUMP-PEER-ISOLATION): accept a NON-BLOCKING dial\n    /// submit. Decode the target addr, derive its correlation key\n    /// (`EndpointAddr::node_id_hex` — the peer the outcome events key on), fire\n    /// the concurrent dial ([`NetHost::submit_dial`], returns immediately), and\n    /// ack RIGHT AWAY. The dial's real outcome rides the presence stream\n    /// (CONNECTED / DIAL_FAILED), never this reply — so one dead peer can neither\n    /// block the caller nor poison the round. The pump submits with `op_id: None`\n    /// (its dials are non-journaled today), so this path does not journal; the\n    /// blocking [`Self::dispatch_net_dial`] keeps the exactly-once op path for its\n    /// other callers.\n    // [impl->REQ-PUMP-PEER-ISOLATION]\n    fn dispatch_net_dial_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetDialReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-dial-submit payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let addr: EndpointAddr = serde_json::from_value(req.addr)\n            .map_err(|e| format!(\"bad net-dial-submit addr: {e}\"))?;\n        // Correlation key: the target's stable hex (from the dial addr's node id\n        // — same derivation as the roster-member path at dispatch_pair_* uses).\n        // On a successful connect `register_conn` re-derives the SAME hex from the\n        // proven pubkey; on failure `submit_dial` tags DIAL_FAILED with it — so an\n        // outcome always matches the peer the pump submitted for.\n        let remote_id_hex = spt_proto::identity::PublicKey::from_bytes(addr.id.as_bytes())\n            .map_err(|e| format!(\"bad net-dial-submit addr id: {e}\"))?\n            .to_hex();\n        host.submit_dial(addr, remote_id_hex);\n        // Immediate bare ack — the dial spawned; its outcome is a presence event.\n        send_frame(send, &Envelope::new(KIND_NET_DIAL_SUBMITTED, serde_json::Value::Null));\n        Ok(())\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and answer with `net-dialed`. The transport a LOCAL\n    /// `spt rc` rides — QUIC self-dial is refused by iroh, so local attach\n    /// uses a broker-minted loopback conn whose `remote_id_hex` is this node's\n    /// own id (the served stream then gates same-node, access short-circuit\n    /// Allow). Not journaled: the singleton is idempotent by reuse, and there\n    /// is no network leg a crash-replay could double.\n    // [impl->REQ-RC-1]\n    fn dispatch_net_dial_loopback(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let (conn_id, remote) = host.dial_loopback();\n        send_frame(\n            send,\n            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),\n        );\n        Ok(())\n    }\n\n    /// Run the joiner leg of the subnet pairing ceremony (M7 D3,\n    /// REQ-SUBNET-2) on the broker-owned endpoint — it binds and pins the\n    /// REAL node pubkey, so it cannot run anywhere else. Not journaled: a\n    /// repeated join is idempotent at the stores (re-pair keeps the existing\n    /// trust row + seed), and the TOTP code in the request must never land\n    /// in a durable journal. Blocks this client's channel for the ceremony's\n    /// (bounded) life; the reply is always a `pair-joined` frame, with\n    /// failures classified for the CLI's guided UX.\n    // [impl->REQ-SUBNET-2]\n    fn dispatch_pair_join(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairJoinReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-join payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        // The optional explicit responder address (the rig seam) decodes\n        // strictly — a malformed address is a usage error, not a dial flop.\n        let responder_addr = match req.responder_addr {\n            Some(v) => Some(\n                serde_json::from_value::<EndpointAddr>(v)\n                    .map_err(|e| format!(\"bad pair-join responder_addr: {e}\"))?,\n            ),\n            None => None,\n        };\n        let frame = match host.pair_join(&req.subnet, &req.code, responder_addr) {\n            Ok(outcome) => Envelope::new(\n                KIND_PAIR_JOINED,\n                serde_json::to_value(PairJoinReply {\n                    ok: true,\n                    class: None,\n                    retry_after_secs: None,\n                    detail: outcome,\n                })\n                .expect(\"PairJoinReply serializes\"),\n            ),\n            Err(fail) => pair_fail_frame(&fail),\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for\n    /// the named subnet — resolve its real, stable pairing address via the\n    /// rendezvous (PUBLIC `(name, epoch)`, no code) — and HOLD it under a fresh\n    /// session id, bounded by [`PAIR_CODE_WAIT`]. Replies [`KIND_MET_MEMBER`] so\n    /// the CLI can collect the code, fresh, against a found member. Expired holds\n    /// from abandoned earlier attempts are swept here (no separate reaper — pairing\n    /// is rare and the map is tiny).\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_meet(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairMeetReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-meet payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\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                        member_hex,\n                    })\n                    .expect(\"MetMember serializes\"),\n                );\n                send_frame(send, &frame);\n            }\n            // A meet failure reuses the classified pair-joined reply shape (ok:\n            // false) — the CLI renders it before ever prompting for a code (the\n            // \"dead subnet must not make the user fetch a code\" path).\n            Err(fail) => send_frame(send, &pair_fail_frame(&fail)),\n        }\n        Ok(())\n    }\n\n    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a\n    /// held meet session. Dials the HELD address (no re-search) and runs the\n    /// SPAKE2 ceremony. A wrong-code retry re-submits against the same session id\n    /// (the hold is KEPT — ceremony only); success or any other terminal verdict\n    /// (and an expired/unknown session) drops the hold.\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_code_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairCodeSubmit = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-code-submit payload: {e}\"))?;\n        // Read the held address FIRST, enforcing the wait-for-code timeout — a\n        // missing or expired hold is a clean failure that needs no net (the CLI\n        // re-runs the meet).\n        let held = {\n            let mut holds = recover(&self.pair_holds);\n            match holds.get(&req.session_id) {\n                Some(h) if hold_is_live(h.deadline, Instant::now()) => {\n                    Some((h.subnet.clone(), h.addr.clone()))\n                }\n                Some(_) => {\n                    holds.remove(&req.session_id);\n                    None\n                }\n                None => None,\n            }\n        };\n        let Some((subnet, addr)) = held else {\n            let reply = PairJoinReply {\n                ok: false,\n                class: Some(\"other\".to_string()),\n                retry_after_secs: None,\n                detail: \"the meet expired (the 5-minute wait-for-code window lapsed) \\\n                         — re-run `spt subnet join`\"\n                    .to_string(),\n            };\n            send_frame(\n                send,\n                &Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(reply).expect(\"PairJoinReply serializes\"),\n                ),\n            );\n            return Ok(());\n        };\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let frame = match host.pair_join(&subnet, &req.code, Some(addr)) {\n            Ok(outcome) => {\n                recover(&self.pair_holds).remove(&req.session_id);\n                Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(PairJoinReply {\n                        ok: true,\n                        class: None,\n                        retry_after_secs: None,\n                        detail: outcome,\n                    })\n                    .expect(\"PairJoinReply serializes\"),\n                )\n            }\n            Err(fail) => {\n                // Keep the hold ONLY for a wrong-code retry (re-runs the ceremony\n                // against the same held address); every other verdict is terminal.\n                if !fail_keeps_hold(&fail) {\n                    recover(&self.pair_holds).remove(&req.session_id);\n                }\n                pair_fail_frame(&fail)\n            }\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Open a bidi stream on a held connection (D4b). Journaled exactly-once\n    /// under the same discipline as [`Self::dispatch_net_dial`]: the minted\n    /// `stream_id` is recorded inside the `apply_once` closure, and a deduped\n    /// replay is answered with the stream the first apply opened.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    fn dispatch_net_stream_open(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-open payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let build = |stream_id: u64, conn_id: u64, op_id: Option<u64>, applied_now: bool| {\n            Envelope::new(\n                KIND_NET_STREAM_OPENED,\n                serde_json::to_value(NetStreamOpened {\n                    stream_id,\n                    conn_id,\n                    op_id,\n                    applied_now,\n                })\n                .expect(\"NetStreamOpened serializes\"),\n            )\n        };\n        match req.op_id {\n            None => {\n                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;\n                send_frame(send, &build(stream_id, req.conn_id, None, true));\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetSend, || {\n                        let stream_id = host.open_stream(req.conn_id)?;\n                        host.record_stream_op(minter, op, stream_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-stream-open failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let stream_id = host\n                    .stream_op_id(minter, op)\n                    .ok_or_else(crate::effect::stream_open_no_longer_held_message)?;\n                let applied_now = outcome == Outcome::Applied;\n                send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));\n                Ok(())\n            }\n        }\n    }\n\n    /// Write bytes to a held stream's send side (D4b). With an `op_id` the\n    /// write is exactly-once through the broker-owned journal (a replayed\n    /// chunk after a brain crash is not written twice — Spike #3 invariant D\n    /// on the send path) and acked with `net-sent`; without one it is a direct\n\n[Showing lines 3999-4323 of 6298. Use :4324 to continue]"}],"details":{"truncation":{"content":"                let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, None, true, observed),\n                );\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetDial, || {\n                        let (conn_id, _) = host.dial(addr)?;\n                        host.record_dial_op(minter, op, conn_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-dial failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let conn_id = host\n                    .dial_op_conn(minter, op)\n                    .ok_or_else(crate::effect::net_dial_no_longer_held_message)?;\n                let remote = host\n                    .conn_remote_hex(conn_id)\n                    .ok_or(\"dialed connection missing from the conn table\")?;\n                let applied_now = outcome == Outcome::Applied;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),\n                );\n                Ok(())\n            }\n        }\n    }\n\n    /// PUMP-TRUTH W2 (REQ-PUMP-PEER-ISOLATION): accept a NON-BLOCKING dial\n    /// submit. Decode the target addr, derive its correlation key\n    /// (`EndpointAddr::node_id_hex` — the peer the outcome events key on), fire\n    /// the concurrent dial ([`NetHost::submit_dial`], returns immediately), and\n    /// ack RIGHT AWAY. The dial's real outcome rides the presence stream\n    /// (CONNECTED / DIAL_FAILED), never this reply — so one dead peer can neither\n    /// block the caller nor poison the round. The pump submits with `op_id: None`\n    /// (its dials are non-journaled today), so this path does not journal; the\n    /// blocking [`Self::dispatch_net_dial`] keeps the exactly-once op path for its\n    /// other callers.\n    // [impl->REQ-PUMP-PEER-ISOLATION]\n    fn dispatch_net_dial_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetDialReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-dial-submit payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let addr: EndpointAddr = serde_json::from_value(req.addr)\n            .map_err(|e| format!(\"bad net-dial-submit addr: {e}\"))?;\n        // Correlation key: the target's stable hex (from the dial addr's node id\n        // — same derivation as the roster-member path at dispatch_pair_* uses).\n        // On a successful connect `register_conn` re-derives the SAME hex from the\n        // proven pubkey; on failure `submit_dial` tags DIAL_FAILED with it — so an\n        // outcome always matches the peer the pump submitted for.\n        let remote_id_hex = spt_proto::identity::PublicKey::from_bytes(addr.id.as_bytes())\n            .map_err(|e| format!(\"bad net-dial-submit addr id: {e}\"))?\n            .to_hex();\n        host.submit_dial(addr, remote_id_hex);\n        // Immediate bare ack — the dial spawned; its outcome is a presence event.\n        send_frame(send, &Envelope::new(KIND_NET_DIAL_SUBMITTED, serde_json::Value::Null));\n        Ok(())\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and answer with `net-dialed`. The transport a LOCAL\n    /// `spt rc` rides — QUIC self-dial is refused by iroh, so local attach\n    /// uses a broker-minted loopback conn whose `remote_id_hex` is this node's\n    /// own id (the served stream then gates same-node, access short-circuit\n    /// Allow). Not journaled: the singleton is idempotent by reuse, and there\n    /// is no network leg a crash-replay could double.\n    // [impl->REQ-RC-1]\n    fn dispatch_net_dial_loopback(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let (conn_id, remote) = host.dial_loopback();\n        send_frame(\n            send,\n            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),\n        );\n        Ok(())\n    }\n\n    /// Run the joiner leg of the subnet pairing ceremony (M7 D3,\n    /// REQ-SUBNET-2) on the broker-owned endpoint — it binds and pins the\n    /// REAL node pubkey, so it cannot run anywhere else. Not journaled: a\n    /// repeated join is idempotent at the stores (re-pair keeps the existing\n    /// trust row + seed), and the TOTP code in the request must never land\n    /// in a durable journal. Blocks this client's channel for the ceremony's\n    /// (bounded) life; the reply is always a `pair-joined` frame, with\n    /// failures classified for the CLI's guided UX.\n    // [impl->REQ-SUBNET-2]\n    fn dispatch_pair_join(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairJoinReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-join payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        // The optional explicit responder address (the rig seam) decodes\n        // strictly — a malformed address is a usage error, not a dial flop.\n        let responder_addr = match req.responder_addr {\n            Some(v) => Some(\n                serde_json::from_value::<EndpointAddr>(v)\n                    .map_err(|e| format!(\"bad pair-join responder_addr: {e}\"))?,\n            ),\n            None => None,\n        };\n        let frame = match host.pair_join(&req.subnet, &req.code, responder_addr) {\n            Ok(outcome) => Envelope::new(\n                KIND_PAIR_JOINED,\n                serde_json::to_value(PairJoinReply {\n                    ok: true,\n                    class: None,\n                    retry_after_secs: None,\n                    detail: outcome,\n                })\n                .expect(\"PairJoinReply serializes\"),\n            ),\n            Err(fail) => pair_fail_frame(&fail),\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for\n    /// the named subnet — resolve its real, stable pairing address via the\n    /// rendezvous (PUBLIC `(name, epoch)`, no code) — and HOLD it under a fresh\n    /// session id, bounded by [`PAIR_CODE_WAIT`]. Replies [`KIND_MET_MEMBER`] so\n    /// the CLI can collect the code, fresh, against a found member. Expired holds\n    /// from abandoned earlier attempts are swept here (no separate reaper — pairing\n    /// is rare and the map is tiny).\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_meet(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairMeetReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-meet payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\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                        member_hex,\n                    })\n                    .expect(\"MetMember serializes\"),\n                );\n                send_frame(send, &frame);\n            }\n            // A meet failure reuses the classified pair-joined reply shape (ok:\n            // false) — the CLI renders it before ever prompting for a code (the\n            // \"dead subnet must not make the user fetch a code\" path).\n            Err(fail) => send_frame(send, &pair_fail_frame(&fail)),\n        }\n        Ok(())\n    }\n\n    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a\n    /// held meet session. Dials the HELD address (no re-search) and runs the\n    /// SPAKE2 ceremony. A wrong-code retry re-submits against the same session id\n    /// (the hold is KEPT — ceremony only); success or any other terminal verdict\n    /// (and an expired/unknown session) drops the hold.\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_code_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairCodeSubmit = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-code-submit payload: {e}\"))?;\n        // Read the held address FIRST, enforcing the wait-for-code timeout — a\n        // missing or expired hold is a clean failure that needs no net (the CLI\n        // re-runs the meet).\n        let held = {\n            let mut holds = recover(&self.pair_holds);\n            match holds.get(&req.session_id) {\n                Some(h) if hold_is_live(h.deadline, Instant::now()) => {\n                    Some((h.subnet.clone(), h.addr.clone()))\n                }\n                Some(_) => {\n                    holds.remove(&req.session_id);\n                    None\n                }\n                None => None,\n            }\n        };\n        let Some((subnet, addr)) = held else {\n            let reply = PairJoinReply {\n                ok: false,\n                class: Some(\"other\".to_string()),\n                retry_after_secs: None,\n                detail: \"the meet expired (the 5-minute wait-for-code window lapsed) \\\n                         — re-run `spt subnet join`\"\n                    .to_string(),\n            };\n            send_frame(\n                send,\n                &Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(reply).expect(\"PairJoinReply serializes\"),\n                ),\n            );\n            return Ok(());\n        };\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let frame = match host.pair_join(&subnet, &req.code, Some(addr)) {\n            Ok(outcome) => {\n                recover(&self.pair_holds).remove(&req.session_id);\n                Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(PairJoinReply {\n                        ok: true,\n                        class: None,\n                        retry_after_secs: None,\n                        detail: outcome,\n                    })\n                    .expect(\"PairJoinReply serializes\"),\n                )\n            }\n            Err(fail) => {\n                // Keep the hold ONLY for a wrong-code retry (re-runs the ceremony\n                // against the same held address); every other verdict is terminal.\n                if !fail_keeps_hold(&fail) {\n                    recover(&self.pair_holds).remove(&req.session_id);\n                }\n                pair_fail_frame(&fail)\n            }\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Open a bidi stream on a held connection (D4b). Journaled exactly-once\n    /// under the same discipline as [`Self::dispatch_net_dial`]: the minted\n    /// `stream_id` is recorded inside the `apply_once` closure, and a deduped\n    /// replay is answered with the stream the first apply opened.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    fn dispatch_net_stream_open(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-open payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let build = |stream_id: u64, conn_id: u64, op_id: Option<u64>, applied_now: bool| {\n            Envelope::new(\n                KIND_NET_STREAM_OPENED,\n                serde_json::to_value(NetStreamOpened {\n                    stream_id,\n                    conn_id,\n                    op_id,\n                    applied_now,\n                })\n                .expect(\"NetStreamOpened serializes\"),\n            )\n        };\n        match req.op_id {\n            None => {\n                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;\n                send_frame(send, &build(stream_id, req.conn_id, None, true));\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetSend, || {\n                        let stream_id = host.open_stream(req.conn_id)?;\n                        host.record_stream_op(minter, op, stream_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-stream-open failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let stream_id = host\n                    .stream_op_id(minter, op)\n                    .ok_or_else(crate::effect::stream_open_no_longer_held_message)?;\n                let applied_now = outcome == Outcome::Applied;\n                send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));\n                Ok(())\n            }\n        }\n    }\n\n    /// Write bytes to a held stream's send side (D4b). With an `op_id` the\n    /// write is exactly-once through the broker-owned journal (a replayed\n    /// chunk after a brain crash is not written twice — Spike #3 invariant D\n    /// on the send path) and acked with `net-sent`; without one it is a direct","truncated":true,"truncatedBy":"lines","totalLines":2300,"totalBytes":15481,"outputLines":325,"outputBytes":15481,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn dispatch_net_dial(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n…\n        match req.op_id {\n            None => {\n…\n                let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, None, true, observed),\n                );\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetDial, || {\n                        let (conn_id, _) = host.dial(addr)?;\n                        host.record_dial_op(minter, op, conn_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-dial failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let conn_id = host\n                    .dial_op_conn(minter, op)\n                    .ok_or_else(crate::effect::net_dial_no_longer_held_message)?;\n                let remote = host\n                    .conn_remote_hex(conn_id)\n                    .ok_or(\"dialed connection missing from the conn table\")?;\n                let applied_now = outcome == Outcome::Applied;\n                let observed = host.conn_remote_addr_json(conn_id);\n                send_frame(\n                    send,\n                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),\n                );\n                Ok(())\n            }\n        }\n    }\n\n    /// PUMP-TRUTH W2 (REQ-PUMP-PEER-ISOLATION): accept a NON-BLOCKING dial\n    /// submit. Decode the target addr, derive its correlation key\n    /// (`EndpointAddr::node_id_hex` — the peer the outcome events key on), fire\n    /// the concurrent dial ([`NetHost::submit_dial`], returns immediately), and\n    /// ack RIGHT AWAY. The dial's real outcome rides the presence stream\n    /// (CONNECTED / DIAL_FAILED), never this reply — so one dead peer can neither\n    /// block the caller nor poison the round. The pump submits with `op_id: None`\n    /// (its dials are non-journaled today), so this path does not journal; the\n    /// blocking [`Self::dispatch_net_dial`] keeps the exactly-once op path for its\n    /// other callers.\n    // [impl->REQ-PUMP-PEER-ISOLATION]\n    fn dispatch_net_dial_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetDialReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-dial-submit payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let addr: EndpointAddr = serde_json::from_value(req.addr)\n            .map_err(|e| format!(\"bad net-dial-submit addr: {e}\"))?;\n        // Correlation key: the target's stable hex (from the dial addr's node id\n        // — same derivation as the roster-member path at dispatch_pair_* uses).\n        // On a successful connect `register_conn` re-derives the SAME hex from the\n        // proven pubkey; on failure `submit_dial` tags DIAL_FAILED with it — so an\n        // outcome always matches the peer the pump submitted for.\n        let remote_id_hex = spt_proto::identity::PublicKey::from_bytes(addr.id.as_bytes())\n            .map_err(|e| format!(\"bad net-dial-submit addr id: {e}\"))?\n            .to_hex();\n        host.submit_dial(addr, remote_id_hex);\n        // Immediate bare ack — the dial spawned; its outcome is a presence event.\n        send_frame(send, &Envelope::new(KIND_NET_DIAL_SUBMITTED, serde_json::Value::Null));\n        Ok(())\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and answer with `net-dialed`. The transport a LOCAL\n    /// `spt rc` rides — QUIC self-dial is refused by iroh, so local attach\n    /// uses a broker-minted loopback conn whose `remote_id_hex` is this node's\n    /// own id (the served stream then gates same-node, access short-circuit\n    /// Allow). Not journaled: the singleton is idempotent by reuse, and there\n    /// is no network leg a crash-replay could double.\n    // [impl->REQ-RC-1]\n    fn dispatch_net_dial_loopback(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let (conn_id, remote) = host.dial_loopback();\n        send_frame(\n            send,\n            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),\n        );\n        Ok(())\n    }\n\n    /// Run the joiner leg of the subnet pairing ceremony (M7 D3,\n    /// REQ-SUBNET-2) on the broker-owned endpoint — it binds and pins the\n    /// REAL node pubkey, so it cannot run anywhere else. Not journaled: a\n    /// repeated join is idempotent at the stores (re-pair keeps the existing\n    /// trust row + seed), and the TOTP code in the request must never land\n    /// in a durable journal. Blocks this client's channel for the ceremony's\n    /// (bounded) life; the reply is always a `pair-joined` frame, with\n    /// failures classified for the CLI's guided UX.\n    // [impl->REQ-SUBNET-2]\n    fn dispatch_pair_join(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairJoinReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-join payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        // The optional explicit responder address (the rig seam) decodes\n        // strictly — a malformed address is a usage error, not a dial flop.\n        let responder_addr = match req.responder_addr {\n            Some(v) => Some(\n                serde_json::from_value::<EndpointAddr>(v)\n                    .map_err(|e| format!(\"bad pair-join responder_addr: {e}\"))?,\n            ),\n            None => None,\n        };\n        let frame = match host.pair_join(&req.subnet, &req.code, responder_addr) {\n            Ok(outcome) => Envelope::new(\n                KIND_PAIR_JOINED,\n                serde_json::to_value(PairJoinReply {\n                    ok: true,\n                    class: None,\n                    retry_after_secs: None,\n                    detail: outcome,\n                })\n                .expect(\"PairJoinReply serializes\"),\n            ),\n            Err(fail) => pair_fail_frame(&fail),\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for\n    /// the named subnet — resolve its real, stable pairing address via the\n    /// rendezvous (PUBLIC `(name, epoch)`, no code) — and HOLD it under a fresh\n    /// session id, bounded by [`PAIR_CODE_WAIT`]. Replies [`KIND_MET_MEMBER`] so\n    /// the CLI can collect the code, fresh, against a found member. Expired holds\n    /// from abandoned earlier attempts are swept here (no separate reaper — pairing\n    /// is rare and the map is tiny).\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_meet(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairMeetReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-meet payload: {e}\"))?;\n        let host = self\n            .net\n            .get()\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                        member_hex,\n                    })\n                    .expect(\"MetMember serializes\"),\n                );\n                send_frame(send, &frame);\n            }\n            // A meet failure reuses the classified pair-joined reply shape (ok:\n            // false) — the CLI renders it before ever prompting for a code (the\n            // \"dead subnet must not make the user fetch a code\" path).\n            Err(fail) => send_frame(send, &pair_fail_frame(&fail)),\n        }\n        Ok(())\n    }\n\n    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a\n    /// held meet session. Dials the HELD address (no re-search) and runs the\n    /// SPAKE2 ceremony. A wrong-code retry re-submits against the same session id\n    /// (the hold is KEPT — ceremony only); success or any other terminal verdict\n    /// (and an expired/unknown session) drops the hold.\n    // [impl->REQ-JOIN-TWO-PHASE]\n    fn dispatch_pair_code_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: PairCodeSubmit = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad pair-code-submit payload: {e}\"))?;\n        // Read the held address FIRST, enforcing the wait-for-code timeout — a\n        // missing or expired hold is a clean failure that needs no net (the CLI\n        // re-runs the meet).\n        let held = {\n            let mut holds = recover(&self.pair_holds);\n            match holds.get(&req.session_id) {\n                Some(h) if hold_is_live(h.deadline, Instant::now()) => {\n                    Some((h.subnet.clone(), h.addr.clone()))\n                }\n                Some(_) => {\n                    holds.remove(&req.session_id);\n                    None\n                }\n                None => None,\n            }\n        };\n        let Some((subnet, addr)) = held else {\n            let reply = PairJoinReply {\n                ok: false,\n                class: Some(\"other\".to_string()),\n                retry_after_secs: None,\n                detail: \"the meet expired (the 5-minute wait-for-code window lapsed) \\\n                         — re-run `spt subnet join`\"\n                    .to_string(),\n            };\n            send_frame(\n                send,\n                &Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(reply).expect(\"PairJoinReply serializes\"),\n                ),\n            );\n            return Ok(());\n        };\n        let host = self\n            .net\n            .get()\n            .ok_or(\"net disabled: this broker was bound without a network host\")?;\n        let frame = match host.pair_join(&subnet, &req.code, Some(addr)) {\n            Ok(outcome) => {\n                recover(&self.pair_holds).remove(&req.session_id);\n                Envelope::new(\n                    KIND_PAIR_JOINED,\n                    serde_json::to_value(PairJoinReply {\n                        ok: true,\n                        class: None,\n                        retry_after_secs: None,\n                        detail: outcome,\n                    })\n                    .expect(\"PairJoinReply serializes\"),\n                )\n            }\n            Err(fail) => {\n                // Keep the hold ONLY for a wrong-code retry (re-runs the ceremony\n                // against the same held address); every other verdict is terminal.\n                if !fail_keeps_hold(&fail) {\n                    recover(&self.pair_holds).remove(&req.session_id);\n                }\n                pair_fail_frame(&fail)\n            }\n        };\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Open a bidi stream on a held connection (D4b). Journaled exactly-once\n    /// under the same discipline as [`Self::dispatch_net_dial`]: the minted\n    /// `stream_id` is recorded inside the `apply_once` closure, and a deduped\n    /// replay is answered with the stream the first apply opened.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    fn dispatch_net_stream_open(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-open payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let build = |stream_id: u64, conn_id: u64, op_id: Option<u64>, applied_now: bool| {\n            Envelope::new(\n                KIND_NET_STREAM_OPENED,\n                serde_json::to_value(NetStreamOpened {\n                    stream_id,\n                    conn_id,\n                    op_id,\n                    applied_now,\n                })\n                .expect(\"NetStreamOpened serializes\"),\n            )\n        };\n        match req.op_id {\n            None => {\n                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;\n                send_frame(send, &build(stream_id, req.conn_id, None, true));\n                Ok(())\n            }\n            Some(op) => {\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: NET_EFFECT_SESSION,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::NetSend, || {\n                        let stream_id = host.open_stream(req.conn_id)?;\n                        host.record_stream_op(minter, op, stream_id);\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"net-stream-open failed: {e}\"))?;\n                // Frozen wire-compat text, single-sourced from the shared marker so\n                // the A-4b retry matches one source of truth (ADR-0034 Addendum 2).\n                let stream_id = host\n                    .stream_op_id(minter, op)\n                    .ok_or_else(crate::effect::stream_open_no_longer_held_message)?;\n                let applied_now = outcome == Outcome::Applied;\n                send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));\n                Ok(())\n            }\n        }\n    }\n\n    /// Write bytes to a held stream's send side (D4b). With an `op_id` the\n    /// write is exactly-once through the broker-owned journal (a replayed\n    /// chunk after a brain crash is not written twice — Spike #3 invariant D\n    /// on the send path) and acked with `net-sent`; without one it is a direct","startLine":3987,"lineNumbers":[3987,null,3996,3997,null,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4029,4030,4031,4032,4033,4034,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4067,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4117,4118,4119,4120,4121,4122,4123,4124,4125,4126,4127,4128,4129,4130,4131,4132,4133,4134,4135,4136,4137,4138,4139,4140,4141,4142,4143,4144,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157,4158,4159,4160,4161,4162,4163,4164,4165,4166,4167,4168,4169,4170,4171,4172,4173,4174,4175,4176,4177,4178,4179,4180,4181,4182,4183,4184,4185,4186,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197,4198,4199,4200,4201,4202,4203,4204,4205,4206,4207,4208,4209,4210,4211,4212,4213,4214,4215,4216,4217,4218,4219,4220,4221,4222,4223,4224,4225,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4238,4239,4240,4241,4242,4243,4244,4245,4246,4247,4248,4249,4250,4251,4252,4253,4254,4255,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271,4272,4273,4274,4275,4276,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,4287,4288,4289,4290,4291,4292,4293,4294,4295,4296,4297,4298,4299,4300,4301,4302,4303,4304,4305,4306,4307,4308,4309,4310,4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":15481,"outputLines":325,"outputBytes":15481,"shownRange":{"start":3999,"end":4323},"nextOffset":4324}}},"isError":false,"timestamp":1784271525582}}
{"type":"message","id":"150caf5e","parentId":"d14656ed","timestamp":"2026-07-17T06:59:16.468Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing broker connection management and thread behavior**\n\n**Investigating subscriber thread cleanup and connection handling**\n\n**Tracing connection churn from terminal retirements**"},{"type":"thinking","thinking":"**Analyzing worker thread count and race conditions**\n\n**Planning inspection of serve and writer threads**"},{"type":"toolCall","id":"call_RMGQ6h6mddXX52e1Fea2ypSO|fc_089095e18118ee7d016a59d2c4670c8190bcad34988cd320d7","name":"grep","arguments":{"i":"Tracing broker connection threads","pattern":"fn serve\\(|accept\\(|handle_client|handle_conn|client|thread::spawn\\(move.*conn|dispatch_net_stream_retire|KIND_NET_STREAM_RETIRE|detach_stream_subscriber","path":"crates/spt-daemon/src/broker.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_gGlKkuEfJGvPwZu7PpoqQaj4|fc_089095e18118ee7d016a59d2c467208190b36cb96e4fee8387","name":"grep","arguments":{"i":"Tracing network retirement","pattern":"pub fn retire_stream|fn retire_stream|streams\\.remove|retain\\(|closed|detach_stream|begin_attach|stream_lease|subscribe_stream|stream_info","path":"crates/spt-daemon/src/nethost.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_ZVPVTYD7sKL5mb9LBDGtxOIz|fc_089095e18118ee7d016a59d2c467248190a73f6aa7a8a92412","name":"grep","arguments":{"i":"Tracing brain connection lifetime","pattern":"pub struct Brain|impl Drop for Brain|struct Brain|cold_start|DaemonTransport|read_event|send\\(","path":"crates/spt-daemon/src/brain.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_ZcVLBzgvH0bbxqcKVO91JwoA|fc_089095e18118ee7d016a59d2c4672c8190babd9ae9b54be9c5","name":"grep","arguments":{"i":"Tracing attach worker termination","pattern":"pub fn serve_attach|net_stream_subscribe|lease canceled|detach|loop \\{|read_event|NetStreamEof|return","path":"crates/spt-daemon/src/attach.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_QLsdYc8LXSCIz403S7LRBjCs|fc_089095e18118ee7d016a59d2c467308190927c7f5168b6c868","name":"grep","arguments":{"i":"Locating pool tests","pattern":"MAX_DISPATCH_WORKERS|pool_has_room|active_workers|bounded worker|O\\(active","path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_roVxHQn7aMgx4dM1EPK84jaD|fc_089095e18118ee7d016a59d2c46734819091c0a2186c1323ee","name":"grep","arguments":{"i":"Locating connection telemetry","pattern":"stream-sub-attach|DISPATCH_EV|CONN_|conn=.*open|conn=.*close|connection.*count|thread","path":"docs/REDISPATCH-STALL-TRIAGE.md; docs/KNOWN-HAZARDS.md; crates/spt-daemon/src/conn.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_LGnGPRMMCx8mEPJvwr5miMfO|fc_089095e18118ee7d016a59d2c467388190a198e4c8d6558f79","name":"read","arguments":{"i":"Reading current history tail","path":".git/logs/HEAD","selector":"1350-1502"}},{"type":"toolCall","id":"call_motA9ndgx4pKcNhkJTYSpseD|fc_089095e18118ee7d016a59d2c4673c81909dd47b76e7597a4a","name":"read","arguments":{"i":"Reading branch histories","path":".git/logs/refs/remotes/origin/build/redispatch-stall-w1","selector":"raw"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":30745,"output":1263,"cacheRead":69120,"cacheWrite":0,"totalTokens":101128,"reasoningTokens":671,"cost":{"input":0.153725,"output":0.03789,"cacheRead":0.03456,"cacheWrite":0,"total":0.22617500000000001}},"stopReason":"toolUse","timestamp":1784271525628,"responseId":"resp_089095e18118ee7d016a59d2a6505c819086c4463648b04b22","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2a7f5f48190bfe7bab0cde5cc85","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdK3s82Ho9K7cRcBMNYI1trlNhLrh5A5aUA_KAaEckWWSHtbUZQ-8n5ocTB85OQs9LPWuUPtA_z-s6LJq_8druD-7cxe0fORMrInFkQh1Ksfz1Y_6KxTeT_7Q9HR0Vj-F_JnE-JFUb5mYLMDClWVQ9h26bo1gjc8Xcr0O8JGLSyPvvBxq3G8zbLA1OSgO-RWJHKT4sLbG6yf-nwUDiexH8ourQ3l9tsjMJGwOVk7T-OnbMdVxcvbPu2dWMe_ZAKAT3vi4WhOfzuYoSzrwkTS-FB3adWD0toWJLhdUtAMv1vtFQCDTsRrezZUfLnTHk_lum9ZrbGYiWrHnRy4-cxkDxqDWGVbxekFCsCS-3h9FwYR_AzmWRs3W3ubstDJeQvBrrIXCnGHU-bLFwX4q6DqDVKZH1-Py6QN7gMco5QHguW9HS4n5oqZhTBRXaEl0HvOXeQujqMkX4QmPFfpd3e3URRhUmFS1aFbuyg-REqTVmgu-r7etayh-M6Glxq-CGzAK3iul_qlT4QVCsfJyKyYveTkJlPAYAGIxsokSdKfKDNT2IR42NFZJV7Ih8lvsMsaJjm9J_BhQB5XPrSWpdreRALC8c_D3cq-fxzOGzuGhoIM_hWHi86SA0Ietwx89uqnX_tNKLGayhe15J0jvvy01VEt27kYnhL6iX47gwY1dvMyBXEt3xFLXLEPhr20XnDYFbq7YB9j16XNHjOpQBywUvIfnYoiOt5f2VOaeIpZIvwnDqsnB_3w2uoC_95sQR3jFZGAknHkE0qA6hnvX5oQ-9bWLJmnLftcAIZPZ4wTyGNzgB9ZTyFPZR_Ptyvb7g_25uGe3nYEv5vKdYI5TnuftU4UduQ6I5OeEJLJiASjcLxZ9LfmIniEKP-QNxKWZgTg8MWXilYB_Zjx4DU2moE0kmi5Y97FCuWwlLgi4YmD4YbbgFT3-MZqt3vAKEDyH63o5lpehxgRHUaLh-sqnO51bwypEiN-5mb9z-Iz0JbrZg7oAS3ZVClBGAtBqxQu31Zb76V8BOxG-ghvaeUxRzLI-UYppDBR35qm1m4sDgufAGd35y-03hTbNaa7SLKW7LP7g_SijQtX5XSDO5k8OTC4sN7AClOoVQs45OqueU1bTBIpDUylJ5pJFCcNSwlAB1-pU0iWzUG7CuzKYrWvsfQ0L0gGv5roYIwoXCZ8U_Mer2AIAAQtrMeDw_dOSxVM_G0FUvncK20dNqg8WWg13yGFffoynRXItCPK43_8UgmymJW8JoYqcwjfduZAPmOVEY_4zq7ihM670jQ3CcjaeEAXqRAAwr1fpzb6sEeo5AUgXwLLcvLty5MG98XgTsHgonCeL7FHtHEFZP99P5FJVyL2Ps7qbEyRsbxJwuFuncSflZ51apKBEVdgwR72XvBkX42TTfWgMIHYIqbTbUd3s-lNsgiDP_ZXf4f3EkuxZgFmi6USN89jWqwU_8ktNXqgrFcOzA9KoZ-pb39qp8nfJ9xnh1fpgBpEiN_bJil2ZbQOJV_cDTnILuTITZQvb_WKtuXGmzNPbCBKR_MD8cc7LIPmc5WuY5OH7KCoaAF8CawANANgvyga8VVL0fgbWHCIqzwVGhMzxqweAZij8go1fefm4N9slA9nXr2wEKREImgyiCWWxap8ASYxTpkc-7jpwXDfvzMnbiKBnarK7gRq8gE0s7NoAj6Qy0bZt8Tg2VaL8maneKmTIXv8Ui8498lXxQ3VkSA6ACtVUPThB7qxEgkpOLfWgZQjmhLukt5129DCkrVbaU1idnEkFA1fExaib-kS7pWeE9D5aedHXDPnw93BHRywRHw7HVKJqafarRHoKcsvETEevlZnkPFAPAZnyB9CNmUMaTHr5m6Jb78k52fdSnm3b_4RHw33Fnjpsqbyz08toTAiTcnbctTgRjhjGgq7Gs9ssGMtDhMnEyiuyh_9D4n0qXJ2U2jijNArdxQDqdr8Ym35HA9jDJERI2c_a70an5phQ_ASfndcJAcqbjAvH6_ABFiQDgOG9okH2WzX6ymJnY7RwR2MaCnx_MvxUOSgmffk-UvCy7CzqJvbj-V9hbJHG_Vn7zQqhJAvZv4AdckkEpSGom-nZK3WWBcLpapKsMVdbTcf4IZ5bnjrN01F04S9AihHP279XdLB90_h2FbVCkmvb6otz3zkLRDylA8Ib2eS2aLG09ikswfFoso-c7626xPhg3V-7N8PX6OsUdDyzlo89kG9YOI6A4xwnHSeZP1u5F8zaBin-pHwJaf6HIFAogf8BvBlUPRfQNA8dobfNsJohTaaMcbWUan3p-N-4SRa5JyFvmMuinXH5HJcMsg5SUzpQMF6z-LppplOguaih57AcAYjdQcj-VmCvpf9pCnlhcnNJYupVAgPZWZSxTlF_9n4T0mg8tFjmOna1XgLiPT7rJHfCsM547d_TGHUUaExumK8lQEmUlXJ9VGGNhjr7bUgSW6PLlqFZf0V9w4_ivxrb97PQ-cgWJCb9lqVqGcrNrcSuS9rs7wJ5zLNp_gsDFy8DveLUIoCso9ZPdvtlBftBcn-k0wpHctTC-9ohoQLkY3w-WkBTXK7GxcW1KxefS-s3t0hqxLImzY7JYzEdQBWl3402dbPgpDbq_QtCMd78DGvRSE58nWk9yfmwpR13WS0kELwyzP9xI7sSOyTj6c12zWqVG2I7Vs7BoICuw1qItnkMs4oiQ3rkKAU56ZDxS-EymPBplKwunLcuPL_Zb66tZR5hDw9Rf55gR0WeZcyHSPLqMQmubjpO9pHVm8CoLoc4mGDFDruTDLwptUqaoywg2zq5SEURpnKOYQp2QPJ-1Zpel6XFdiweYHsDbT9WQ9NQ0bA_9bGFatSc5EH6hjRuCW274XTyMjNfYWmWT9eXMebwc34LhzQA6f9dNugFwD5E0eqqaLmI7ApLDbeYrljm5GkEwjOAejIO2vTNU570C_BDe05A_o8UyR9tsW-9NajyoPyucg29QzNTJQ2RTwftcmUWcKLTkWlQz1aBkxWO4UVFRfMYWM9_npxMQd4Cd-zvsFpBnPBMxtEAVyQx-mabFgKrdxP-2wWRyAzqcb4QpZLHELT-FYyrf_jlIYdS36D9kQrgzNFnXgn_CCIhwvLNJ2_SSqJ2KZyx91sUo3zDDGPop7ElISv-1jy_b-BXzLcMuVHreqHduFP0fWgymWyRfvMq2iJuv5QcAOJnslG3obxGRnJ4ObFOUuI-8F1tdjsDuprYkA_dRX5jA8ABecBjYQ2TOBMxlfytknXy_jf3zHXusH9fmLkA6bPOaszo55b00oVLYBeAa7qDLrX0_S3xSH2tcsIptE1LzR50YqChkWIODiHbkQyiy9Xr56gkre3UwndksIZNV1qL56wZ91kIV2R5yE28wrNASaDDQzGulBid7vDrlpqEwBAuw8BGD0cH-9dI_XqsK31-Zqb76AbwSntG-XScDDpTrkMFMhtcZNAYVXO1e4p53Wf19K0kPB4rcbTUt2kOVPJ7Er1q1Jf_j68VDkWm2QtlJBOr6dfCcE9nZLoA-uwprocI7396s-GHughrQd4_jWDC2FQ3vgkCA8oFvErBvX7NpW5mHBQbF5iJGwcFhy4akPr0HaWsmRBoLe43F8KNKRZbL7SnBuvuEblQ0SgT9Djr8Oco6zSZVGCfQ2Io5Q4ljJMfDHXOcYq4BxyirLu-ZsKhpQBWBEwroYL9c9ks5xrNWLITBJtDEW_QKFvAtOMrPu35aQlrQMjxv1NeJmbF1AH5vrikKGEHcOVOtwEj1ipptahS1E15DqWcaqRMRiEDSlvDybhRmUhOvrKFAIGFNf2YL2DqrJxtn3nYQ3zSrlSkNkaLxkkXgGPtPzS4Qx6GHfMvh7N3wkuweZR5aflEKrP4wsH65awJ6shDdOxYBQzO2cNDKurdxLndgVVbdzNoPGhjiiMfbb7flm1jI1y7IuPmVvngSTAOtCfFJIhQMuW7kWy4xPR2J-vwQxEoZ3pPs1HFiTpPgfKVb52IUamVX8oVW8xsQpgkMJYLT4N0YsznbI42NWFUQvOjBnaoiJ78zuV5V1QpH438VYJ6wO9TNuVhOVQaiNBusIwEEysfkI1DHtjCchXlTF7Vu0BEUqIbY5B2NS1VCWESlXZZvzuvrDe1mJfpcrn89n7W2u13nORerLNnWppmcQ8crgI8MkEK8CjaFHSgJSRdDLW-pZM1VywWEVlriogtLk8GddHLu7GJ9uTQ7sVueu_gfYiFMFrsyInkirTwLO18zU5KablVjNsnXgy_S-l09ejTSxJ28UUGO0pDWGjMv7aBORtO0ojz8rhqx2egVRLDdvl8RHkt2twNMxLw8gLZRhreHDAMT1YrloyKETxpu9sCoQ6icBv5jIeMMU6YWj5zQ2VAmGbfWrO2ZkAgbA_a_wNdxi0RgccNNrjJUzz9SnpCoNpI3x_T2jmAzjlUYLEiPXbPXu232uQK6e5IIU2Fb_OVDuHS_1v6qZInboT35bnWVeQ89lNKRlo_ImNRgUeElF2Sc1gR-Ru1N8OT-hsuO6XIEnJpD8x6Jjgu8vqe_00TxnaZ48CSaaSl9UyA8C0GC4tr2O1sPJHvo5cbMLByGnOZ3Uwy3QNYleOX6zmwqoDTdk7QWLbHqaJvetiyuzZ5ZXwgoXQxQ65Rx2JdDE=","summary":[{"type":"summary_text","text":"**Assessing broker connection management and thread behavior**"},{"type":"summary_text","text":"**Investigating subscriber thread cleanup and connection handling**"},{"type":"summary_text","text":"**Tracing connection churn from terminal retirements**"}]},{"id":"rs_089095e18118ee7d016a59d2b7bf8c81909a65736976bc34b1","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdLEcQwcccwHISAAcpPt0-HIXNgrqKHI-1ial1YDTwyatFwBuC5T_Eoiiy-kW01kHqp_HG-YK55VaIl0sE5utwn_wXxX_kPw8nDsgpO_2E30Uoh5I01u5-QPvZ2ipTLlS3BMt1ZmMI8CjXNl0-1udqVUx_5iiUrHea4pz9iRl3N0rYMQU0f3ACVo4_vYz6-rokDY8_tTdxJbtT5l-QMgfHn8y-dTtFpoUjXz1FOHJei8xj_i3WCPtHozaiJ5lMSOpJQOFGyN2ilRvUDTg0NckJHveBcxt8pSjkwSpi9A4GjWSjT6oHdesCDPmuotkzSZKoQI_-Nez46g1jMDUZMatITUKAR7FfN6eBZcnbcIsFCLM6vw-Y7vmF0tRCmhn_SsAiUFrSVFEiZ_kCTezNn4mGcBdlChqdR8tTebxYsFo-yoUxp7iO6GXOQHIMDatTJM3jh-7AcIm55pf4Ek-za0wmOvjD1jRjk2VznGgpmi4nDiitpByi3FDiJ1afh7r-olVpflOZ88seQv2oAKtlvocAzJXL4bM73h10Vrnb9tQbvBE9p9CINpEMn81XDlM0QD-u3SbJdB4_fNpxxLqimbsEM5ZUYVGVwkZNOyJ2MOQQpLKsIPBMLw92mko7qDaw6Gy37NwX-jltiwc9KVN2pOYyYa20B8dy5FGPXpqXUIIfexYEPyLIbU5G5H9dfKIifIqzy8f49M9mEmHNDsVhnh-sIm1ZyAmUlfj_7jw98VAIohkfuYHhCHxXWPkC72dc8VgMkzQ4u2M_wuJxmNlVSPFXMrkqBR1O-IE3_x7WLooryX3BgFiU9Px7z_R-WwmNzBjTEGzYAapFVUtwbLhcl8JbrDJlHTqHQe5cHOftLz6MiOQf7ghR-Ei-bGyahLbjZpP7hBbY5YIuauEbXgyIbMDvs8Wx1LT83IPOXiAPrIcMYiZX4dzUXBkDBtQu44BjCJrMgQ1MShamTMAEERrxPsP9-qscnNOPoq6583l8a9zR-atFCxf9QHC8__PKZzL1hD-wHFZHXKXWBIXPF4VN0j03vfYoGk55VdsPDivt3QViLFsyB3VLF166IJQclEdjvLd7atyF9bsZk5OeZBUxpYJ4XBDdpHD2ryEVg9aBDDN2tJ6xFp7UHduFg6hanyaemOqinwUEewZ6YEinNWClRXStRdmQwKPS7XB7OsEQr4wxvew5omqlcuC5oCkiLvDL9GXKtZLEQtdq4AyJYIQEn9FPfRlSOhNmZWSfs5vaRRywD7Oh6pe18PB9Bl0AbV8GDSCuJ4yMl-d4gQHDusCy0BLkaxUcc59_hwSk3sZWbP_drkVZkSmcmDA6czgP8wU3badNxd3ZU2TkhShngtOWXoMXX2KFJY4HGuym4OQf1IiGP4RmkGEuIZjGfwFucQ2b_g-TSahPUQSy7lxx1_RVEwiZIezhvOzUW5k14VtjmQscN2epp2EviE-ak53GSg1MBuINPYaOFANisejieL_McyRibBktKdNpNTdNGCdfRoAjhQLsGIEsJesF96cYKjMtDJ0yHwUhJrbiM8FSRV75ba4MgsrF0KOPeDSubRIaMKNE8t7pZCxSouE_XzG_dVnIgo5lAoukhI5hyfB_19Rrtiu2P9vr6EE53m8wOPiku5XdoDoaSnbweljsasFrTjmWvKPT3pFMHVzWk-_aU_uHnXeorDc9K02xMi68elSq8Da3CUdZr4b7nrQiYY93Tl9wtc8mYRWxMp5qeUnI94X3kQByy6QZ6aI-PQiWJVWyi39lYk6woVg6THSVIR50-ACZkR_VmB6eMNTAm6kSHkAkwPJOe_NsJcR1DQwkGGZipaRdzmKJLXi8OaXYBASG-FR2AtzCmCVlf-h3iddcGsnrG8_VuPf12rs0p-4snALdnBuyN219I=","summary":[{"type":"summary_text","text":"**Analyzing worker thread count and race conditions**"},{"type":"summary_text","text":"**Planning inspection of serve and writer threads**"}]},{"id":"fc_089095e18118ee7d016a59d2c4670c8190bcad34988cd320d7","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing broker connection threads\",\"pattern\":\"fn serve\\\\(|accept\\\\(|handle_client|handle_conn|client|thread::spawn\\\\(move.*conn|dispatch_net_stream_retire|KIND_NET_STREAM_RETIRE|detach_stream_subscriber\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_RMGQ6h6mddXX52e1Fea2ypSO","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c467208190b36cb96e4fee8387","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing network retirement\",\"pattern\":\"pub fn retire_stream|fn retire_stream|streams\\\\.remove|retain\\\\(|closed|detach_stream|begin_attach|stream_lease|subscribe_stream|stream_info\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_gGlKkuEfJGvPwZu7PpoqQaj4","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c467248190a73f6aa7a8a92412","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing brain connection lifetime\",\"pattern\":\"pub struct Brain|impl Drop for Brain|struct Brain|cold_start|DaemonTransport|read_event|send\\\\(\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_ZVPVTYD7sKL5mb9LBDGtxOIz","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c4672c8190babd9ae9b54be9c5","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing attach worker termination\",\"pattern\":\"pub fn serve_attach|net_stream_subscribe|lease canceled|detach|loop \\\\{|read_event|NetStreamEof|return\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_ZcVLBzgvH0bbxqcKVO91JwoA","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c467308190927c7f5168b6c868","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating pool tests\",\"pattern\":\"MAX_DISPATCH_WORKERS|pool_has_room|active_workers|bounded worker|O\\\\(active\",\"path\":\"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_QLsdYc8LXSCIz403S7LRBjCs","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c46734819091c0a2186c1323ee","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating connection telemetry\",\"pattern\":\"stream-sub-attach|DISPATCH_EV|CONN_|conn=.*open|conn=.*close|connection.*count|thread\",\"path\":\"docs/REDISPATCH-STALL-TRIAGE.md; docs/KNOWN-HAZARDS.md; crates/spt-daemon/src/conn.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_roVxHQn7aMgx4dM1EPK84jaD","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2c467388190a198e4c8d6558f79","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading current history tail\",\"path\":\".git/logs/HEAD\",\"selector\":\"1350-1502\"}","call_id":"call_LGnGPRMMCx8mEPJvwr5miMfO","name":"read"},{"id":"fc_089095e18118ee7d016a59d2c4673c81909dd47b76e7597a4a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading branch histories\",\"path\":\".git/logs/refs/remotes/origin/build/redispatch-stall-w1\",\"selector\":\"raw\"}","call_id":"call_motA9ndgx4pKcNhkJTYSpseD","name":"read"}]},"duration":30836.72089999914,"ttft":2345.9603999853134,"contextSnapshot":{"promptTokens":99865,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_RMGQ6h6mddXX52e1Fea2ypSO|fc_089095e18118ee7d016a59d2c4670c8190bcad34988cd320d7","toolName":"grep","startedAt":"2026-07-17T06:59:16.468Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Tracing broker connection threads"},"id":"d61ddaf1","parentId":"150caf5e","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_gGlKkuEfJGvPwZu7PpoqQaj4|fc_089095e18118ee7d016a59d2c467208190b36cb96e4fee8387","toolName":"grep","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Tracing network retirement"},"id":"44c86d61","parentId":"d61ddaf1","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ZVPVTYD7sKL5mb9LBDGtxOIz|fc_089095e18118ee7d016a59d2c467248190a73f6aa7a8a92412","toolName":"grep","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Tracing brain connection lifetime"},"id":"fe8cbbf1","parentId":"44c86d61","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ZcVLBzgvH0bbxqcKVO91JwoA|fc_089095e18118ee7d016a59d2c4672c8190babd9ae9b54be9c5","toolName":"grep","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Tracing attach worker termination"},"id":"2819534e","parentId":"fe8cbbf1","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_QLsdYc8LXSCIz403S7LRBjCs|fc_089095e18118ee7d016a59d2c467308190927c7f5168b6c868","toolName":"grep","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests"},"intent":"Locating pool tests"},"id":"91d89b82","parentId":"2819534e","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_roVxHQn7aMgx4dM1EPK84jaD|fc_089095e18118ee7d016a59d2c46734819091c0a2186c1323ee","toolName":"grep","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":"docs/REDISPATCH-STALL-TRIAGE.md; docs/KNOWN-HAZARDS.md; crates/spt-daemon/src/conn.rs"},"intent":"Locating connection telemetry"},"id":"3786faad","parentId":"91d89b82","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LGnGPRMMCx8mEPJvwr5miMfO|fc_089095e18118ee7d016a59d2c467388190a198e4c8d6558f79","toolName":"read","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":".git/logs/HEAD"},"intent":"Reading current history tail"},"id":"ed901304","parentId":"3786faad","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_motA9ndgx4pKcNhkJTYSpseD|fc_089095e18118ee7d016a59d2c4673c81909dd47b76e7597a4a","toolName":"read","startedAt":"2026-07-17T06:59:16.469Z","args":{"path":".git/logs/refs/remotes/origin/build/redispatch-stall-w1"},"intent":"Reading branch histories"},"id":"74b923b8","parentId":"ed901304","timestamp":"2026-07-17T06:59:16.469Z"}
{"type":"message","id":"cdea0e9a","parentId":"74b923b8","timestamp":"2026-07-17T06:59:16.478Z","message":{"role":"toolResult","toolCallId":"call_ZVPVTYD7sKL5mb9LBDGtxOIz|fc_089095e18118ee7d016a59d2c467248190a73f6aa7a8a92412","toolName":"grep","content":[{"type":"text","text":" 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 21|//!   error — so a lost chunk can never pass silently.\n...\n 83|#[derive(Debug, Clone)]\n*84|pub struct BrainState {\n 85|    /// The session the brain was driving.\n 86|    pub session_id: u64,\n 87|    /// The generation counter (incremented on each handoff).\n...\n 241|                    // Receiver gone (Brain dropped) → stop reading.\n*242|                    if tx.send(framed).is_err() {\n 243|                        break;\n 244|                    }\n 245|                    // A transport error (conn closed / hung up) is terminal —\n...\n 263|/// A live brain attached to a broker.\n*264|pub struct Brain {\n 265|    conn: BrainConn,\n 266|    generation: u64,\n 267|    gen_start_ms: u64,\n...\n 272|    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n*273|    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n 274|    /// Durability across a handoff is the logic layer's job (a re-attaching\n 275|    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n 276|    net_cursors: std::collections::HashMap<u64, u64>,\n...\n 278|    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n*279|    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n 280|    /// `None` until subscribed (or the first event arrives).\n 281|    presence_cursor: Option<u64>,\n 282|    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n...\n 294|    /// When `Some`, every reply read is bounded by this per-call deadline budget\n*295|    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n 296|    /// into the reader-thread form so the deadline can be honored over a channel\n 297|    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n 298|    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n...\n 318|    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n*319|    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n*320|    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n 321|        let mut conn = LocalSocketTransport::connect(name)?;\n 322|        send_hello(&mut conn, Role::Brain)?;\n 323|        Ok(Brain {\n...\n 339|    /// call deadline of `io_timeout`. This is the peer pump's dedicated client —\n*340|    /// every reply read is bounded ([`Brain::read_event_until`] via\n 341|    /// [`Brain::call_deadline`]) so a black-holed peer can never wedge the\n 342|    /// single-threaded pump. The split is done at CONSTRUCTION (not flipped on a\n 343|    /// live `Whole` after the fact — moving the `Stream` out by value to split it\n...\n 346|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*347|    pub fn cold_start_pump(name: &str, now_ms: u64, io_timeout: Duration) -> io::Result<Self> {\n 348|        let mut stream = LocalSocketTransport::connect(name)?;\n 349|        send_hello(&mut stream, Role::Brain)?;\n 350|        Ok(Brain {\n...\n 381|    /// **Test-only (restoration D4-3).** The production resume path is\n*382|    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n 383|    /// never this brain→brain frame; `handoff` survives only for the integration\n 384|    /// tests. See [`BrainState`].\n 385|    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n...\n 389|        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so\n*390|        // `read_event` routes this session through the dedup-below + snap-above\n 391|        // path (matching `resume_sessions`), not the legacy reject-gap path that\n 392|        // turns any at-least-once boundary reorder into a fatal `output gap`.\n 393|        let mut session_cursors = std::collections::HashMap::new();\n...\n 456|    /// **Resume-mode caveat (KNOWN-HAZARDS 6.9).** The `Spawned`-wait loop below\n*457|    /// calls [`Brain::read_event`] on every interleaved frame, so on a *resume-mode*\n 458|    /// brain (multiple sessions in `session_cursors`) an OUTPUT frame for a\n 459|    /// **different** session read here is cursor-processed (its cursor snaps; the\n 460|    /// broker already counted it delivered) and then **discarded** by `_ => continue`\n...\n 463|    /// seat has no other session); the daemon-hosted live-agent event pump must make\n*464|    /// this non-blocking (one demux owns `read_event`, routing each frame to its\n 465|    /// session) or route the frames it reads for other sessions, not drop them.\n 466|    pub fn spawn_session_pid(&mut self, req: SpawnReq) -> io::Result<(u64, Option<u32>)> {\n*467|        self.send(\n 468|            KIND_SPAWN,\n 469|            serde_json::to_value(req).expect(\"SpawnReq serializes\"),\n 470|        )?;\n 471|        loop {\n*472|            match self.read_event()? {\n 473|                BrokerEvent::Spawned { session_id, pid } => {\n 474|                    self.session_id = Some(session_id);\n 475|                    // A resume-mode brain tracks this fresh session per-session via\n*476|                    // `read_event`'s first-sight `or_insert` seed (D4-2b) — no seed\n 477|                    // needed here; a legacy brain (empty map) uses `next_seq`.\n 478|                    return Ok((session_id, pid));\n 479|                }\n...\n 493|        let sid = self.require_session()?;\n*494|        self.send(\n 495|            KIND_INPUT,\n 496|            serde_json::to_value(InputReq {\n 497|                session_id: sid,\n...\n 543|        let sid = self.require_session()?;\n*544|        self.send(\n 545|            KIND_INPUT,\n 546|            serde_json::to_value(InputReq {\n 547|                session_id: sid,\n...\n 559|        let sid = self.require_session()?;\n*560|        self.send(\n 561|            KIND_RESIZE,\n 562|            serde_json::to_value(ResizeReq {\n 563|                session_id: sid,\n...\n 573|        let sid = self.require_session()?;\n*574|        self.send(\n 575|            KIND_KILL,\n 576|            serde_json::to_value(KillReq { session_id: sid }).expect(\"KillReq serializes\"),\n 577|        )\n...\n 587|            }\n*588|            match self.read_event()? {\n 589|                BrokerEvent::Output { bytes, .. } => buf.extend_from_slice(&bytes),\n 590|                BrokerEvent::Exit { .. } => {\n 591|                    return Err(io::Error::new(\n...\n 607|    /// The deadline for ONE IPC call: `now + io_timeout`, computed at call\n*608|    /// entry (a whole `loop { read_event }` shares it, so unrelated events\n 609|    /// dripping in cannot reset it — the total-wait bound). `None` when not in\n 610|    /// pump mode (blocking, unbounded).\n 611|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n...\n 624|\n*625|    pub fn read_event(&mut self) -> io::Result<BrokerEvent> {\n*626|        self.read_event_until(None)\n 627|    }\n 628|\n*629|    /// Like [`Brain::read_event`] but each frame read is bounded by an optional\n 630|    /// wall-clock `deadline` (pump mode passes one; everyone else passes\n 631|    /// `None`). A frame that does not arrive before `deadline` surfaces\n 632|    /// [`io::ErrorKind::TimedOut`] — the pump escalates that to a SUPERVISED\n...\n 635|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*636|    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n 637|        loop {\n 638|            let env = self.read_frame_until(deadline)?;\n 639|            match env.kind.as_str() {\n...\n 876|\n*877|    /// Like [`Self::read_event_until`] but for a PEER-REPLY read (a pump leg\n 878|    /// waiting on a peer's forwarded stream response). A no-progress timeout is\n 879|    /// the peer being connect-then-silent / half-alive — an ORDINARY per-peer\n 880|    /// failure, so it is reclassified OUT of `TimedOut` and the pump's\n...\n 890|    pub fn read_peer_reply_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n*891|        self.read_event_until(deadline)\n 892|            .map_err(reclassify_peer_reply_err)\n 893|    }\n 894|\n...\n 907|        // A resume-mode brain (non-empty map) routes this session's output through\n*908|        // the per-session map via `read_event`'s first-sight `or_insert` seed\n 909|        // (D4-2b); a legacy brain (empty map) uses the `next_seq` set above. The\n 910|        // resume-mode dedup-cursor reset for an explicit re-subscribe lives in\n 911|        // `subscribe_with` (P1c, shared by `attach`/`attach_as`).\n...\n 975|    /// `session_cursors` EMPTY and runs the LEGACY reject-gap path\n*976|    /// ([`Brain::read_event`] :624/:628), which FATALS the forward gap →\n 977|    /// `serve_attach` returns → forwarding stops → the operator sees nothing\n 978|    /// (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty;\n 979|    /// MASKED on Windows ConPTY, which floods slower so the ring never rolls far\n...\n 1016|    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,\n*1017|    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send\n 1018|    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is\n 1019|    /// at-least-once; input/effects stay exactly-once via the broker-owned\n 1020|    /// `EffectJournal`. The generation is **not** touched — this is a cold start\n...\n 1023|    /// Each session is re-attached as a **VIEWER**, never a controller: a caller that\n*1024|    /// DRAINS the resumed output (`Brain::read_event`) gets output-continuity from its\n 1025|    /// cursor; a viewer never seizes/stall-evicts a controller slot (UPDATE-WEDGE-2,\n 1026|    /// REQ-BRAIN-RESUME-NO-CONTROL-STEAL). Genuinely daemon-brain-driven sessions (the\n 1027|    /// live-agent adapter future) will re-attach as Control on a drained carrier; that\n...\n 1117|    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {\n*1118|        self.send(KIND_NET_STATUS, serde_json::Value::Null)?;\n 1119|        loop {\n*1120|            match self.read_event()? {\n 1121|                BrokerEvent::NetStatus(s) => return Ok(s),\n 1122|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1123|                _ => continue,\n...\n 1138|    pub fn broker_image_version(&mut self) -> io::Result<Option<String>> {\n*1139|        self.send(KIND_BROKER_IMAGE, serde_json::Value::Null)?;\n 1140|        loop {\n 1141|            let env = self.read_frame_until(None)?;\n 1142|            match env.kind.as_str() {\n...\n 1164|    pub fn stall_evicts(&mut self) -> io::Result<Option<(u64, u64)>> {\n*1165|        self.send(KIND_STALL_EVICTS, serde_json::Value::Null)?;\n 1166|        loop {\n 1167|            let env = self.read_frame_until(None)?;\n 1168|            match env.kind.as_str() {\n...\n 1188|    pub fn adapter_apply(&mut self, req: &AdapterApplyReq) -> io::Result<()> {\n*1189|        self.send(\n 1190|            KIND_ADAPTER_APPLY,\n 1191|            serde_json::to_value(req).expect(\"AdapterApplyReq serializes\"),\n 1192|        )?;\n...\n 1219|    ) -> io::Result<PairJoinReply> {\n*1220|        self.send(\n 1221|            KIND_PAIR_JOIN,\n 1222|            serde_json::to_value(PairJoinReq {\n 1223|                subnet: subnet.to_string(),\n...\n 1229|        loop {\n*1230|            match self.read_event()? {\n 1231|                BrokerEvent::PairJoined(r) => return Ok(r),\n 1232|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1233|                _ => continue,\n...\n 1245|    pub fn pair_meet(&mut self, subnet: &str, verbose: bool) -> io::Result<MetOutcome> {\n*1246|        self.send(\n 1247|            KIND_PAIR_MEET,\n 1248|            serde_json::to_value(PairMeetReq {\n 1249|                subnet: subnet.to_string(),\n...\n 1254|        loop {\n*1255|            match self.read_event()? {\n 1256|                BrokerEvent::MetMember(m) => return Ok(MetOutcome::Met(m)),\n 1257|                BrokerEvent::PairJoined(r) => return Ok(MetOutcome::Failed(r)),\n 1258|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n...\n 1269|    pub fn pair_submit_code(&mut self, session_id: u64, code: &str) -> io::Result<PairJoinReply> {\n*1270|        self.send(\n 1271|            KIND_PAIR_CODE_SUBMIT,\n 1272|            serde_json::to_value(PairCodeSubmit {\n 1273|                session_id,\n...\n 1278|        loop {\n*1279|            match self.read_event()? {\n 1280|                BrokerEvent::PairJoined(r) => return Ok(r),\n 1281|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1282|                _ => continue,\n...\n 1297|    ) -> io::Result<NetDialed> {\n*1298|        self.send(\n 1299|            KIND_NET_DIAL,\n 1300|            serde_json::to_value(NetDialReq {\n 1301|                addr,\n...\n 1308|        loop {\n*1309|            match self.read_event_until(deadline)? {\n 1310|                BrokerEvent::NetDialed(d) => return Ok(d),\n 1311|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1312|                _ => continue,\n...\n 1327|    pub fn submit_dial(&mut self, addr: serde_json::Value) -> io::Result<()> {\n*1328|        self.send(\n 1329|            KIND_NET_DIAL_SUBMIT,\n 1330|            serde_json::to_value(NetDialReq {\n 1331|                addr,\n...\n 1338|        loop {\n*1339|            match self.read_event_until(deadline)? {\n 1340|                BrokerEvent::NetDialSubmitted => return Ok(()),\n 1341|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1342|                _ => continue,\n...\n 1356|    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {\n*1357|        self.send(KIND_NET_DIAL_LOOPBACK, serde_json::Value::Null)?;\n 1358|        let deadline = self.call_deadline();\n 1359|        loop {\n*1360|            match self.read_event_until(deadline)? {\n 1361|                BrokerEvent::NetDialed(d) => return Ok(d),\n 1362|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1363|                _ => continue,\n...\n 1377|    ) -> io::Result<NetStreamOpened> {\n*1378|        self.send(\n 1379|            KIND_NET_STREAM_OPEN,\n 1380|            serde_json::to_value(NetStreamOpenReq {\n 1381|                conn_id,\n...\n 1388|        loop {\n*1389|            match self.read_event_until(deadline)? {\n 1390|                BrokerEvent::NetStreamOpened(ev) => return Ok(ev),\n 1391|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1392|                _ => continue,\n...\n 1401|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1402|    pub fn net_stream_send(\n 1403|        &mut self,\n 1404|        stream_id: u64,\n 1405|        bytes: &[u8],\n...\n 1408|    ) -> io::Result<Option<NetSent>> {\n*1409|        self.send(\n 1410|            KIND_NET_STREAM_SEND,\n 1411|            serde_json::to_value(NetStreamSendReq {\n 1412|                stream_id,\n...\n 1425|        loop {\n*1426|            match self.read_event_until(deadline)? {\n 1427|                BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),\n 1428|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1429|                _ => continue,\n...\n 1436|    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n*1437|        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438|        loop {\n*1439|            match self.read_event()? {\n 1440|                BrokerEvent::NetStreams(ev) => return Ok(ev),\n 1441|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1442|                _ => continue,\n...\n 1454|    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n*1455|        self.send(\n 1456|            KIND_NET_STREAM_RETIRE,\n 1457|            serde_json::to_value(NetStreamRetireReq { stream_id })\n 1458|                .expect(\"NetStreamRetireReq serializes\"),\n...\n 1460|        loop {\n*1461|            match self.read_event()? {\n 1462|                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n 1463|                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n 1464|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n...\n 1480|    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n*1481|        self.send(\n 1482|            KIND_NET_STREAM_OPENER,\n 1483|            serde_json::to_value(NetStreamOpenerReq { stream_id })\n 1484|                .expect(\"NetStreamOpenerReq serializes\"),\n...\n 1486|        loop {\n*1487|            match self.read_event()? {\n 1488|                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n 1489|                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n 1490|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n...\n 1518|    pub fn request_brain_restart(&mut self) -> io::Result<bool> {\n*1519|        self.send(KIND_BRAIN_RESTART, serde_json::Value::Null)?;\n 1520|        loop {\n*1521|            match self.read_event()? {\n 1522|                BrokerEvent::Other(env) if env.kind == KIND_BRAIN_RESTARTED => {\n 1523|                    let reply: BrainRestarted = serde_json::from_value(env.payload)\n 1524|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n...\n 1550|    ) -> io::Result<(bool, bool)> {\n*1551|        self.send(\n 1552|            KIND_ENDPOINT_INPUT,\n 1553|            serde_json::to_value(EndpointInputReq {\n 1554|                endpoint: endpoint.to_string(),\n...\n 1560|        loop {\n*1561|            match self.read_event()? {\n 1562|                BrokerEvent::Other(env) if env.kind == KIND_ENDPOINT_INJECTED => {\n 1563|                    let reply: EndpointInjected = serde_json::from_value(env.payload)\n 1564|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n...\n 1575|    pub fn sessions(&mut self) -> io::Result<SessionsReply> {\n*1576|        self.send(KIND_SESSIONS, serde_json::Value::Null)?;\n 1577|        loop {\n*1578|            match self.read_event()? {\n 1579|                BrokerEvent::Sessions(ev) => return Ok(ev),\n 1580|                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1581|                _ => continue,\n...\n 1591|        self.net_cursors.insert(stream_id, from_seq);\n*1592|        self.send(\n 1593|            KIND_NET_STREAM_SUBSCRIBE,\n 1594|            serde_json::to_value(NetStreamSubscribeReq {\n 1595|                stream_id,\n...\n 1613|        self.presence_cursor = Some(from_seq);\n*1614|        self.send(\n 1615|            KIND_NET_PRESENCE_SUBSCRIBE,\n 1616|            serde_json::to_value(NetPresenceSubscribeReq { from_seq })\n 1617|                .expect(\"NetPresenceSubscribeReq serializes\"),\n...\n 1640|        self.presence_cursor = None; // baseline on the first live event (no floor)\n*1641|        self.send(\n 1642|            KIND_NET_PRESENCE_SUBSCRIBE,\n 1643|            serde_json::to_value(NetPresenceSubscribeReq { from_seq: u64::MAX })\n 1644|                .expect(\"NetPresenceSubscribeReq serializes\"),\n...\n 1684|    pub fn detach_session_gen(&mut self, session_id: u64, gen: Option<u64>) -> io::Result<()> {\n*1685|        self.send(\n 1686|            KIND_UNSUBSCRIBE,\n 1687|            serde_json::to_value(UnsubscribeReq { session_id, gen })\n 1688|                .expect(\"UnsubscribeReq serializes\"),\n...\n 1707|        // re-delivered (and the operator's render cursor dedups the overlap),\n*1708|        // never lost. WHY THIS, NOT just `read_event`'s `or_insert`: on a re-serve,\n 1709|        // `serve_attach` may CONSUME a handoff-replay frame at the K boundary\n 1710|        // BEFORE it is `attached` (the frame is dropped by the `if attached` gate\n 1711|        // but the snap-above cursor still advanced past K); `attach_as(sid, 0)`\n...\n 1721|        }\n*1722|        self.send(\n 1723|            KIND_SUBSCRIBE,\n 1724|            serde_json::to_value(SubscribeReq {\n 1725|                session_id,\n...\n 1734|\n*1735|    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n 1736|        let env = Envelope::new(kind, payload);\n 1737|        match &mut self.conn {\n 1738|            BrainConn::Whole(stream) => write_frame(stream, &env),\n...\n 1841|    use crate::msg::output_envelope;\n*1842|    use crate::transport::DaemonTransport;\n 1843|    use std::sync::atomic::{AtomicU32, Ordering};\n 1844|    use std::sync::mpsc::{sync_channel, SyncSender};\n 1845|\n...\n 1895|    /// (subscribe frames), and writes whatever envelopes the test feeds it. This is\n*1896|    /// the brain's REAL `read_event` / `attach_skip_to_live` dedup state machine\n 1897|    /// exercised end-to-end over the real codec — the cheapest harness that still\n 1898|    /// drives the production paths (the integration tests in `tests/` use a real\n 1899|    /// broker; here we isolate the brain's dedup decision).\n...\n 1907|    /// Stand up the stub + a connected cold-start `Brain`. The stub binds, the brain\n*1908|    /// connects (cold_start sends the hello), the stub recv_hello's it, then loops:\n 1909|    /// it owns the server `Stream` and writes any envelope the test feeds via `feed`.\n 1910|    /// (We never need to READ the brain's writes for these tests — the subscribe\n 1911|    /// `attach_skip_to_live` sends is harmlessly left in the OS socket buffer; the\n...\n 1916|\n*1917|        // The brain connects on a thread (cold_start blocks on connect+hello).\n 1918|        let connect_name = name.clone();\n 1919|        let brain_handle =\n*1920|            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect(\"brain cold_start\"));\n 1921|\n 1922|        let mut server = listener.accept().expect(\"accept the brain\");\n 1923|        crate::transport::recv_hello(&mut server, Role::Brain).expect(\"stub recv_hello\");\n...\n 1963|        // Contiguous seq 0 is accepted and advances the cursor.\n*1964|        stub.feed.send(output_envelope(sid, 0, b\"hello\")).unwrap();\n*1965|        match brain.read_event().expect(\"seq 0 accepted\") {\n 1966|            BrokerEvent::Output { seq, bytes, .. } => {\n 1967|                assert_eq!(seq, 0);\n 1968|                assert_eq!(bytes, b\"hello\");\n...\n 1974|        // path — a cold brain treats a skipped seq as a lost chunk, never silently.\n*1975|        stub.feed.send(output_envelope(sid, 5, b\"jumped\")).unwrap();\n*1976|        let err = brain.read_event().expect_err(\"a cold brain rejects a forward gap\");\n 1977|        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 1978|        assert!(\n 1979|            err.to_string().contains(\"output gap\"),\n...\n 2019|            stub.feed\n*2020|                .send(output_envelope(sid, seq, format!(\"c{seq}\").as_bytes()))\n 2021|                .unwrap();\n*2022|            match brain.read_event().expect(\"contiguous controller frame accepted\") {\n 2023|                BrokerEvent::Output { seq: got, .. } => assert_eq!(got, seq),\n 2024|                other => panic!(\"expected Output({seq}), got {other:?}\"),\n 2025|            }\n...\n 2036|        // frozen `delivered_through`); this IS the resume floor.\n*2037|        stub.feed.send(output_envelope(sid, 10, b\"gapped\")).unwrap();\n 2038|        let err = brain\n*2039|            .read_event()\n 2040|            .expect_err(\"the strict controller path fatals a forward gap\");\n 2041|        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 2042|        assert!(\n...\n 2060|            stub.feed\n*2061|                .send(output_envelope(sid, seq, format!(\"r{seq}\").as_bytes()))\n 2062|                .unwrap();\n*2063|            match brain.read_event().expect(\"ring-replayed floor frame accepted\") {\n 2064|                BrokerEvent::Output { seq: got, .. } => assert_eq!(got, seq),\n 2065|                other => panic!(\"expected re-fetched Output({seq}), got {other:?}\"),\n 2066|            }\n...\n 2082|            .expect(\"controller second resume\");\n*2083|        stub.feed.send(output_envelope(sid, 9, b\"rolled\")).unwrap();\n 2084|        let err = brain\n*2085|            .read_event()\n 2086|            .expect_err(\"the ring rolled past the floor — still a gap\");\n 2087|        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 2088|        assert!(err.to_string().contains(\"output gap\"));\n...\n 2107|    ///     → the map stays empty → the forward jump hits the legacy reject-gap path\n*2108|    ///     → `read_event` errors `output gap` instead of accepting → RED.\n 2109|    ///   - Leave `next_seq = u64::MAX` (the from_seq) instead of resetting to 0 AND\n 2110|    ///     route through the legacy path → every live seq < MAX is dedup-dropped →\n*2111|    ///     `read_event` would block (no event) → the test's bounded reader times out.\n 2112|    ///   - Snap-above mis-implemented as reject-gap (`seq > cursor` → Err) → RED.\n 2113|    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n 2114|    #[test]\n...\n 2134|        // jump is only ever a legitimate post-eviction ring-floor clamp).\n*2135|        stub.feed.send(output_envelope(sid, 12, b\"LIVE-A\")).unwrap();\n*2136|        match brain.read_event().expect(\"post-eviction forward jump accepted\") {\n 2137|            BrokerEvent::Output { seq, bytes, .. } => {\n 2138|                assert_eq!(seq, 12, \"the forward-jumped live seq is accepted, not rejected\");\n 2139|                assert_eq!(bytes, b\"LIVE-A\");\n...\n 2150|        // re-send (seq 12, already delivered) is DEDUPED (not a gap, not re-emitted).\n*2151|        stub.feed.send(output_envelope(sid, 13, b\"LIVE-B\")).unwrap();\n*2152|        stub.feed.send(output_envelope(sid, 12, b\"DUP\")).unwrap(); // dedup\n*2153|        stub.feed.send(output_envelope(sid, 14, b\"LIVE-C\")).unwrap();\n 2154|        let mut accepted = Vec::new();\n 2155|        for _ in 0..2 {\n*2156|            match brain.read_event().expect(\"subsequent live frames\") {\n 2157|                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),\n 2158|                other => panic!(\"expected Output, got {other:?}\"),\n 2159|            }\n...\n 2175|    /// dedup BELOW `from_seq`, snap ABOVE a forward ring-roll gap. Without arming,\n*2176|    /// the empty `session_cursors` runs the LEGACY reject-gap path → `read_event`\n 2177|    /// FATALS the forward gap (`output gap: got 4504 want 100`) → forwarding stops →\n 2178|    /// the operator sees nothing (wedged viewer, Linux forkpty).\n 2179|    ///\n...\n 2182|    ///     `attach_as_viewer_snap` → the map stays empty → the forward gap hits the\n*2183|    ///     legacy reject-gap path → `read_event` errors `output gap` → RED.\n 2184|    ///   - Seed at 0 instead of `from_seq` → the dedup-below boundary is WRONG: a\n 2185|    ///     seq in [0, from_seq) that should DEDUP (already delivered before this\n 2186|    ///     attach floor) would instead be ACCEPTED below the intended floor. The\n...\n 2211|        // `output gap: got 4504 want 100`). Snap-above must ACCEPT it.\n*2212|        stub.feed.send(output_envelope(sid, 4504, b\"ROLLED\")).unwrap();\n*2213|        match brain.read_event().expect(\"pre-eviction forward ring-roll gap accepted\") {\n 2214|            BrokerEvent::Output { seq, bytes, .. } => {\n 2215|                assert_eq!(seq, 4504, \"the forward-jumped live seq is accepted, not rejected\");\n 2216|                assert_eq!(bytes, b\"ROLLED\");\n...\n 2228|        // gap, not re-emitted), seq 4506 accepted.\n*2229|        stub.feed.send(output_envelope(sid, 4505, b\"LIVE-B\")).unwrap();\n*2230|        stub.feed.send(output_envelope(sid, 4504, b\"DUP\")).unwrap(); // dedup below cursor\n*2231|        stub.feed.send(output_envelope(sid, 4506, b\"LIVE-C\")).unwrap();\n 2232|        let mut accepted = Vec::new();\n 2233|        for _ in 0..2 {\n*2234|            match brain.read_event().expect(\"subsequent live frames\") {\n 2235|                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),\n 2236|                other => panic!(\"expected Output, got {other:?}\"),\n 2237|            }"}],"details":{"scopePath":"crates/spt-daemon/src/brain.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":108,"fileCount":1,"files":["crates/spt-daemon/src/brain.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brain.rs","count":108}],"truncated":false,"displayContent":"   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   21│//!   error — so a lost chunk can never pass silently.\n     │...\n   83│#[derive(Debug, Clone)]\n  *84│pub struct BrainState {\n   85│    /// The session the brain was driving.\n   86│    pub session_id: u64,\n   87│    /// The generation counter (incremented on each handoff).\n     │...\n  241│                    // Receiver gone (Brain dropped) → stop reading.\n *242│                    if tx.send(framed).is_err() {\n  243│                        break;\n  244│                    }\n  245│                    // A transport error (conn closed / hung up) is terminal —\n     │...\n  263│/// A live brain attached to a broker.\n *264│pub struct Brain {\n  265│    conn: BrainConn,\n  266│    generation: u64,\n  267│    gen_start_ms: u64,\n     │...\n  272│    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n *273│    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n  274│    /// Durability across a handoff is the logic layer's job (a re-attaching\n  275│    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n  276│    net_cursors: std::collections::HashMap<u64, u64>,\n     │...\n  278│    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n *279│    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n  280│    /// `None` until subscribed (or the first event arrives).\n  281│    presence_cursor: Option<u64>,\n  282│    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n     │...\n  294│    /// When `Some`, every reply read is bounded by this per-call deadline budget\n *295│    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n  296│    /// into the reader-thread form so the deadline can be honored over a channel\n  297│    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n  298│    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n     │...\n  318│    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n *319│    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n *320│    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n  321│        let mut conn = LocalSocketTransport::connect(name)?;\n  322│        send_hello(&mut conn, Role::Brain)?;\n  323│        Ok(Brain {\n     │...\n  339│    /// call deadline of `io_timeout`. This is the peer pump's dedicated client —\n *340│    /// every reply read is bounded ([`Brain::read_event_until`] via\n  341│    /// [`Brain::call_deadline`]) so a black-holed peer can never wedge the\n  342│    /// single-threaded pump. The split is done at CONSTRUCTION (not flipped on a\n  343│    /// live `Whole` after the fact — moving the `Stream` out by value to split it\n     │...\n  346│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n *347│    pub fn cold_start_pump(name: &str, now_ms: u64, io_timeout: Duration) -> io::Result<Self> {\n  348│        let mut stream = LocalSocketTransport::connect(name)?;\n  349│        send_hello(&mut stream, Role::Brain)?;\n  350│        Ok(Brain {\n     │...\n  381│    /// **Test-only (restoration D4-3).** The production resume path is\n *382│    /// [`Brain::cold_start`] + [`Brain::resume_sessions`] (broker cursor-of-record),\n  383│    /// never this brain→brain frame; `handoff` survives only for the integration\n  384│    /// tests. See [`BrainState`].\n  385│    // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n     │...\n  389│        // DEFENSE-IN-DEPTH (P1c): seed the dedup cursor at the resume point so\n *390│        // `read_event` routes this session through the dedup-below + snap-above\n  391│        // path (matching `resume_sessions`), not the legacy reject-gap path that\n  392│        // turns any at-least-once boundary reorder into a fatal `output gap`.\n  393│        let mut session_cursors = std::collections::HashMap::new();\n     │...\n  456│    /// **Resume-mode caveat (KNOWN-HAZARDS 6.9).** The `Spawned`-wait loop below\n *457│    /// calls [`Brain::read_event`] on every interleaved frame, so on a *resume-mode*\n  458│    /// brain (multiple sessions in `session_cursors`) an OUTPUT frame for a\n  459│    /// **different** session read here is cursor-processed (its cursor snaps; the\n  460│    /// broker already counted it delivered) and then **discarded** by `_ => continue`\n     │...\n  463│    /// seat has no other session); the daemon-hosted live-agent event pump must make\n *464│    /// this non-blocking (one demux owns `read_event`, routing each frame to its\n  465│    /// session) or route the frames it reads for other sessions, not drop them.\n  466│    pub fn spawn_session_pid(&mut self, req: SpawnReq) -> io::Result<(u64, Option<u32>)> {\n *467│        self.send(\n  468│            KIND_SPAWN,\n  469│            serde_json::to_value(req).expect(\"SpawnReq serializes\"),\n  470│        )?;\n  471│        loop {\n *472│            match self.read_event()? {\n  473│                BrokerEvent::Spawned { session_id, pid } => {\n  474│                    self.session_id = Some(session_id);\n  475│                    // A resume-mode brain tracks this fresh session per-session via\n *476│                    // `read_event`'s first-sight `or_insert` seed (D4-2b) — no seed\n  477│                    // needed here; a legacy brain (empty map) uses `next_seq`.\n  478│                    return Ok((session_id, pid));\n  479│                }\n     │...\n  493│        let sid = self.require_session()?;\n *494│        self.send(\n  495│            KIND_INPUT,\n  496│            serde_json::to_value(InputReq {\n  497│                session_id: sid,\n     │...\n  543│        let sid = self.require_session()?;\n *544│        self.send(\n  545│            KIND_INPUT,\n  546│            serde_json::to_value(InputReq {\n  547│                session_id: sid,\n     │...\n  559│        let sid = self.require_session()?;\n *560│        self.send(\n  561│            KIND_RESIZE,\n  562│            serde_json::to_value(ResizeReq {\n  563│                session_id: sid,\n     │...\n  573│        let sid = self.require_session()?;\n *574│        self.send(\n  575│            KIND_KILL,\n  576│            serde_json::to_value(KillReq { session_id: sid }).expect(\"KillReq serializes\"),\n  577│        )\n     │...\n  587│            }\n *588│            match self.read_event()? {\n  589│                BrokerEvent::Output { bytes, .. } => buf.extend_from_slice(&bytes),\n  590│                BrokerEvent::Exit { .. } => {\n  591│                    return Err(io::Error::new(\n     │...\n  607│    /// The deadline for ONE IPC call: `now + io_timeout`, computed at call\n *608│    /// entry (a whole `loop { read_event }` shares it, so unrelated events\n  609│    /// dripping in cannot reset it — the total-wait bound). `None` when not in\n  610│    /// pump mode (blocking, unbounded).\n  611│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n     │...\n  624│\n *625│    pub fn read_event(&mut self) -> io::Result<BrokerEvent> {\n *626│        self.read_event_until(None)\n  627│    }\n  628│\n *629│    /// Like [`Brain::read_event`] but each frame read is bounded by an optional\n  630│    /// wall-clock `deadline` (pump mode passes one; everyone else passes\n  631│    /// `None`). A frame that does not arrive before `deadline` surfaces\n  632│    /// [`io::ErrorKind::TimedOut`] — the pump escalates that to a SUPERVISED\n     │...\n  635│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n *636│    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n  637│        loop {\n  638│            let env = self.read_frame_until(deadline)?;\n  639│            match env.kind.as_str() {\n     │...\n  876│\n *877│    /// Like [`Self::read_event_until`] but for a PEER-REPLY read (a pump leg\n  878│    /// waiting on a peer's forwarded stream response). A no-progress timeout is\n  879│    /// the peer being connect-then-silent / half-alive — an ORDINARY per-peer\n  880│    /// failure, so it is reclassified OUT of `TimedOut` and the pump's\n     │...\n  890│    pub fn read_peer_reply_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n *891│        self.read_event_until(deadline)\n  892│            .map_err(reclassify_peer_reply_err)\n  893│    }\n  894│\n     │...\n  907│        // A resume-mode brain (non-empty map) routes this session's output through\n *908│        // the per-session map via `read_event`'s first-sight `or_insert` seed\n  909│        // (D4-2b); a legacy brain (empty map) uses the `next_seq` set above. The\n  910│        // resume-mode dedup-cursor reset for an explicit re-subscribe lives in\n  911│        // `subscribe_with` (P1c, shared by `attach`/`attach_as`).\n     │...\n  975│    /// `session_cursors` EMPTY and runs the LEGACY reject-gap path\n *976│    /// ([`Brain::read_event`] :624/:628), which FATALS the forward gap →\n  977│    /// `serve_attach` returns → forwarding stops → the operator sees nothing\n  978│    /// (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty;\n  979│    /// MASKED on Windows ConPTY, which floods slower so the ring never rolls far\n     │...\n 1016│    /// Each session is seeded into [`Self::session_cursors`] at its `resume_seq`,\n*1017│    /// so [`Brain::read_event`] dedups the broker's at-least-once boundary re-send\n 1018│    /// and accepts a post-eviction ring-floor jump (no reject-gap). Output is\n 1019│    /// at-least-once; input/effects stay exactly-once via the broker-owned\n 1020│    /// `EffectJournal`. The generation is **not** touched — this is a cold start\n     │...\n 1023│    /// Each session is re-attached as a **VIEWER**, never a controller: a caller that\n*1024│    /// DRAINS the resumed output (`Brain::read_event`) gets output-continuity from its\n 1025│    /// cursor; a viewer never seizes/stall-evicts a controller slot (UPDATE-WEDGE-2,\n 1026│    /// REQ-BRAIN-RESUME-NO-CONTROL-STEAL). Genuinely daemon-brain-driven sessions (the\n 1027│    /// live-agent adapter future) will re-attach as Control on a drained carrier; that\n     │...\n 1117│    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {\n*1118│        self.send(KIND_NET_STATUS, serde_json::Value::Null)?;\n 1119│        loop {\n*1120│            match self.read_event()? {\n 1121│                BrokerEvent::NetStatus(s) => return Ok(s),\n 1122│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1123│                _ => continue,\n     │...\n 1138│    pub fn broker_image_version(&mut self) -> io::Result<Option<String>> {\n*1139│        self.send(KIND_BROKER_IMAGE, serde_json::Value::Null)?;\n 1140│        loop {\n 1141│            let env = self.read_frame_until(None)?;\n 1142│            match env.kind.as_str() {\n     │...\n 1164│    pub fn stall_evicts(&mut self) -> io::Result<Option<(u64, u64)>> {\n*1165│        self.send(KIND_STALL_EVICTS, serde_json::Value::Null)?;\n 1166│        loop {\n 1167│            let env = self.read_frame_until(None)?;\n 1168│            match env.kind.as_str() {\n     │...\n 1188│    pub fn adapter_apply(&mut self, req: &AdapterApplyReq) -> io::Result<()> {\n*1189│        self.send(\n 1190│            KIND_ADAPTER_APPLY,\n 1191│            serde_json::to_value(req).expect(\"AdapterApplyReq serializes\"),\n 1192│        )?;\n     │...\n 1219│    ) -> io::Result<PairJoinReply> {\n*1220│        self.send(\n 1221│            KIND_PAIR_JOIN,\n 1222│            serde_json::to_value(PairJoinReq {\n 1223│                subnet: subnet.to_string(),\n     │...\n 1229│        loop {\n*1230│            match self.read_event()? {\n 1231│                BrokerEvent::PairJoined(r) => return Ok(r),\n 1232│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1233│                _ => continue,\n     │...\n 1245│    pub fn pair_meet(&mut self, subnet: &str, verbose: bool) -> io::Result<MetOutcome> {\n*1246│        self.send(\n 1247│            KIND_PAIR_MEET,\n 1248│            serde_json::to_value(PairMeetReq {\n 1249│                subnet: subnet.to_string(),\n     │...\n 1254│        loop {\n*1255│            match self.read_event()? {\n 1256│                BrokerEvent::MetMember(m) => return Ok(MetOutcome::Met(m)),\n 1257│                BrokerEvent::PairJoined(r) => return Ok(MetOutcome::Failed(r)),\n 1258│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n     │...\n 1269│    pub fn pair_submit_code(&mut self, session_id: u64, code: &str) -> io::Result<PairJoinReply> {\n*1270│        self.send(\n 1271│            KIND_PAIR_CODE_SUBMIT,\n 1272│            serde_json::to_value(PairCodeSubmit {\n 1273│                session_id,\n     │...\n 1278│        loop {\n*1279│            match self.read_event()? {\n 1280│                BrokerEvent::PairJoined(r) => return Ok(r),\n 1281│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1282│                _ => continue,\n     │...\n 1297│    ) -> io::Result<NetDialed> {\n*1298│        self.send(\n 1299│            KIND_NET_DIAL,\n 1300│            serde_json::to_value(NetDialReq {\n 1301│                addr,\n     │...\n 1308│        loop {\n*1309│            match self.read_event_until(deadline)? {\n 1310│                BrokerEvent::NetDialed(d) => return Ok(d),\n 1311│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1312│                _ => continue,\n     │...\n 1327│    pub fn submit_dial(&mut self, addr: serde_json::Value) -> io::Result<()> {\n*1328│        self.send(\n 1329│            KIND_NET_DIAL_SUBMIT,\n 1330│            serde_json::to_value(NetDialReq {\n 1331│                addr,\n     │...\n 1338│        loop {\n*1339│            match self.read_event_until(deadline)? {\n 1340│                BrokerEvent::NetDialSubmitted => return Ok(()),\n 1341│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1342│                _ => continue,\n     │...\n 1356│    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {\n*1357│        self.send(KIND_NET_DIAL_LOOPBACK, serde_json::Value::Null)?;\n 1358│        let deadline = self.call_deadline();\n 1359│        loop {\n*1360│            match self.read_event_until(deadline)? {\n 1361│                BrokerEvent::NetDialed(d) => return Ok(d),\n 1362│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1363│                _ => continue,\n     │...\n 1377│    ) -> io::Result<NetStreamOpened> {\n*1378│        self.send(\n 1379│            KIND_NET_STREAM_OPEN,\n 1380│            serde_json::to_value(NetStreamOpenReq {\n 1381│                conn_id,\n     │...\n 1388│        loop {\n*1389│            match self.read_event_until(deadline)? {\n 1390│                BrokerEvent::NetStreamOpened(ev) => return Ok(ev),\n 1391│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1392│                _ => continue,\n     │...\n 1401│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1402│    pub fn net_stream_send(\n 1403│        &mut self,\n 1404│        stream_id: u64,\n 1405│        bytes: &[u8],\n     │...\n 1408│    ) -> io::Result<Option<NetSent>> {\n*1409│        self.send(\n 1410│            KIND_NET_STREAM_SEND,\n 1411│            serde_json::to_value(NetStreamSendReq {\n 1412│                stream_id,\n     │...\n 1425│        loop {\n*1426│            match self.read_event_until(deadline)? {\n 1427│                BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),\n 1428│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1429│                _ => continue,\n     │...\n 1436│    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n*1437│        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438│        loop {\n*1439│            match self.read_event()? {\n 1440│                BrokerEvent::NetStreams(ev) => return Ok(ev),\n 1441│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1442│                _ => continue,\n     │...\n 1454│    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n*1455│        self.send(\n 1456│            KIND_NET_STREAM_RETIRE,\n 1457│            serde_json::to_value(NetStreamRetireReq { stream_id })\n 1458│                .expect(\"NetStreamRetireReq serializes\"),\n     │...\n 1460│        loop {\n*1461│            match self.read_event()? {\n 1462│                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n 1463│                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n 1464│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n     │...\n 1480│    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n*1481│        self.send(\n 1482│            KIND_NET_STREAM_OPENER,\n 1483│            serde_json::to_value(NetStreamOpenerReq { stream_id })\n 1484│                .expect(\"NetStreamOpenerReq serializes\"),\n     │...\n 1486│        loop {\n*1487│            match self.read_event()? {\n 1488│                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n 1489│                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n 1490│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n     │...\n 1518│    pub fn request_brain_restart(&mut self) -> io::Result<bool> {\n*1519│        self.send(KIND_BRAIN_RESTART, serde_json::Value::Null)?;\n 1520│        loop {\n*1521│            match self.read_event()? {\n 1522│                BrokerEvent::Other(env) if env.kind == KIND_BRAIN_RESTARTED => {\n 1523│                    let reply: BrainRestarted = serde_json::from_value(env.payload)\n 1524│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n     │...\n 1550│    ) -> io::Result<(bool, bool)> {\n*1551│        self.send(\n 1552│            KIND_ENDPOINT_INPUT,\n 1553│            serde_json::to_value(EndpointInputReq {\n 1554│                endpoint: endpoint.to_string(),\n     │...\n 1560│        loop {\n*1561│            match self.read_event()? {\n 1562│                BrokerEvent::Other(env) if env.kind == KIND_ENDPOINT_INJECTED => {\n 1563│                    let reply: EndpointInjected = serde_json::from_value(env.payload)\n 1564│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n     │...\n 1575│    pub fn sessions(&mut self) -> io::Result<SessionsReply> {\n*1576│        self.send(KIND_SESSIONS, serde_json::Value::Null)?;\n 1577│        loop {\n*1578│            match self.read_event()? {\n 1579│                BrokerEvent::Sessions(ev) => return Ok(ev),\n 1580│                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n 1581│                _ => continue,\n     │...\n 1591│        self.net_cursors.insert(stream_id, from_seq);\n*1592│        self.send(\n 1593│            KIND_NET_STREAM_SUBSCRIBE,\n 1594│            serde_json::to_value(NetStreamSubscribeReq {\n 1595│                stream_id,\n     │...\n 1613│        self.presence_cursor = Some(from_seq);\n*1614│        self.send(\n 1615│            KIND_NET_PRESENCE_SUBSCRIBE,\n 1616│            serde_json::to_value(NetPresenceSubscribeReq { from_seq })\n 1617│                .expect(\"NetPresenceSubscribeReq serializes\"),\n     │...\n 1640│        self.presence_cursor = None; // baseline on the first live event (no floor)\n*1641│        self.send(\n 1642│            KIND_NET_PRESENCE_SUBSCRIBE,\n 1643│            serde_json::to_value(NetPresenceSubscribeReq { from_seq: u64::MAX })\n 1644│                .expect(\"NetPresenceSubscribeReq serializes\"),\n     │...\n 1684│    pub fn detach_session_gen(&mut self, session_id: u64, gen: Option<u64>) -> io::Result<()> {\n*1685│        self.send(\n 1686│            KIND_UNSUBSCRIBE,\n 1687│            serde_json::to_value(UnsubscribeReq { session_id, gen })\n 1688│                .expect(\"UnsubscribeReq serializes\"),\n     │...\n 1707│        // re-delivered (and the operator's render cursor dedups the overlap),\n*1708│        // never lost. WHY THIS, NOT just `read_event`'s `or_insert`: on a re-serve,\n 1709│        // `serve_attach` may CONSUME a handoff-replay frame at the K boundary\n 1710│        // BEFORE it is `attached` (the frame is dropped by the `if attached` gate\n 1711│        // but the snap-above cursor still advanced past K); `attach_as(sid, 0)`\n     │...\n 1721│        }\n*1722│        self.send(\n 1723│            KIND_SUBSCRIBE,\n 1724│            serde_json::to_value(SubscribeReq {\n 1725│                session_id,\n     │...\n 1734│\n*1735│    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n 1736│        let env = Envelope::new(kind, payload);\n 1737│        match &mut self.conn {\n 1738│            BrainConn::Whole(stream) => write_frame(stream, &env),\n     │...\n 1841│    use crate::msg::output_envelope;\n*1842│    use crate::transport::DaemonTransport;\n 1843│    use std::sync::atomic::{AtomicU32, Ordering};\n 1844│    use std::sync::mpsc::{sync_channel, SyncSender};\n 1845│\n     │...\n 1895│    /// (subscribe frames), and writes whatever envelopes the test feeds it. This is\n*1896│    /// the brain's REAL `read_event` / `attach_skip_to_live` dedup state machine\n 1897│    /// exercised end-to-end over the real codec — the cheapest harness that still\n 1898│    /// drives the production paths (the integration tests in `tests/` use a real\n 1899│    /// broker; here we isolate the brain's dedup decision).\n     │...\n 1907│    /// Stand up the stub + a connected cold-start `Brain`. The stub binds, the brain\n*1908│    /// connects (cold_start sends the hello), the stub recv_hello's it, then loops:\n 1909│    /// it owns the server `Stream` and writes any envelope the test feeds via `feed`.\n 1910│    /// (We never need to READ the brain's writes for these tests — the subscribe\n 1911│    /// `attach_skip_to_live` sends is harmlessly left in the OS socket buffer; the\n     │...\n 1916│\n*1917│        // The brain connects on a thread (cold_start blocks on connect+hello).\n 1918│        let connect_name = name.clone();\n 1919│        let brain_handle =\n*1920│            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect(\"brain cold_start\"));\n 1921│\n 1922│        let mut server = listener.accept().expect(\"accept the brain\");\n 1923│        crate::transport::recv_hello(&mut server, Role::Brain).expect(\"stub recv_hello\");\n     │...\n 1963│        // Contiguous seq 0 is accepted and advances the cursor.\n*1964│        stub.feed.send(output_envelope(sid, 0, b\"hello\")).unwrap();\n*1965│        match brain.read_event().expect(\"seq 0 accepted\") {\n 1966│            BrokerEvent::Output { seq, bytes, .. } => {\n 1967│                assert_eq!(seq, 0);\n 1968│                assert_eq!(bytes, b\"hello\");\n     │...\n 1974│        // path — a cold brain treats a skipped seq as a lost chunk, never silently.\n*1975│        stub.feed.send(output_envelope(sid, 5, b\"jumped\")).unwrap();\n*1976│        let err = brain.read_event().expect_err(\"a cold brain rejects a forward gap\");\n 1977│        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 1978│        assert!(\n 1979│            err.to_string().contains(\"output gap\"),\n     │...\n 2019│            stub.feed\n*2020│                .send(output_envelope(sid, seq, format!(\"c{seq}\").as_bytes()))\n 2021│                .unwrap();\n*2022│            match brain.read_event().expect(\"contiguous controller frame accepted\") {\n 2023│                BrokerEvent::Output { seq: got, .. } => assert_eq!(got, seq),\n 2024│                other => panic!(\"expected Output({seq}), got {other:?}\"),\n 2025│            }\n     │...\n 2036│        // frozen `delivered_through`); this IS the resume floor.\n*2037│        stub.feed.send(output_envelope(sid, 10, b\"gapped\")).unwrap();\n 2038│        let err = brain\n*2039│            .read_event()\n 2040│            .expect_err(\"the strict controller path fatals a forward gap\");\n 2041│        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 2042│        assert!(\n     │...\n 2060│            stub.feed\n*2061│                .send(output_envelope(sid, seq, format!(\"r{seq}\").as_bytes()))\n 2062│                .unwrap();\n*2063│            match brain.read_event().expect(\"ring-replayed floor frame accepted\") {\n 2064│                BrokerEvent::Output { seq: got, .. } => assert_eq!(got, seq),\n 2065│                other => panic!(\"expected re-fetched Output({seq}), got {other:?}\"),\n 2066│            }\n     │...\n 2082│            .expect(\"controller second resume\");\n*2083│        stub.feed.send(output_envelope(sid, 9, b\"rolled\")).unwrap();\n 2084│        let err = brain\n*2085│            .read_event()\n 2086│            .expect_err(\"the ring rolled past the floor — still a gap\");\n 2087│        assert_eq!(err.kind(), io::ErrorKind::InvalidData);\n 2088│        assert!(err.to_string().contains(\"output gap\"));\n     │...\n 2107│    ///     → the map stays empty → the forward jump hits the legacy reject-gap path\n*2108│    ///     → `read_event` errors `output gap` instead of accepting → RED.\n 2109│    ///   - Leave `next_seq = u64::MAX` (the from_seq) instead of resetting to 0 AND\n 2110│    ///     route through the legacy path → every live seq < MAX is dedup-dropped →\n*2111│    ///     `read_event` would block (no event) → the test's bounded reader times out.\n 2112│    ///   - Snap-above mis-implemented as reject-gap (`seq > cursor` → Err) → RED.\n 2113│    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]\n 2114│    #[test]\n     │...\n 2134│        // jump is only ever a legitimate post-eviction ring-floor clamp).\n*2135│        stub.feed.send(output_envelope(sid, 12, b\"LIVE-A\")).unwrap();\n*2136│        match brain.read_event().expect(\"post-eviction forward jump accepted\") {\n 2137│            BrokerEvent::Output { seq, bytes, .. } => {\n 2138│                assert_eq!(seq, 12, \"the forward-jumped live seq is accepted, not rejected\");\n 2139│                assert_eq!(bytes, b\"LIVE-A\");\n     │...\n 2150│        // re-send (seq 12, already delivered) is DEDUPED (not a gap, not re-emitted).\n*2151│        stub.feed.send(output_envelope(sid, 13, b\"LIVE-B\")).unwrap();\n*2152│        stub.feed.send(output_envelope(sid, 12, b\"DUP\")).unwrap(); // dedup\n*2153│        stub.feed.send(output_envelope(sid, 14, b\"LIVE-C\")).unwrap();\n 2154│        let mut accepted = Vec::new();\n 2155│        for _ in 0..2 {\n*2156│            match brain.read_event().expect(\"subsequent live frames\") {\n 2157│                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),\n 2158│                other => panic!(\"expected Output, got {other:?}\"),\n 2159│            }\n     │...\n 2175│    /// dedup BELOW `from_seq`, snap ABOVE a forward ring-roll gap. Without arming,\n*2176│    /// the empty `session_cursors` runs the LEGACY reject-gap path → `read_event`\n 2177│    /// FATALS the forward gap (`output gap: got 4504 want 100`) → forwarding stops →\n 2178│    /// the operator sees nothing (wedged viewer, Linux forkpty).\n 2179│    ///\n     │...\n 2182│    ///     `attach_as_viewer_snap` → the map stays empty → the forward gap hits the\n*2183│    ///     legacy reject-gap path → `read_event` errors `output gap` → RED.\n 2184│    ///   - Seed at 0 instead of `from_seq` → the dedup-below boundary is WRONG: a\n 2185│    ///     seq in [0, from_seq) that should DEDUP (already delivered before this\n 2186│    ///     attach floor) would instead be ACCEPTED below the intended floor. The\n     │...\n 2211│        // `output gap: got 4504 want 100`). Snap-above must ACCEPT it.\n*2212│        stub.feed.send(output_envelope(sid, 4504, b\"ROLLED\")).unwrap();\n*2213│        match brain.read_event().expect(\"pre-eviction forward ring-roll gap accepted\") {\n 2214│            BrokerEvent::Output { seq, bytes, .. } => {\n 2215│                assert_eq!(seq, 4504, \"the forward-jumped live seq is accepted, not rejected\");\n 2216│                assert_eq!(bytes, b\"ROLLED\");\n     │...\n 2228│        // gap, not re-emitted), seq 4506 accepted.\n*2229│        stub.feed.send(output_envelope(sid, 4505, b\"LIVE-B\")).unwrap();\n*2230│        stub.feed.send(output_envelope(sid, 4504, b\"DUP\")).unwrap(); // dedup below cursor\n*2231│        stub.feed.send(output_envelope(sid, 4506, b\"LIVE-C\")).unwrap();\n 2232│        let mut accepted = Vec::new();\n 2233│        for _ in 0..2 {\n*2234│            match brain.read_event().expect(\"subsequent live frames\") {\n 2235│                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),\n 2236│                other => panic!(\"expected Output, got {other:?}\"),\n 2237│            }"},"isError":false,"timestamp":1784271556478}}
{"type":"message","id":"de6fd97c","parentId":"cdea0e9a","timestamp":"2026-07-17T06:59:16.482Z","message":{"role":"toolResult","toolCallId":"call_ZcVLBzgvH0bbxqcKVO91JwoA|fc_089095e18118ee7d016a59d2c4672c8190babd9ae9b54be9c5","toolName":"grep","content":[{"type":"text","text":" 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 65|\n*66|/// Why a serve loop returned.\n 67|#[derive(Debug, Clone, PartialEq, Eq)]\n 68|pub enum AttachServeOutcome {\n 69|    /// The access gate refused the requesting origin, OR a `Control` attach hit\n...\n 76|    Exited(Option<i32>),\n*77|    /// The operator closed its send side (detached); the viewport ended with\n 78|    /// the session still running.\n*79|    Detached,\n 80|    /// This controller was DISPLACED by a different operator's `--take`/`kick`\n 81|    /// (REQ-KICK-1): the loud `Displaced` notice was sent and the stream\n 82|    /// finished. The endpoint stays driven (by the taker) — no dormant edge.\n...\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...\n 162|/// (a) re-arm the stale-replay steal fix 6 refuses and (b) make the worker's\n*163|/// own terminal `detach_session_gen` mismatch the slot it re-installed.\n 164|// [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n 165|pub fn read_controller_event_resuming(\n 166|    brain: &mut Brain,\n...\n 172|    let mut swallow_resume_ack = false;\n*173|    loop {\n*174|        match brain.read_event() {\n 175|            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n 176|            // resolved) so it cannot mask a repeated gap as forward progress.\n 177|            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n...\n 180|            }\n*181|            Ok(ev) => return Ok(ev),\n 182|            Err(e) if is_output_gap(&e) => {\n 183|                let floor = brain.controller_resume_floor();\n 184|                if last_resume_floor == Some(floor) {\n...\n 186|                    // floor → the dropped frames are gone.\n*187|                    return Err(io::Error::other(ControllerIrrecoverablyBehind { floor }));\n 188|                }\n 189|                last_resume_floor = Some(floor);\n 190|                // RE-FETCH from the frozen delivered_through (same operator `by` →\n...\n 195|            }\n*196|            Err(e) => return Err(e),\n 197|        }\n 198|    }\n 199|}\n...\n 255|        wire.net_stream_send(stream_id, &[], None, true)?;\n*256|        return Ok(Some(AttachServeOutcome::Refused));\n 257|    }\n 258|    let sid = remote_session.unwrap_or(session_id);\n 259|    *intent = req_intent;\n...\n 271|/// Serve one attach stream against this brain's broker, blocking until the\n*272|/// session exits, the operator detaches, or the gate refuses.\n 273|///\n 274|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n 275|/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n...\n 303|#[allow(clippy::too_many_arguments)]\n*304|pub fn serve_attach(\n 305|    brain: &mut Brain,\n 306|    broker_name: &str,\n 307|    stream_id: u64,\n...\n 313|) -> io::Result<AttachServeOutcome> {\n*314|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 315|    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n*316|    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 317|    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n 318|    // session output (broker→serve) — including the controller's synchronous,\n 319|    // inline, multi-frame ring replay (`become_controller`), which holds the\n...\n 387|        )? {\n*388|            return Ok(out);\n 389|        }\n 390|    }\n 391|\n*392|    loop {\n 393|        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n 394|        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n 395|        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n...\n 418|                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n*419|                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n*420|                        return Err(e);\n 421|                    }\n 422|                    Err(e) => {\n*423|                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n*424|                        return Err(e);\n 425|                    }\n 426|                }\n 427|            }\n*428|            _ => brain.read_event()?,\n 429|        };\n 430|        match event {\n 431|            // ── Wire → session: requests, input, resize. ───────────────────\n...\n 465|                            )? {\n*466|                                return Ok(out);\n 467|                            }\n 468|                        }\n 469|                        AttachRecord::Input { data_b64, op_id }\n...\n 479|                                // flood N input frames on this one conn; an acked write\n*480|                                // back would fill the return direction and deadlock the\n 481|                                // broker's per-conn handler. Dedup still holds at the\n 482|                                // broker (op_id applied-set).\n 483|                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n...\n 539|                        wire.net_stream_send(stream_id, &[], None, true)?;\n*540|                        return Ok(AttachServeOutcome::Refused);\n 541|                    }\n 542|                }\n 543|            }\n...\n 554|                // own serve loop already fired the wake).\n*555|                return Ok(AttachServeOutcome::Displaced);\n 556|            }\n 557|\n 558|            // This VIEWER overflowed its broker subscription queue and was evicted\n...\n 580|            // The operator closed its send side: the viewport is over.\n*581|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n 582|                wire.net_stream_send(stream_id, &[], None, true)?;\n 583|                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 584|                // info.json, carrying the current driven_by through), THEN release\n...\n 588|                if role == Some(ServeRole::Controller) {\n*589|                    // The driver detached — the endpoint rests warm (controller-\n 590|                    // only; a viewer leaving is wake-neutral).\n*591|                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 592|                }\n 593|                if let Some(sid) = attached_session {\n*594|                    let _ = brain.detach_session_gen(sid, Some(serve_gen)); // release ONLY our generation\n 595|                }\n*596|                return Ok(AttachServeOutcome::Detached);\n 597|            }\n 598|\n 599|            // ── Session → wire: output and exit. ────────────────────────────\n...\n 612|                if role == Some(ServeRole::Controller) {\n*613|                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 614|                }\n*615|                return Ok(AttachServeOutcome::Exited(code));\n 616|            }\n 617|\n 618|            BrokerEvent::Error { message } => {\n 619|                if role == Some(ServeRole::Controller) {\n*620|                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 621|                }\n*622|                return Err(io::Error::other(message));\n 623|            }\n 624|            // Applied/NetSent acks and unrelated events: not ours to handle.\n 625|            _ => {}\n...\n 630|/// Operator-side: open an attach stream on a dialed connection and send the\n*631|/// [`AttachRecord::Request`]. Returns the stream id the caller then consumes\n 632|/// ([`AttachRecord::Output`] frames, deduped by `seq`) and writes\n 633|/// ([`AttachRecord::Input`] with operator-minted op ids). `open_op` is the\n 634|/// durable id for the exactly-once stream open (safe to block for its ack —"}],"details":{"scopePath":"crates/spt-daemon/src/attach.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":37,"fileCount":1,"files":["crates/spt-daemon/src/attach.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/attach.rs","count":37}],"truncated":false,"displayContent":"  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  65│\n *66│/// Why a serve loop returned.\n  67│#[derive(Debug, Clone, PartialEq, Eq)]\n  68│pub enum AttachServeOutcome {\n  69│    /// The access gate refused the requesting origin, OR a `Control` attach hit\n    │...\n  76│    Exited(Option<i32>),\n *77│    /// The operator closed its send side (detached); the viewport ended with\n  78│    /// the session still running.\n *79│    Detached,\n  80│    /// This controller was DISPLACED by a different operator's `--take`/`kick`\n  81│    /// (REQ-KICK-1): the loud `Displaced` notice was sent and the stream\n  82│    /// finished. The endpoint stays driven (by the taker) — no dormant edge.\n    │...\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    │...\n 162│/// (a) re-arm the stale-replay steal fix 6 refuses and (b) make the worker's\n*163│/// own terminal `detach_session_gen` mismatch the slot it re-installed.\n 164│// [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n 165│pub fn read_controller_event_resuming(\n 166│    brain: &mut Brain,\n    │...\n 172│    let mut swallow_resume_ack = false;\n*173│    loop {\n*174│        match brain.read_event() {\n 175│            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n 176│            // resolved) so it cannot mask a repeated gap as forward progress.\n 177│            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n    │...\n 180│            }\n*181│            Ok(ev) => return Ok(ev),\n 182│            Err(e) if is_output_gap(&e) => {\n 183│                let floor = brain.controller_resume_floor();\n 184│                if last_resume_floor == Some(floor) {\n    │...\n 186│                    // floor → the dropped frames are gone.\n*187│                    return Err(io::Error::other(ControllerIrrecoverablyBehind { floor }));\n 188│                }\n 189│                last_resume_floor = Some(floor);\n 190│                // RE-FETCH from the frozen delivered_through (same operator `by` →\n    │...\n 195│            }\n*196│            Err(e) => return Err(e),\n 197│        }\n 198│    }\n 199│}\n    │...\n 255│        wire.net_stream_send(stream_id, &[], None, true)?;\n*256│        return Ok(Some(AttachServeOutcome::Refused));\n 257│    }\n 258│    let sid = remote_session.unwrap_or(session_id);\n 259│    *intent = req_intent;\n    │...\n 271│/// Serve one attach stream against this brain's broker, blocking until the\n*272│/// session exits, the operator detaches, or the gate refuses.\n 273│///\n 274│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n 275│/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n    │...\n 303│#[allow(clippy::too_many_arguments)]\n*304│pub fn serve_attach(\n 305│    brain: &mut Brain,\n 306│    broker_name: &str,\n 307│    stream_id: u64,\n    │...\n 313│) -> io::Result<AttachServeOutcome> {\n*314│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 315│    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n*316│    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 317│    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n 318│    // session output (broker→serve) — including the controller's synchronous,\n 319│    // inline, multi-frame ring replay (`become_controller`), which holds the\n    │...\n 387│        )? {\n*388│            return Ok(out);\n 389│        }\n 390│    }\n 391│\n*392│    loop {\n 393│        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n 394│        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n 395│        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n    │...\n 418│                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n*419│                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n*420│                        return Err(e);\n 421│                    }\n 422│                    Err(e) => {\n*423│                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n*424│                        return Err(e);\n 425│                    }\n 426│                }\n 427│            }\n*428│            _ => brain.read_event()?,\n 429│        };\n 430│        match event {\n 431│            // ── Wire → session: requests, input, resize. ───────────────────\n    │...\n 465│                            )? {\n*466│                                return Ok(out);\n 467│                            }\n 468│                        }\n 469│                        AttachRecord::Input { data_b64, op_id }\n    │...\n 479│                                // flood N input frames on this one conn; an acked write\n*480│                                // back would fill the return direction and deadlock the\n 481│                                // broker's per-conn handler. Dedup still holds at the\n 482│                                // broker (op_id applied-set).\n 483│                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n    │...\n 539│                        wire.net_stream_send(stream_id, &[], None, true)?;\n*540│                        return Ok(AttachServeOutcome::Refused);\n 541│                    }\n 542│                }\n 543│            }\n    │...\n 554│                // own serve loop already fired the wake).\n*555│                return Ok(AttachServeOutcome::Displaced);\n 556│            }\n 557│\n 558│            // This VIEWER overflowed its broker subscription queue and was evicted\n    │...\n 580│            // The operator closed its send side: the viewport is over.\n*581│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n 582│                wire.net_stream_send(stream_id, &[], None, true)?;\n 583│                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 584│                // info.json, carrying the current driven_by through), THEN release\n    │...\n 588│                if role == Some(ServeRole::Controller) {\n*589│                    // The driver detached — the endpoint rests warm (controller-\n 590│                    // only; a viewer leaving is wake-neutral).\n*591│                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 592│                }\n 593│                if let Some(sid) = attached_session {\n*594│                    let _ = brain.detach_session_gen(sid, Some(serve_gen)); // release ONLY our generation\n 595│                }\n*596│                return Ok(AttachServeOutcome::Detached);\n 597│            }\n 598│\n 599│            // ── Session → wire: output and exit. ────────────────────────────\n    │...\n 612│                if role == Some(ServeRole::Controller) {\n*613│                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 614│                }\n*615│                return Ok(AttachServeOutcome::Exited(code));\n 616│            }\n 617│\n 618│            BrokerEvent::Error { message } => {\n 619│                if role == Some(ServeRole::Controller) {\n*620│                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 621│                }\n*622│                return Err(io::Error::other(message));\n 623│            }\n 624│            // Applied/NetSent acks and unrelated events: not ours to handle.\n 625│            _ => {}\n    │...\n 630│/// Operator-side: open an attach stream on a dialed connection and send the\n*631│/// [`AttachRecord::Request`]. Returns the stream id the caller then consumes\n 632│/// ([`AttachRecord::Output`] frames, deduped by `seq`) and writes\n 633│/// ([`AttachRecord::Input`] with operator-minted op ids). `open_op` is the\n 634│/// durable id for the exactly-once stream open (safe to block for its ack —"},"isError":false,"timestamp":1784271556482}}
{"type":"message","id":"3c8e4018","parentId":"de6fd97c","timestamp":"2026-07-17T06:59:16.485Z","message":{"role":"toolResult","toolCallId":"call_gGlKkuEfJGvPwZu7PpoqQaj4|fc_089095e18118ee7d016a59d2c467208190b36cb96e4fee8387","toolName":"grep","content":[{"type":"text","text":" 307|    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n*308|    /// there is no accept loop and no closed-watcher (it lives for the host).\n 309|    Loopback,\n 310|}\n 311|\n...\n 517|    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*518|    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n 519|        if let Some(prior) = &self.subscriber {\n 520|            // No-reinstall-until-gone applies to the POISON class only\n 521|            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\n...\n 565|        // Arc, old workers' handles stay theirs (C2 consumes via\n*566|        // [`NetHost::stream_lease`]).\n 567|        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 568|        self.lease = Arc::new(StreamLease::default());\n 569|        let replay_len = replay.len();\n...\n 591|/// applied to the conn table itself. `register_conn` appends `connected`; each\n*592|/// connection's closed-watcher appends `disconnected` after removing the row.\n 593|/// A brain subscribes with a cursor and replays events produced while it was\n 594|/// down, so presence survives a brain restart exactly like stream reads do.\n 595|// [impl->REQ-EP-4]\n...\n 616|    /// ring, live-send to the current subscriber. Producers: `register_conn`\n*617|    /// (connected) and the per-conn closed-watchers (disconnected) — every\n 618|    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive\n 619|    /// in strict `seq` order.\n 620|    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {\n...\n 689|    /// order structural, conn I/O outside this mutex — same discipline as\n*690|    /// [`StreamLog::begin_attach`]. Returns the replay event count.\n 691|    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*692|    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n 693|        if let Some(prior) = &self.subscriber {\n*694|            // Poison-class only (see StreamLog::begin_attach): a healthy\n 695|            // prior presence subscriber is displaced by the newer brain.\n 696|            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\n 697|                return Err(io::Error::new(\n...\n 933|\n*934|    // Presence: announce the row BEFORE spawning the closed-watcher, so the\n 935|    // `connected` event can never race behind its own `disconnected` (D4c). The\n 936|    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\n 937|    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\n...\n 945|\n*946|    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\n 947|    // close, conn loss, or our own endpoint close), removes the row — conns\n 948|    // finally LEAVE the table — then appends the `disconnected` event (row\n 949|    // gone before the event is observable).\n...\n 954|    tokio::spawn(async move {\n*955|        conn_close.closed().await;\n 956|        sh_close.conns.lock().unwrap().remove(&id);\n 957|        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n 958|        // — nothing can ever be served on them again, and leaving them makes\n...\n 966|            .unwrap()\n*967|            .retain(|_, e| e.conn_id != id);\n 968|        sh_close\n 969|            .presence\n 970|            .lock()\n...\n 1114|    /// — a local `spt rc` needs exactly one. `Some(conn_id)` once minted; the\n*1115|    /// row lives for the host (no handshake to lose, no closed-watcher).\n 1116|    loopback_conn: Mutex<Option<u64>>,\n 1117|    /// Serializes [`Self::open_loopback_pair`] (M11-W3) so the peer-row discovery\n 1118|    /// (a before/after diff on the loopback conn's inbound rows) sees exactly the\n...\n 1462|            // semaphore as `submit_dial`, so the concurrency cap bounds every dial\n*1463|            // source (never bypassed). `acquire_owned` errs only on a closed\n 1464|            // semaphore — we never close it.\n 1465|            let _permit = sem\n 1466|                .acquire_owned()\n...\n 1793|    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*1794|    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\n 1795|        let streams = self.shared.streams.lock().unwrap();\n 1796|        streams\n 1797|            .get(&stream_id)\n...\n 1804|    /// enumeration is exactly what a future dispatcher generation would replay.\n*1805|    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806|        let streams = self.shared.streams.lock().unwrap();\n 1807|        streams\n 1808|            .iter()\n...\n 1828|    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n*1829|    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n 1830|    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n 1831|    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n 1832|    /// reply sends when it removed the entry (see the `retired` field note).\n...\n 1835|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*1836|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837|        let Some(entry) = self\n 1838|            .shared\n 1839|            .streams\n...\n 1949|    // [impl->REQ-DAEMON-2]\n*1950|    pub(crate) fn subscribe_stream(\n 1951|        &self,\n 1952|        stream_id: u64,\n 1953|        sub: SharedSend,\n...\n 1974|        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*1975|        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\n 1976|        Ok(())\n 1977|    }\n 1978|\n...\n 1980|    /// disconnect cleanup; the stream + its ring persist).\n*1981|    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1982|        let log = {\n 1983|            let streams = self.shared.streams.lock().unwrap();\n 1984|            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n...\n 1994|    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n*1995|        // Same atomic install-with-replay discipline as subscribe_stream\n 1996|        // (the PresenceLog mirror): the seat's writer drains the replay\n 1997|        // before any live event, conn I/O outside the mutex. A replay write\n 1998|        // failure poisons the seat; the next push halt-and-removes it — the\n...\n 2005|            .unwrap()\n*2006|            .begin_attach(Arc::clone(&sub), from_seq); // busy refusal: quiet, as before\n 2007|    }\n 2008|\n 2009|    /// Detach `sub` from presence iff it is the current subscriber (brain\n...\n 2015|    /// Gracefully close the endpoint: every held connection flushes its QUIC\n*2016|    /// CLOSE so peers' closed-watchers fire promptly (a bare host drop kills\n 2017|    /// the runtime first and leaves peers to an idle timeout).\n 2018|    pub fn close(&self) {\n 2019|        let endpoint = Arc::clone(&self.endpoint);\n...\n 2164|        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n*2165|        let infos = host.stream_infos();\n 2166|        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167|        let op = infos\n 2168|            .iter()\n...\n 2189|            if host\n*2190|                .stream_infos()\n 2191|                .iter()\n 2192|                .any(|s| s.stream_id == peer_id && s.next_seq > 0)\n 2193|            {\n...\n 2387|    // named row from dispatch eligibility: the retired stream leaves the\n*2388|    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389|    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390|    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391|    // teardown — the transport half of the contract is the sibling test\n...\n 2414|        assert!(host.retire_stream(shell), \"first retire hides the row\");\n*2415|        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416|        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417|        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418|        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n...\n 2471|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n*2472|    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2473|    // future dispatcher generation to replay on a transport that can never\n 2474|    // serve again (the field bug's historical-rows-forever half).\n 2475|    #[test]\n...\n 2484|        for _ in 0..400 {\n*2485|            if !b.stream_infos().is_empty() {\n 2486|                saw = true;\n 2487|                break;\n 2488|            }\n...\n 2492|\n*2493|        a.close(); // graceful CLOSE — b's closed-watcher fires promptly\n 2494|        drop(a);\n 2495|        let mut swept = false;\n 2496|        for _ in 0..600 {\n*2497|            if b.stream_infos().is_empty() {\n 2498|                swept = true;\n 2499|                break;\n 2500|            }\n...\n 2502|        }\n*2503|        assert!(swept, \"the closed-watcher swept the dead conn's stream rows\");\n 2504|    }\n 2505|\n 2506|    // ── ADR-0038 Amendment fixes 2+3+4 — the subscriber-seat discipline at\n...\n 2563|        let mut log = StreamLog::new(7, 64);\n*2564|        assert_eq!(log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\"), 0);\n 2565|        let lease = Arc::clone(&log.lease);\n 2566|\n 2567|        // Kill the conn: the peer side drops, so the writer's next write fails.\n...\n 2589|        let (fresh, _cf, _rf) = seat_socket_pair(Duration::from_secs(5));\n*2590|        log.begin_attach(Arc::clone(&fresh), 0).expect(\"new generation attaches\");\n 2591|        assert!(\n 2592|            !log.lease.is_canceled(),\n 2593|            \"a new subscriber generation renews the serve lease\"\n...\n 2608|        let mut log = StreamLog::new(8, 64);\n*2609|        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2610|        let lease = Arc::clone(&log.lease);\n 2611|\n 2612|        drop(client);\n...\n 2636|    #[test]\n*2637|    fn begin_attach_refuses_only_while_a_poisoned_prior_still_drains() {\n 2638|        let (a, _ca, _ra) = seat_socket_pair(Duration::from_secs(5));\n 2639|        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\n 2640|        let mut log = StreamLog::new(3, 64);\n*2641|        log.begin_attach(Arc::clone(&a), 0).expect(\"first attach\");\n 2642|\n 2643|        // Healthy prior → displaced (the legit brain-swap path).\n*2644|        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n 2645|        assert!(log.subscriber.as_ref().unwrap().is(&b));\n 2646|\n 2647|        // Poisoned but NOT gone (writer still draining) → refuse WouldBlock.\n...\n 2650|        let err = log\n*2651|            .begin_attach(Arc::clone(&a), 0)\n 2652|            .expect_err(\"no reinstall until the prior seat is fully gone\");\n 2653|        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\n 2654|        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\n...\n 2657|        log.subscriber.as_ref().unwrap().done.store(true, Ordering::Release);\n*2658|        log.begin_attach(Arc::clone(&a), 0).expect(\"gone prior admits the replacement\");\n 2659|        assert!(log.subscriber.as_ref().unwrap().is(&a));\n 2660|    }\n 2661|\n...\n 2671|        let mut log = StreamLog::new(9, 4096);\n*2672|        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2673|        let lease = Arc::clone(&log.lease);\n 2674|\n 2675|        // Park the seat's writer on its first write (the gate pin — bounded\n...\n 2698|        let replayed = log\n*2699|            .begin_attach(Arc::clone(&sub), 5)\n 2700|            .expect(\"the detached brain re-attaches after the overflow\");\n 2701|        assert_eq!(\n 2702|            replayed,\n...\n 2729|        let pin = sub.pin_gate_for_test();\n*2730|        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach with a pending replay\");\n 2731|        // Live appends land while the replay is still entirely undrained.\n 2732|        for i in 6..10u8 {\n 2733|            log.append(&[i]); // live seqs 6..=9\n...\n 2765|        let mut log = StreamLog::new(11, 64);\n*2766|        log.begin_attach(Arc::clone(&a), 0).expect(\"attach A\");\n 2767|        let lease_a = Arc::clone(&log.lease);\n 2768|\n 2769|        // Queue a frame for A's writer and PARK its write (gate pinned) so\n...\n 2774|        // HEALTHY displacement: B takes the seat (the brain-swap contract).\n*2775|        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n 2776|        let lease_b = Arc::clone(&log.lease);\n 2777|        assert!(\n 2778|            !Arc::ptr_eq(&lease_a, &lease_b),\n...\n 2813|        log.append(\"connected\", 1, \"aa\");\n*2814|        let replayed = log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2815|        assert_eq!(replayed, 1, \"retained events replay from the cursor\");\n 2816|\n 2817|        drop(client);\n...\n 2830|        let mut log2 = PresenceLog::new(64);\n*2831|        log2.begin_attach(Arc::clone(&a), 0).expect(\"attach\");\n 2832|        log2.subscriber\n 2833|            .as_ref()\n 2834|            .unwrap()\n...\n 2837|        let err = log2\n*2838|            .begin_attach(Arc::clone(&b), 0)\n 2839|            .expect_err(\"poisoned prior presence seat refuses reinstall\");\n 2840|        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\n 2841|        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\n...\n 2853|        let (owner, _shell) = host.open_loopback_pair().expect(\"open pair\");\n*2854|        let lease = host.stream_lease(owner).expect(\"lease handle\");\n 2855|        assert!(!lease.is_canceled());\n 2856|        host.send_stream(owner, b\"ok\", false).expect(\"a live lease serves\");\n 2857|"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":50,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":50}],"truncated":false,"displayContent":"  307│    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n *308│    /// there is no accept loop and no closed-watcher (it lives for the host).\n  309│    Loopback,\n  310│}\n  311│\n     │...\n  517│    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n *518│    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n  519│        if let Some(prior) = &self.subscriber {\n  520│            // No-reinstall-until-gone applies to the POISON class only\n  521│            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\n     │...\n  565│        // Arc, old workers' handles stay theirs (C2 consumes via\n *566│        // [`NetHost::stream_lease`]).\n  567│        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n  568│        self.lease = Arc::new(StreamLease::default());\n  569│        let replay_len = replay.len();\n     │...\n  591│/// applied to the conn table itself. `register_conn` appends `connected`; each\n *592│/// connection's closed-watcher appends `disconnected` after removing the row.\n  593│/// A brain subscribes with a cursor and replays events produced while it was\n  594│/// down, so presence survives a brain restart exactly like stream reads do.\n  595│// [impl->REQ-EP-4]\n     │...\n  616│    /// ring, live-send to the current subscriber. Producers: `register_conn`\n *617│    /// (connected) and the per-conn closed-watchers (disconnected) — every\n  618│    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive\n  619│    /// in strict `seq` order.\n  620│    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {\n     │...\n  689│    /// order structural, conn I/O outside this mutex — same discipline as\n *690│    /// [`StreamLog::begin_attach`]. Returns the replay event count.\n  691│    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n *692│    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n  693│        if let Some(prior) = &self.subscriber {\n *694│            // Poison-class only (see StreamLog::begin_attach): a healthy\n  695│            // prior presence subscriber is displaced by the newer brain.\n  696│            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\n  697│                return Err(io::Error::new(\n     │...\n  933│\n *934│    // Presence: announce the row BEFORE spawning the closed-watcher, so the\n  935│    // `connected` event can never race behind its own `disconnected` (D4c). The\n  936│    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\n  937│    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\n     │...\n  945│\n *946│    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\n  947│    // close, conn loss, or our own endpoint close), removes the row — conns\n  948│    // finally LEAVE the table — then appends the `disconnected` event (row\n  949│    // gone before the event is observable).\n     │...\n  954│    tokio::spawn(async move {\n *955│        conn_close.closed().await;\n  956│        sh_close.conns.lock().unwrap().remove(&id);\n  957│        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n  958│        // — nothing can ever be served on them again, and leaving them makes\n     │...\n  966│            .unwrap()\n *967│            .retain(|_, e| e.conn_id != id);\n  968│        sh_close\n  969│            .presence\n  970│            .lock()\n     │...\n 1114│    /// — a local `spt rc` needs exactly one. `Some(conn_id)` once minted; the\n*1115│    /// row lives for the host (no handshake to lose, no closed-watcher).\n 1116│    loopback_conn: Mutex<Option<u64>>,\n 1117│    /// Serializes [`Self::open_loopback_pair`] (M11-W3) so the peer-row discovery\n 1118│    /// (a before/after diff on the loopback conn's inbound rows) sees exactly the\n     │...\n 1462│            // semaphore as `submit_dial`, so the concurrency cap bounds every dial\n*1463│            // source (never bypassed). `acquire_owned` errs only on a closed\n 1464│            // semaphore — we never close it.\n 1465│            let _permit = sem\n 1466│                .acquire_owned()\n     │...\n 1793│    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*1794│    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\n 1795│        let streams = self.shared.streams.lock().unwrap();\n 1796│        streams\n 1797│            .get(&stream_id)\n     │...\n 1804│    /// enumeration is exactly what a future dispatcher generation would replay.\n*1805│    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806│        let streams = self.shared.streams.lock().unwrap();\n 1807│        streams\n 1808│            .iter()\n     │...\n 1828│    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n*1829│    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n 1830│    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n 1831│    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n 1832│    /// reply sends when it removed the entry (see the `retired` field note).\n     │...\n 1835│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*1836│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837│        let Some(entry) = self\n 1838│            .shared\n 1839│            .streams\n     │...\n 1949│    // [impl->REQ-DAEMON-2]\n*1950│    pub(crate) fn subscribe_stream(\n 1951│        &self,\n 1952│        stream_id: u64,\n 1953│        sub: SharedSend,\n     │...\n 1974│        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n*1975│        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\n 1976│        Ok(())\n 1977│    }\n 1978│\n     │...\n 1980│    /// disconnect cleanup; the stream + its ring persist).\n*1981│    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1982│        let log = {\n 1983│            let streams = self.shared.streams.lock().unwrap();\n 1984│            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n     │...\n 1994│    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n*1995│        // Same atomic install-with-replay discipline as subscribe_stream\n 1996│        // (the PresenceLog mirror): the seat's writer drains the replay\n 1997│        // before any live event, conn I/O outside the mutex. A replay write\n 1998│        // failure poisons the seat; the next push halt-and-removes it — the\n     │...\n 2005│            .unwrap()\n*2006│            .begin_attach(Arc::clone(&sub), from_seq); // busy refusal: quiet, as before\n 2007│    }\n 2008│\n 2009│    /// Detach `sub` from presence iff it is the current subscriber (brain\n     │...\n 2015│    /// Gracefully close the endpoint: every held connection flushes its QUIC\n*2016│    /// CLOSE so peers' closed-watchers fire promptly (a bare host drop kills\n 2017│    /// the runtime first and leaves peers to an idle timeout).\n 2018│    pub fn close(&self) {\n 2019│        let endpoint = Arc::clone(&self.endpoint);\n     │...\n 2164│        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n*2165│        let infos = host.stream_infos();\n 2166│        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167│        let op = infos\n 2168│            .iter()\n     │...\n 2189│            if host\n*2190│                .stream_infos()\n 2191│                .iter()\n 2192│                .any(|s| s.stream_id == peer_id && s.next_seq > 0)\n 2193│            {\n     │...\n 2387│    // named row from dispatch eligibility: the retired stream leaves the\n*2388│    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389│    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390│    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391│    // teardown — the transport half of the contract is the sibling test\n     │...\n 2414│        assert!(host.retire_stream(shell), \"first retire hides the row\");\n*2415│        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416│        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417│        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418│        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n     │...\n 2471│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n*2472│    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2473│    // future dispatcher generation to replay on a transport that can never\n 2474│    // serve again (the field bug's historical-rows-forever half).\n 2475│    #[test]\n     │...\n 2484│        for _ in 0..400 {\n*2485│            if !b.stream_infos().is_empty() {\n 2486│                saw = true;\n 2487│                break;\n 2488│            }\n     │...\n 2492│\n*2493│        a.close(); // graceful CLOSE — b's closed-watcher fires promptly\n 2494│        drop(a);\n 2495│        let mut swept = false;\n 2496│        for _ in 0..600 {\n*2497│            if b.stream_infos().is_empty() {\n 2498│                swept = true;\n 2499│                break;\n 2500│            }\n     │...\n 2502│        }\n*2503│        assert!(swept, \"the closed-watcher swept the dead conn's stream rows\");\n 2504│    }\n 2505│\n 2506│    // ── ADR-0038 Amendment fixes 2+3+4 — the subscriber-seat discipline at\n     │...\n 2563│        let mut log = StreamLog::new(7, 64);\n*2564│        assert_eq!(log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\"), 0);\n 2565│        let lease = Arc::clone(&log.lease);\n 2566│\n 2567│        // Kill the conn: the peer side drops, so the writer's next write fails.\n     │...\n 2589│        let (fresh, _cf, _rf) = seat_socket_pair(Duration::from_secs(5));\n*2590│        log.begin_attach(Arc::clone(&fresh), 0).expect(\"new generation attaches\");\n 2591│        assert!(\n 2592│            !log.lease.is_canceled(),\n 2593│            \"a new subscriber generation renews the serve lease\"\n     │...\n 2608│        let mut log = StreamLog::new(8, 64);\n*2609│        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2610│        let lease = Arc::clone(&log.lease);\n 2611│\n 2612│        drop(client);\n     │...\n 2636│    #[test]\n*2637│    fn begin_attach_refuses_only_while_a_poisoned_prior_still_drains() {\n 2638│        let (a, _ca, _ra) = seat_socket_pair(Duration::from_secs(5));\n 2639│        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\n 2640│        let mut log = StreamLog::new(3, 64);\n*2641│        log.begin_attach(Arc::clone(&a), 0).expect(\"first attach\");\n 2642│\n 2643│        // Healthy prior → displaced (the legit brain-swap path).\n*2644│        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n 2645│        assert!(log.subscriber.as_ref().unwrap().is(&b));\n 2646│\n 2647│        // Poisoned but NOT gone (writer still draining) → refuse WouldBlock.\n     │...\n 2650│        let err = log\n*2651│            .begin_attach(Arc::clone(&a), 0)\n 2652│            .expect_err(\"no reinstall until the prior seat is fully gone\");\n 2653│        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\n 2654│        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\n     │...\n 2657│        log.subscriber.as_ref().unwrap().done.store(true, Ordering::Release);\n*2658│        log.begin_attach(Arc::clone(&a), 0).expect(\"gone prior admits the replacement\");\n 2659│        assert!(log.subscriber.as_ref().unwrap().is(&a));\n 2660│    }\n 2661│\n     │...\n 2671│        let mut log = StreamLog::new(9, 4096);\n*2672│        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2673│        let lease = Arc::clone(&log.lease);\n 2674│\n 2675│        // Park the seat's writer on its first write (the gate pin — bounded\n     │...\n 2698│        let replayed = log\n*2699│            .begin_attach(Arc::clone(&sub), 5)\n 2700│            .expect(\"the detached brain re-attaches after the overflow\");\n 2701│        assert_eq!(\n 2702│            replayed,\n     │...\n 2729│        let pin = sub.pin_gate_for_test();\n*2730│        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach with a pending replay\");\n 2731│        // Live appends land while the replay is still entirely undrained.\n 2732│        for i in 6..10u8 {\n 2733│            log.append(&[i]); // live seqs 6..=9\n     │...\n 2765│        let mut log = StreamLog::new(11, 64);\n*2766│        log.begin_attach(Arc::clone(&a), 0).expect(\"attach A\");\n 2767│        let lease_a = Arc::clone(&log.lease);\n 2768│\n 2769│        // Queue a frame for A's writer and PARK its write (gate pinned) so\n     │...\n 2774│        // HEALTHY displacement: B takes the seat (the brain-swap contract).\n*2775│        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n 2776│        let lease_b = Arc::clone(&log.lease);\n 2777│        assert!(\n 2778│            !Arc::ptr_eq(&lease_a, &lease_b),\n     │...\n 2813│        log.append(\"connected\", 1, \"aa\");\n*2814│        let replayed = log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n 2815│        assert_eq!(replayed, 1, \"retained events replay from the cursor\");\n 2816│\n 2817│        drop(client);\n     │...\n 2830│        let mut log2 = PresenceLog::new(64);\n*2831│        log2.begin_attach(Arc::clone(&a), 0).expect(\"attach\");\n 2832│        log2.subscriber\n 2833│            .as_ref()\n 2834│            .unwrap()\n     │...\n 2837│        let err = log2\n*2838│            .begin_attach(Arc::clone(&b), 0)\n 2839│            .expect_err(\"poisoned prior presence seat refuses reinstall\");\n 2840│        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\n 2841│        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\n     │...\n 2853│        let (owner, _shell) = host.open_loopback_pair().expect(\"open pair\");\n*2854│        let lease = host.stream_lease(owner).expect(\"lease handle\");\n 2855│        assert!(!lease.is_canceled());\n 2856│        host.send_stream(owner, b\"ok\", false).expect(\"a live lease serves\");\n 2857│"},"isError":false,"timestamp":1784271556485}}
{"type":"message","id":"c59b0e72","parentId":"3c8e4018","timestamp":"2026-07-17T06:59:16.488Z","message":{"role":"toolResult","toolCallId":"call_RMGQ6h6mddXX52e1Fea2ypSO|fc_089095e18118ee7d016a59d2c4670c8190bcad34988cd320d7","toolName":"grep","content":[{"type":"text","text":" 5|//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n*6|//! client / listening sockets. It runs **no logic** — no routing, registry,\n 7|//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n 8|//! forward its output to the brain, inject the brain's input, report exit.\n 9|//!\n...\n 66|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n*67|    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n 68|    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n 69|    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n 70|};\n...\n 160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n*161|/// protects the fan-out against ONE per-client conn falling behind and is\n 162|/// output-driven (the drain observes a Full channel); THIS bound is measured\n 163|/// full-with-**zero writer progress** and covers the case the output-driven path\n 164|/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n...\n 993|    /// no output, conn not yet EOF'd) stays `is_finished() == false` and converges\n*994|    /// only once output resumes or the conn EOFs (→ `handle_conn` detach) — that\n 995|    /// harder active-probe case is the reserved [`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`],\n 996|    /// deliberately NOT built here. KH 7.15 held by construction: only ever CLEARS,\n 997|    /// never latches `driven_by`.\n...\n 2563|    /// The namespaced listener name — held so [`Broker::stop`] can dial a throwaway\n*2564|    /// connection to unblock the blocked `accept()` in [`serve`](Self::serve) (there is\n 2565|    /// no other wakeup for the accept loop).\n 2566|    name: String,\n 2567|    /// Graceful-stop latch (C1, REQ-HAZARD-DAEMON-STOP-BARRIER teardown-hygiene):\n...\n 2605|    wake_inflight: Mutex<HashSet<String>>,\n*2606|    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n 2607|    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n 2608|    /// UPDATE-WEDGE round-4 int gate's observability seam\n 2609|    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n 2610|    /// physical conn must drive its handler to EOF cleanup within the write\n*2611|    /// bound WITHOUT the client dropping — the test watches this count fall.\n 2612|    conn_handlers: AtomicUsize,\n 2613|}\n 2614|\n...\n 2784|    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n*2785|    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n 2786|        loop {\n*2787|            let conn = self.listener.accept()?;\n 2788|            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n 2789|            // throwaway connection that woke this accept — observe it and return so the\n 2790|            // serve thread (and, with it, the listener) tears down instead of leaking.\n...\n 2795|            thread::spawn(move || {\n*2796|                let _ = me.handle_conn(conn);\n 2797|            });\n 2798|        }\n 2799|    }\n...\n 2801|    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n*2802|    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n 2803|    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n 2804|    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n 2805|    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n...\n 2815|    pub fn serve_once(self: &Arc<Self>) -> io::Result<JoinHandle<()>> {\n*2816|        let conn = self.listener.accept()?;\n 2817|        let me = Arc::clone(self);\n 2818|        Ok(thread::spawn(move || {\n*2819|            let _ = me.handle_conn(conn);\n 2820|        }))\n 2821|    }\n 2822|\n...\n 2867|        };\n*2868|        let Ok(_client) = LocalSocketTransport::connect(&name) else {\n 2869|            return false;\n 2870|        };\n*2871|        let Ok(server) = listener.accept() else {\n 2872|            return false;\n 2873|        };\n 2874|        let (_recv, send_half) = server.split();\n...\n 2919|    /// drives its handler to EOF cleanup within the write bound — without the\n*2920|    /// black-holed client dropping its end.\n 2921|    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 2922|    pub fn conn_handler_count(&self) -> usize {\n 2923|        self.conn_handlers.load(Ordering::Acquire)\n...\n 3024|    /// restart must not disturb the hosted children).\n*3025|    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n 3026|        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n 3027|        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028|        struct HandlerTally<'a>(&'a AtomicUsize);\n...\n 3042|        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n*3043|        // Base attribution fact (F-039 leg b): broker clients are brains; role\n 3044|        // facts (controller/viewer/presence/stream subscriber) accrete at the\n 3045|        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n 3046|        send.describe(\"role=brain\");\n...\n 3145|                }\n*3146|                KIND_NET_STREAM_RETIRE => {\n*3147|                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n 3148|                        send_error(&send, &msg);\n 3149|                    }\n 3150|                }\n...\n 4100|    /// trust row + seed), and the TOTP code in the request must never land\n*4101|    /// in a durable journal. Blocks this client's channel for the ceremony's\n 4102|    /// (bounded) life; the reply is always a `pair-joined` frame, with\n 4103|    /// failures classified for the CLI's guided UX.\n 4104|    // [impl->REQ-SUBNET-2]\n...\n 4383|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*4384|    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4385|        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4386|            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n 4387|        let host = self.net.get().ok_or(\"net disabled\")?;\n...\n 4389|        let frame = Envelope::new(\n*4390|            KIND_NET_STREAM_RETIRED,\n 4391|            serde_json::to_value(NetStreamRetired {\n 4392|                stream_id: req.stream_id,\n 4393|                removed,\n...\n 4742|    //    removed. White-box over a real `controller_socket_pair()` (server SharedSend,\n*4743|    //    client Stream to read the wire) + a real `sync_channel` rx, exactly like the\n 4744|    //    existing viewer/controller writer-thread tests. No PTY, no network. ──────────\n 4745|\n*4746|    /// Read at most ONE more frame off `client` with a bounded wait, returning\n 4747|    /// `Some(env)` if a frame arrives before `wait` elapses, `None` on a clean EOF\n 4748|    /// (the writer dropped its send half with nothing further) or the timeout. A\n 4749|    /// dedicated reader thread + a channel `recv_timeout` so we never block the test\n...\n 4752|    /// a normal teardown: a normal `viewer_writer` exit drops its send-half clone, the\n*4753|    /// client read then hits EOF, and `read_frame` returns Err → `None`.\n*4754|    fn try_read_frame(client: Stream, wait: Duration) -> Option<Envelope> {\n 4755|        let (tx, rx) = sync_channel::<Option<Envelope>>(1);\n*4756|        let mut client = client;\n 4757|        thread::spawn(move || {\n*4758|            let r = read_frame(&mut client).ok();\n 4759|            let _ = tx.send(r);\n 4760|        });\n 4761|        rx.recv_timeout(wait).ok().flatten()\n...\n 4780|        {\n*4781|            let (send, client, _recv) = controller_socket_pair();\n 4782|            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4783|            let evicted = Arc::new(AtomicBool::new(false));\n 4784|            let w_evicted = Arc::clone(&evicted);\n...\n 4793|\n*4794|            let mut client = client;\n*4795|            let env = read_frame(&mut client).expect(\"an evicted close writes a final frame\");\n 4796|            assert_eq!(\n 4797|                env.kind, KIND_VIEWER_EVICTED,\n 4798|                \"an OVERFLOW-evicted viewer's writer emits a viewer_evicted marker as \\\n...\n 4805|\n*4806|        // ── NORMAL close: flag false → no marker; the client read hits EOF. ──\n 4807|        {\n*4808|            let (send, client, _recv) = controller_socket_pair();\n 4809|            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4810|            let evicted = Arc::new(AtomicBool::new(false)); // stays false\n 4811|            let w_evicted = Arc::clone(&evicted);\n...\n 4817|            writer.join().expect(\"normal writer joins\");\n*4818|            // Drop the broker's send-half clone so the client read sees EOF (otherwise\n 4819|            // the half stays open and the blocking read would hang).\n 4820|            drop(send);\n 4821|\n 4822|            assert!(\n*4823|                try_read_frame(client, Duration::from_secs(2)).is_none(),\n 4824|                \"a NORMAL teardown (evicted=false) writes NOTHING — no spurious \\\n 4825|                 viewer_evicted marker that would trigger an uncalled-for re-subscribe\"\n 4826|            );\n...\n 4851|        // after `append` drops the in-map sink.\n*4852|        let (vsend, _vclient, _vrecv) = controller_socket_pair();\n 4853|        let (vtx, _vrx) = sync_channel::<Envelope>(1);\n 4854|        let evicted = Arc::new(AtomicBool::new(false));\n 4855|        let observed = Arc::clone(&evicted); // survives the sink's removal\n...\n 4932|        // the b4 decision lives in append's `try_send`, and `send` is never written here.\n*4933|        let (csend, _cclient, _crecv) = controller_socket_pair();\n 4934|        let (ctx, _crx) = sync_channel::<CtrlMsg>(2);\n 4935|        log.controller = Some(ControllerSink {\n 4936|            attach_gen: 0,\n...\n 4944|        // A VIEWER with a generous channel we CAN drain — to prove it stays fed.\n*4945|        let (vsend, _vclient, _vrecv) = controller_socket_pair();\n 4946|        let (vtx, vrx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4947|        log.viewers\n 4948|            .insert(\n...\n 5569|    /// A connected local-socket pair for the unit: bind a listener, connect a\n*5570|    /// client `Stream`, accept the server `Stream`. Returns the SERVER send half\n 5571|    /// wrapped as a `SharedSend` (the broker side a `become_controller` writes\n*5572|    /// through) plus the CLIENT `Stream` (the brain side we read the wire off) and\n 5573|    /// the unused server RECV half (held so the server fd stays open while we\n*5574|    /// read). Mirrors `LocalSocketTransport`'s own round-trip test + `handle_conn`'s\n 5575|    /// `conn.split()`.\n 5576|    fn controller_socket_pair() -> (SharedSend, Stream, interprocess::local_socket::RecvHalf) {\n 5577|        use std::sync::atomic::AtomicU32;\n...\n 5585|        let listener = LocalSocketTransport::bind(&name).expect(\"bind listener\");\n*5586|        let client = LocalSocketTransport::connect(&name).expect(\"connect client\");\n*5587|        let server = listener.accept().expect(\"accept server side\");\n 5588|        let (recv, send) = server.split();\n 5589|        (\n 5590|            Arc::new(BrokerConn::new(send, brain_write_deadline())),\n*5591|            client,\n 5592|            recv,\n 5593|        )\n 5594|    }\n...\n 5599|    /// (no per-read deadline: Windows named pipes reject I/O timeouts).\n*5600|    fn drain_output_seqs(client: &mut Stream, n: usize) -> Vec<u64> {\n 5601|        let mut seqs = Vec::with_capacity(n);\n 5602|        while seqs.len() < n {\n*5603|            let env = read_frame(client).expect(\"output frame on the wire\");\n 5604|            if env.kind == crate::msg::KIND_OUTPUT {\n 5605|                let ev: crate::msg::OutputEvent =\n 5606|                    serde_json::from_value(env.payload).expect(\"output payload\");\n...\n 5647|    fn superseded_controller_writer_is_epoch_gated_silent_one_live_writer_per_conn() {\n*5648|        let (send, mut client, _recv) = controller_socket_pair();\n 5649|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5650|        // Two chunks in the ring (seqs 0,1); the grid now holds their screen state.\n 5651|        assert!(\n...\n 5681|\n*5682|        let seqs = drain_output_seqs(&mut client, 1);\n 5683|        assert_eq!(\n 5684|            seqs,\n 5685|            vec![2],\n...\n 5694|        // Windows named pipe while a writer still holds its send-half clone.\n*5695|        let leaked = try_read_frame(client, Duration::from_millis(400));\n 5696|        assert!(\n 5697|            leaked.is_none(),\n 5698|            \"a superseded writer-A must emit NOTHING; got a leaked frame {leaked:?}\"\n...\n 5716|    fn same_identity_resubscribe_refuses_a_stale_generation() {\n*5717|        let (live, _client_a, _recv_a) = controller_socket_pair();\n*5718|        let (stale, _client_b, _recv_b) = controller_socket_pair();\n 5719|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5720|\n 5721|        // The live replacement controller holds the slot at generation 200.\n...\n 5788|    fn gap_resume_resubscribe_preserves_the_generation_against_stale_replay() {\n*5789|        let (worker, _client_a, _recv_a) = controller_socket_pair();\n*5790|        let (stale, _client_b, _recv_b) = controller_socket_pair();\n 5791|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5792|\n 5793|        // The serve worker attaches with its generation (serve_gen = 200).\n...\n 5847|    fn stale_generation_detach_never_clears_the_newer_controller() {\n*5848|        let (send, _client, _recv) = controller_socket_pair();\n 5849|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5850|        log.become_controller(Arc::clone(&send), Some(\"op\".into()), 0, 200);\n 5851|\n...\n 5878|    /// be ONE synthesized clean current-screen repaint, NOT the raw ring replayed\n*5879|    /// chunk-by-chunk: it paints the alt viewport, switches the client into the alt\n 5880|    /// buffer, and does NOT carry the main-screen scrollback that preceded the\n 5881|    /// alt-enter (the exact corruption #6/#7/#8 report on `rc` to a running TUI).\n 5882|    // [int->REQ-BROKER-SCREEN-GRID]\n...\n 5884|    fn attach_initial_batch_is_a_clean_alt_screen_repaint_not_the_raw_ring() {\n*5885|        let (send, mut client, _recv) = controller_socket_pair();\n 5886|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5887|        // The raw ring holds ALL of these chunks; a raw replay would dump the main\n 5888|        // scrollback + the alt-enter into a fresh terminal — the #6 corruption.\n...\n 5904|        // The initial batch is ONE output frame carrying the synthesized repaint.\n*5905|        let frame = read_frame(&mut client).expect(\"a repaint frame on attach\");\n 5906|        assert_eq!(frame.kind, crate::msg::KIND_OUTPUT, \"the initial batch is an output frame\");\n 5907|        let ev: crate::msg::OutputEvent =\n 5908|            serde_json::from_value(frame.payload).expect(\"output payload\");\n...\n 5922|            s.contains(\"\\x1b[?1049h\"),\n*5923|            \"the repaint switches the client into the alt buffer so its own scrollback \\\n 5924|             is untouched: {s:?}\"\n 5925|        );\n 5926|\n...\n 5928|        assert!(\n*5929|            try_read_frame(client, Duration::from_millis(300)).is_none(),\n 5930|            \"the initial batch is ONE clean repaint, not the raw ring replayed chunk-by-chunk\"\n 5931|        );\n 5932|\n...\n 5945|    fn reap_dead_controller_clears_a_finished_writer() {\n*5946|        let (send, _client, _recv) = controller_socket_pair();\n 5947|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5948|        // A controller whose writer thread has already EXITED (models a writer that\n 5949|        // returned after a failed socket write on a severed conn).\n...\n 5989|    fn reap_dead_controller_leaves_a_live_writer_untouched() {\n*5990|        let (send, _client, _recv) = controller_socket_pair();\n 5991|        let mut log = OutputLog::new(2, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5992|        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 5993|        let writer = thread::spawn(move || {\n...\n 6023|    ) -> Arc<Mutex<Option<Instant>>> {\n*6024|        // The peer client/recv can drop immediately: no writer in these fixtures ever\n 6025|        // writes to `send` (the thread only parks on rx.recv()), so a closed peer is\n 6026|        // irrelevant — the stall signal is the injected `write_blocked_since`, not a\n 6027|        // real socket block.\n*6028|        let (send, _client, _recv) = controller_socket_pair();\n 6029|        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 6030|        let write_blocked_since = Arc::new(Mutex::new(blocked));\n 6031|        let writer = thread::spawn(move || {"}],"details":{"scopePath":"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\\spt-core","matchCount":64,"fileCount":1,"files":["crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":64}],"truncated":false,"displayContent":"    5│//! the spawned child (both via [`spt_term::PtySession`]), and the accepted\n   *6│//! client / listening sockets. It runs **no logic** — no routing, registry,\n    7│//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,\n    8│//! forward its output to the brain, inject the brain's input, report exit.\n    9│//!\n     │...\n   66│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n  *67│    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n   68│    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n   69│    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n   70│};\n     │...\n  160│/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n *161│/// protects the fan-out against ONE per-client conn falling behind and is\n  162│/// output-driven (the drain observes a Full channel); THIS bound is measured\n  163│/// full-with-**zero writer progress** and covers the case the output-driven path\n  164│/// can't — an IDLE session whose brain-subscriber conn is suspended produces no\n     │...\n  993│    /// no output, conn not yet EOF'd) stays `is_finished() == false` and converges\n *994│    /// only once output resumes or the conn EOFs (→ `handle_conn` detach) — that\n  995│    /// harder active-probe case is the reserved [`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`],\n  996│    /// deliberately NOT built here. KH 7.15 held by construction: only ever CLEARS,\n  997│    /// never latches `driven_by`.\n     │...\n 2563│    /// The namespaced listener name — held so [`Broker::stop`] can dial a throwaway\n*2564│    /// connection to unblock the blocked `accept()` in [`serve`](Self::serve) (there is\n 2565│    /// no other wakeup for the accept loop).\n 2566│    name: String,\n 2567│    /// Graceful-stop latch (C1, REQ-HAZARD-DAEMON-STOP-BARRIER teardown-hygiene):\n     │...\n 2605│    wake_inflight: Mutex<HashSet<String>>,\n*2606│    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n 2607│    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n 2608│    /// UPDATE-WEDGE round-4 int gate's observability seam\n 2609│    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n 2610│    /// physical conn must drive its handler to EOF cleanup within the write\n*2611│    /// bound WITHOUT the client dropping — the test watches this count fall.\n 2612│    conn_handlers: AtomicUsize,\n 2613│}\n 2614│\n     │...\n 2784│    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n*2785│    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n 2786│        loop {\n*2787│            let conn = self.listener.accept()?;\n 2788│            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n 2789│            // throwaway connection that woke this accept — observe it and return so the\n 2790│            // serve thread (and, with it, the listener) tears down instead of leaking.\n     │...\n 2795│            thread::spawn(move || {\n*2796│                let _ = me.handle_conn(conn);\n 2797│            });\n 2798│        }\n 2799│    }\n     │...\n 2801│    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n*2802│    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n 2803│    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n 2804│    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n 2805│    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n     │...\n 2815│    pub fn serve_once(self: &Arc<Self>) -> io::Result<JoinHandle<()>> {\n*2816│        let conn = self.listener.accept()?;\n 2817│        let me = Arc::clone(self);\n 2818│        Ok(thread::spawn(move || {\n*2819│            let _ = me.handle_conn(conn);\n 2820│        }))\n 2821│    }\n 2822│\n     │...\n 2867│        };\n*2868│        let Ok(_client) = LocalSocketTransport::connect(&name) else {\n 2869│            return false;\n 2870│        };\n*2871│        let Ok(server) = listener.accept() else {\n 2872│            return false;\n 2873│        };\n 2874│        let (_recv, send_half) = server.split();\n     │...\n 2919│    /// drives its handler to EOF cleanup within the write bound — without the\n*2920│    /// black-holed client dropping its end.\n 2921│    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n 2922│    pub fn conn_handler_count(&self) -> usize {\n 2923│        self.conn_handlers.load(Ordering::Acquire)\n     │...\n 3024│    /// restart must not disturb the hosted children).\n*3025│    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n 3026│        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n 3027│        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028│        struct HandlerTally<'a>(&'a AtomicUsize);\n     │...\n 3042│        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n*3043│        // Base attribution fact (F-039 leg b): broker clients are brains; role\n 3044│        // facts (controller/viewer/presence/stream subscriber) accrete at the\n 3045│        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n 3046│        send.describe(\"role=brain\");\n     │...\n 3145│                }\n*3146│                KIND_NET_STREAM_RETIRE => {\n*3147│                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n 3148│                        send_error(&send, &msg);\n 3149│                    }\n 3150│                }\n     │...\n 4100│    /// trust row + seed), and the TOTP code in the request must never land\n*4101│    /// in a durable journal. Blocks this client's channel for the ceremony's\n 4102│    /// (bounded) life; the reply is always a `pair-joined` frame, with\n 4103│    /// failures classified for the CLI's guided UX.\n 4104│    // [impl->REQ-SUBNET-2]\n     │...\n 4383│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*4384│    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4385│        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4386│            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n 4387│        let host = self.net.get().ok_or(\"net disabled\")?;\n     │...\n 4389│        let frame = Envelope::new(\n*4390│            KIND_NET_STREAM_RETIRED,\n 4391│            serde_json::to_value(NetStreamRetired {\n 4392│                stream_id: req.stream_id,\n 4393│                removed,\n     │...\n 4742│    //    removed. White-box over a real `controller_socket_pair()` (server SharedSend,\n*4743│    //    client Stream to read the wire) + a real `sync_channel` rx, exactly like the\n 4744│    //    existing viewer/controller writer-thread tests. No PTY, no network. ──────────\n 4745│\n*4746│    /// Read at most ONE more frame off `client` with a bounded wait, returning\n 4747│    /// `Some(env)` if a frame arrives before `wait` elapses, `None` on a clean EOF\n 4748│    /// (the writer dropped its send half with nothing further) or the timeout. A\n 4749│    /// dedicated reader thread + a channel `recv_timeout` so we never block the test\n     │...\n 4752│    /// a normal teardown: a normal `viewer_writer` exit drops its send-half clone, the\n*4753│    /// client read then hits EOF, and `read_frame` returns Err → `None`.\n*4754│    fn try_read_frame(client: Stream, wait: Duration) -> Option<Envelope> {\n 4755│        let (tx, rx) = sync_channel::<Option<Envelope>>(1);\n*4756│        let mut client = client;\n 4757│        thread::spawn(move || {\n*4758│            let r = read_frame(&mut client).ok();\n 4759│            let _ = tx.send(r);\n 4760│        });\n 4761│        rx.recv_timeout(wait).ok().flatten()\n     │...\n 4780│        {\n*4781│            let (send, client, _recv) = controller_socket_pair();\n 4782│            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4783│            let evicted = Arc::new(AtomicBool::new(false));\n 4784│            let w_evicted = Arc::clone(&evicted);\n     │...\n 4793│\n*4794│            let mut client = client;\n*4795│            let env = read_frame(&mut client).expect(\"an evicted close writes a final frame\");\n 4796│            assert_eq!(\n 4797│                env.kind, KIND_VIEWER_EVICTED,\n 4798│                \"an OVERFLOW-evicted viewer's writer emits a viewer_evicted marker as \\\n     │...\n 4805│\n*4806│        // ── NORMAL close: flag false → no marker; the client read hits EOF. ──\n 4807│        {\n*4808│            let (send, client, _recv) = controller_socket_pair();\n 4809│            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4810│            let evicted = Arc::new(AtomicBool::new(false)); // stays false\n 4811│            let w_evicted = Arc::clone(&evicted);\n     │...\n 4817│            writer.join().expect(\"normal writer joins\");\n*4818│            // Drop the broker's send-half clone so the client read sees EOF (otherwise\n 4819│            // the half stays open and the blocking read would hang).\n 4820│            drop(send);\n 4821│\n 4822│            assert!(\n*4823│                try_read_frame(client, Duration::from_secs(2)).is_none(),\n 4824│                \"a NORMAL teardown (evicted=false) writes NOTHING — no spurious \\\n 4825│                 viewer_evicted marker that would trigger an uncalled-for re-subscribe\"\n 4826│            );\n     │...\n 4851│        // after `append` drops the in-map sink.\n*4852│        let (vsend, _vclient, _vrecv) = controller_socket_pair();\n 4853│        let (vtx, _vrx) = sync_channel::<Envelope>(1);\n 4854│        let evicted = Arc::new(AtomicBool::new(false));\n 4855│        let observed = Arc::clone(&evicted); // survives the sink's removal\n     │...\n 4932│        // the b4 decision lives in append's `try_send`, and `send` is never written here.\n*4933│        let (csend, _cclient, _crecv) = controller_socket_pair();\n 4934│        let (ctx, _crx) = sync_channel::<CtrlMsg>(2);\n 4935│        log.controller = Some(ControllerSink {\n 4936│            attach_gen: 0,\n     │...\n 4944│        // A VIEWER with a generous channel we CAN drain — to prove it stays fed.\n*4945│        let (vsend, _vclient, _vrecv) = controller_socket_pair();\n 4946│        let (vtx, vrx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);\n 4947│        log.viewers\n 4948│            .insert(\n     │...\n 5569│    /// A connected local-socket pair for the unit: bind a listener, connect a\n*5570│    /// client `Stream`, accept the server `Stream`. Returns the SERVER send half\n 5571│    /// wrapped as a `SharedSend` (the broker side a `become_controller` writes\n*5572│    /// through) plus the CLIENT `Stream` (the brain side we read the wire off) and\n 5573│    /// the unused server RECV half (held so the server fd stays open while we\n*5574│    /// read). Mirrors `LocalSocketTransport`'s own round-trip test + `handle_conn`'s\n 5575│    /// `conn.split()`.\n 5576│    fn controller_socket_pair() -> (SharedSend, Stream, interprocess::local_socket::RecvHalf) {\n 5577│        use std::sync::atomic::AtomicU32;\n     │...\n 5585│        let listener = LocalSocketTransport::bind(&name).expect(\"bind listener\");\n*5586│        let client = LocalSocketTransport::connect(&name).expect(\"connect client\");\n*5587│        let server = listener.accept().expect(\"accept server side\");\n 5588│        let (recv, send) = server.split();\n 5589│        (\n 5590│            Arc::new(BrokerConn::new(send, brain_write_deadline())),\n*5591│            client,\n 5592│            recv,\n 5593│        )\n 5594│    }\n     │...\n 5599│    /// (no per-read deadline: Windows named pipes reject I/O timeouts).\n*5600│    fn drain_output_seqs(client: &mut Stream, n: usize) -> Vec<u64> {\n 5601│        let mut seqs = Vec::with_capacity(n);\n 5602│        while seqs.len() < n {\n*5603│            let env = read_frame(client).expect(\"output frame on the wire\");\n 5604│            if env.kind == crate::msg::KIND_OUTPUT {\n 5605│                let ev: crate::msg::OutputEvent =\n 5606│                    serde_json::from_value(env.payload).expect(\"output payload\");\n     │...\n 5647│    fn superseded_controller_writer_is_epoch_gated_silent_one_live_writer_per_conn() {\n*5648│        let (send, mut client, _recv) = controller_socket_pair();\n 5649│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5650│        // Two chunks in the ring (seqs 0,1); the grid now holds their screen state.\n 5651│        assert!(\n     │...\n 5681│\n*5682│        let seqs = drain_output_seqs(&mut client, 1);\n 5683│        assert_eq!(\n 5684│            seqs,\n 5685│            vec![2],\n     │...\n 5694│        // Windows named pipe while a writer still holds its send-half clone.\n*5695│        let leaked = try_read_frame(client, Duration::from_millis(400));\n 5696│        assert!(\n 5697│            leaked.is_none(),\n 5698│            \"a superseded writer-A must emit NOTHING; got a leaked frame {leaked:?}\"\n     │...\n 5716│    fn same_identity_resubscribe_refuses_a_stale_generation() {\n*5717│        let (live, _client_a, _recv_a) = controller_socket_pair();\n*5718│        let (stale, _client_b, _recv_b) = controller_socket_pair();\n 5719│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5720│\n 5721│        // The live replacement controller holds the slot at generation 200.\n     │...\n 5788│    fn gap_resume_resubscribe_preserves_the_generation_against_stale_replay() {\n*5789│        let (worker, _client_a, _recv_a) = controller_socket_pair();\n*5790│        let (stale, _client_b, _recv_b) = controller_socket_pair();\n 5791│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5792│\n 5793│        // The serve worker attaches with its generation (serve_gen = 200).\n     │...\n 5847│    fn stale_generation_detach_never_clears_the_newer_controller() {\n*5848│        let (send, _client, _recv) = controller_socket_pair();\n 5849│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5850│        log.become_controller(Arc::clone(&send), Some(\"op\".into()), 0, 200);\n 5851│\n     │...\n 5878│    /// be ONE synthesized clean current-screen repaint, NOT the raw ring replayed\n*5879│    /// chunk-by-chunk: it paints the alt viewport, switches the client into the alt\n 5880│    /// buffer, and does NOT carry the main-screen scrollback that preceded the\n 5881│    /// alt-enter (the exact corruption #6/#7/#8 report on `rc` to a running TUI).\n 5882│    // [int->REQ-BROKER-SCREEN-GRID]\n     │...\n 5884│    fn attach_initial_batch_is_a_clean_alt_screen_repaint_not_the_raw_ring() {\n*5885│        let (send, mut client, _recv) = controller_socket_pair();\n 5886│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5887│        // The raw ring holds ALL of these chunks; a raw replay would dump the main\n 5888│        // scrollback + the alt-enter into a fresh terminal — the #6 corruption.\n     │...\n 5904│        // The initial batch is ONE output frame carrying the synthesized repaint.\n*5905│        let frame = read_frame(&mut client).expect(\"a repaint frame on attach\");\n 5906│        assert_eq!(frame.kind, crate::msg::KIND_OUTPUT, \"the initial batch is an output frame\");\n 5907│        let ev: crate::msg::OutputEvent =\n 5908│            serde_json::from_value(frame.payload).expect(\"output payload\");\n     │...\n 5922│            s.contains(\"\\x1b[?1049h\"),\n*5923│            \"the repaint switches the client into the alt buffer so its own scrollback \\\n 5924│             is untouched: {s:?}\"\n 5925│        );\n 5926│\n     │...\n 5928│        assert!(\n*5929│            try_read_frame(client, Duration::from_millis(300)).is_none(),\n 5930│            \"the initial batch is ONE clean repaint, not the raw ring replayed chunk-by-chunk\"\n 5931│        );\n 5932│\n     │...\n 5945│    fn reap_dead_controller_clears_a_finished_writer() {\n*5946│        let (send, _client, _recv) = controller_socket_pair();\n 5947│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5948│        // A controller whose writer thread has already EXITED (models a writer that\n 5949│        // returned after a failed socket write on a severed conn).\n     │...\n 5989│    fn reap_dead_controller_leaves_a_live_writer_untouched() {\n*5990│        let (send, _client, _recv) = controller_socket_pair();\n 5991│        let mut log = OutputLog::new(2, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 5992│        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 5993│        let writer = thread::spawn(move || {\n     │...\n 6023│    ) -> Arc<Mutex<Option<Instant>>> {\n*6024│        // The peer client/recv can drop immediately: no writer in these fixtures ever\n 6025│        // writes to `send` (the thread only parks on rx.recv()), so a closed peer is\n 6026│        // irrelevant — the stall signal is the injected `write_blocked_since`, not a\n 6027│        // real socket block.\n*6028│        let (send, _client, _recv) = controller_socket_pair();\n 6029│        let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);\n 6030│        let write_blocked_since = Arc::new(Mutex::new(blocked));\n 6031│        let writer = thread::spawn(move || {"},"isError":false,"timestamp":1784271556488}}
{"type":"message","id":"689c76b7","parentId":"c59b0e72","timestamp":"2026-07-17T06:59:16.490Z","message":{"role":"toolResult","toolCallId":"call_QLsdYc8LXSCIz403S7LRBjCs|fc_089095e18118ee7d016a59d2c467308190927c7f5168b6c868","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## dispatch.rs\n 446|    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n*447|    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n 448|    // with a large backlog) claims at most this many rows per poll — the\n 449|    // rest wait for the next tick, batching the cold enumeration instead of\n 450|    // spawning one thread per row. No claim lock is held across any I/O:\n...\n 452|    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n*453|    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n 454|\n 455|    while !stop.load(Ordering::Relaxed) {\n 456|        if breaker.lock().unwrap().open(Instant::now()) {\n...\n 472|            }\n*473|            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n 474|                break; // pool full: the rest of the table waits for the next poll\n 475|            }\n 476|            let attempts = {\n...\n 495|            let breaker_done = Arc::clone(&breaker);\n*496|            let pool = Arc::clone(&active_workers);\n 497|            pool.fetch_add(1, Ordering::Relaxed);\n 498|            thread::spawn(move || {\n 499|                let started = Instant::now();\n...\n 583|/// table claims at most this many rows per poll tick.\n*584|const MAX_DISPATCH_WORKERS: usize = 8;\n 585|\n 586|/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n 587|/// now? The cold-enumeration break rides this — a refresh with a large\n*588|/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n 589|/// wait for the next tick (batched, never one thread per row).\n 590|// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n*591|fn pool_has_room(active: usize) -> bool {\n*592|    active < MAX_DISPATCH_WORKERS\n 593|}\n 594|\n 595|/// Connect a fresh brain IPC client (retrying through broker startup).\n...\n 1474|    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n*1475|    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n 1476|    // the rest wait for the next tick; a freed slot re-admits.\n 1477|    #[test]\n 1478|    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n...\n 1481|        for _row in 0..50 {\n*1482|            if !pool_has_room(active) {\n 1483|                break; // the loop's cold-table break\n 1484|            }\n 1485|            active += 1;\n...\n 1488|        assert_eq!(\n*1489|            claimed, MAX_DISPATCH_WORKERS,\n 1490|            \"a 50-row cold table claims exactly the pool bound in one poll\"\n 1491|        );\n 1492|        // A worker finishing frees a slot for the next tick.\n 1493|        active -= 1;\n*1494|        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n 1495|    }\n 1496|\n 1497|    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs, crates/spt-daemon/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":12,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":12}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## dispatch.rs\n  446│    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n *447│    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n  448│    // with a large backlog) claims at most this many rows per poll — the\n  449│    // rest wait for the next tick, batching the cold enumeration instead of\n  450│    // spawning one thread per row. No claim lock is held across any I/O:\n     │...\n  452│    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n *453│    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n  454│\n  455│    while !stop.load(Ordering::Relaxed) {\n  456│        if breaker.lock().unwrap().open(Instant::now()) {\n     │...\n  472│            }\n *473│            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n  474│                break; // pool full: the rest of the table waits for the next poll\n  475│            }\n  476│            let attempts = {\n     │...\n  495│            let breaker_done = Arc::clone(&breaker);\n *496│            let pool = Arc::clone(&active_workers);\n  497│            pool.fetch_add(1, Ordering::Relaxed);\n  498│            thread::spawn(move || {\n  499│                let started = Instant::now();\n     │...\n  583│/// table claims at most this many rows per poll tick.\n *584│const MAX_DISPATCH_WORKERS: usize = 8;\n  585│\n  586│/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n  587│/// now? The cold-enumeration break rides this — a refresh with a large\n *588│/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n  589│/// wait for the next tick (batched, never one thread per row).\n  590│// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n *591│fn pool_has_room(active: usize) -> bool {\n *592│    active < MAX_DISPATCH_WORKERS\n  593│}\n  594│\n  595│/// Connect a fresh brain IPC client (retrying through broker startup).\n     │...\n 1474│    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n*1475│    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n 1476│    // the rest wait for the next tick; a freed slot re-admits.\n 1477│    #[test]\n 1478│    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n     │...\n 1481│        for _row in 0..50 {\n*1482│            if !pool_has_room(active) {\n 1483│                break; // the loop's cold-table break\n 1484│            }\n 1485│            active += 1;\n     │...\n 1488│        assert_eq!(\n*1489│            claimed, MAX_DISPATCH_WORKERS,\n 1490│            \"a 50-row cold table claims exactly the pool bound in one poll\"\n 1491│        );\n 1492│        // A worker finishing frees a slot for the next tick.\n 1493│        active -= 1;\n*1494│        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n 1495│    }\n 1496│\n 1497│    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key"},"isError":false,"timestamp":1784271556490}}
{"type":"message","id":"08455d05","parentId":"689c76b7","timestamp":"2026-07-17T06:59:16.496Z","message":{"role":"toolResult","toolCallId":"call_motA9ndgx4pKcNhkJTYSpseD|fc_089095e18118ee7d016a59d2c4673c81909dd47b76e7597a4a","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 cb70090bf20963a89d814cf8f5d8e750d4413afa Reavo End <decidel@gmail.com> 1784254964 -0700\tupdate by push\ncb70090bf20963a89d814cf8f5d8e750d4413afa 73b29b858bbffb2c758382c80d74def0aabb328f Reavo End <decidel@gmail.com> 1784255747 -0700\tupdate by push\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 cb70090bf20963a89d814cf8f5d8e750d4413afa Reavo End <decidel@gmail.com> 1784254964 -0700\tupdate by push\ncb70090bf20963a89d814cf8f5d8e750d4413afa 73b29b858bbffb2c758382c80d74def0aabb328f Reavo End <decidel@gmail.com> 1784255747 -0700\tupdate by push\n","startLine":1,"lineNumbers":[1,2,3]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\remotes\\origin\\build\\redispatch-stall-w1"}}},"isError":false,"timestamp":1784271556496}}
{"type":"message","id":"a9167da8","parentId":"08455d05","timestamp":"2026-07-17T06:59:16.520Z","message":{"role":"toolResult","toolCallId":"call_LGnGPRMMCx8mEPJvwr5miMfO|fc_089095e18118ee7d016a59d2c467388190a198e4c8d6558f79","toolName":"read","content":[{"type":"text","text":"f16e2e0282c48192f01d94f0f87f4267388e511b 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 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573219 -0700\treset: moving to HEAD\n134f0fea01d9fe606ee08b9db0013274ade7862c 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783576224 -0700\tcommit: triage(f-034): register 3 core legs for /sptc:live bringup cascade (perri/hertz field finding)\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783658323 -0700\tcheckout: moving from main to triage/update-wedge-rca-trace\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n59cf48212449e4f198826e14f2808c014db2f99e 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\nc3a86a6cf21683a2262f99f4938b1376d46678b0 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710844 -0700\tcheckout: moving from triage/update-wedge-rca-trace to build/msg-identity-w1-f036\n17b770a18c858182038284f08e8ba0d93bb91263 abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783712376 -0700\tcommit: feat(msg-identity): W1 F-036 — identity-env sanitize + psyche-custody squat guard\nabaf08f7e77338cb525c79feb29f821c772c1eca abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783722307 -0700\tcheckout: moving from build/msg-identity-w1-f036 to build/msg-identity-w2-f034\nabaf08f7e77338cb525c79feb29f821c772c1eca 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)\n32b134ef314c84cc737124085c615cf8a2ab60d6 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783723638 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w3-f032\n32b134ef314c84cc737124085c615cf8a2ab60d6 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783724233 -0700\tcommit: fix(msg-identity): W3 F-032 — commune ingest never deletes an un-committed project slice\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783724295 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783726485 -0700\tcommit: fix(msg-identity): W4 buildable legs — live_agent inject exclusion + shelled-send agent-id stamp\neb8a8c2a3c82e15394e63087dc312def8fe66841 eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783726557 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w5-f038\neb8a8c2a3c82e15394e63087dc312def8fe66841 c260122ff8d696553a15514d084d5279133e5c9b Reavo End <decidel@gmail.com> 1783727510 -0700\tcommit: wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\nc260122ff8d696553a15514d084d5279133e5c9b eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783727511 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w4-delivery\neb8a8c2a3c82e15394e63087dc312def8fe66841 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783728207 -0700\tcommit: fix(msg-identity): W4 carrier-claim — one atomic cross-carrier take, audit stays truthful\n4b29512434c02998467d594dedd8965eb79212c2 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783728275 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w2-f034\n32b134ef314c84cc737124085c615cf8a2ab60d6 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783729426 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w4-delivery\n4b29512434c02998467d594dedd8965eb79212c2 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783729571 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w2-f034\n32b134ef314c84cc737124085c615cf8a2ab60d6 53b1d59e1e0bcdd3c5e3aa501015e879fceba179 Reavo End <decidel@gmail.com> 1783729675 -0700\tcommit: fix(msg-identity): W2 rework — seed spend-vs-restore split, sid bearer provenance, squat guard on all three rotation seams\n53b1d59e1e0bcdd3c5e3aa501015e879fceba179 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783729702 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w4-delivery\n4b29512434c02998467d594dedd8965eb79212c2 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783729929 -0700\tcommit: revert(msg-identity): W4 — drop the blanket live_agent inject exclusion (F-033 leg)\n21928cb66a05f5eca81f78b9351d1332615d93a9 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783730009 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w3-f032\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783730496 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n21928cb66a05f5eca81f78b9351d1332615d93a9 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783730525 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w3-f032\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 17dc68ea7d72e00d77bb9ff0622028dd5f9fa878 Reavo End <decidel@gmail.com> 1783730525 -0700\tcommit: fix(msg-identity): W3 rework — deferred pending always rides the commune suffix\n17dc68ea7d72e00d77bb9ff0622028dd5f9fa878 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783730541 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n21928cb66a05f5eca81f78b9351d1332615d93a9 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783730602 -0700\tcommit: docs(msg-identity): re-scope REQ-LIVE-AGENT-NO-INJECT-DELIVERY — hosting-mode class split (doyle re-ruling)\na632e2b19802a82f72ad2523ea064e36aab423b1 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (start): checkout build/msg-identity-w4-delivery\na632e2b19802a82f72ad2523ea064e36aab423b1 42ecada13ad198393aad5b8b8764f16c3c5b04c8 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (pick): wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\n42ecada13ad198393aad5b8b8764f16c3c5b04c8 42ecada13ad198393aad5b8b8764f16c3c5b04c8 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (finish): returning to refs/heads/build/msg-identity-w5-f038\n42ecada13ad198393aad5b8b8764f16c3c5b04c8 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783732066 -0700\tcommit: feat(msg-identity): W5 F-038 — endpoint startup defaults (run --save + daemon-boot replay)\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783734139 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w4-delivery\na632e2b19802a82f72ad2523ea064e36aab423b1 6236b39753ff9afebc1d7d6029ca02e9e51b3a94 Reavo End <decidel@gmail.com> 1783734338 -0700\tcommit: fix(msg-identity): W4 echo-scrub — ambient inject-echo knobs die at daemon startup (F-033 leg b)\n6236b39753ff9afebc1d7d6029ca02e9e51b3a94 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783734395 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w6-f039\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 6236b39753ff9afebc1d7d6029ca02e9e51b3a94 Reavo End <decidel@gmail.com> 1783734461 -0700\tcheckout: moving from build/msg-identity-w6-f039 to build/msg-identity-w4-delivery\n6236b39753ff9afebc1d7d6029ca02e9e51b3a94 916bd2af87969b3b64e083070ff8ef54cf2aa4da Reavo End <decidel@gmail.com> 1783734738 -0700\tcommit: docs(msg-identity): W4 F-033 residual-class flip — REQ-LIVE-AGENT-NO-INJECT-DELIVERY [impl,unit] (doyle ruling)\n916bd2af87969b3b64e083070ff8ef54cf2aa4da 2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 Reavo End <decidel@gmail.com> 1783734952 -0700\tcommit: test(msg-identity): W4 carrier-race rig hardening — DatabaseBusy is yield+retry, not a panic (doyle ruling)\n2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783734968 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w5-f038\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (start): checkout build/msg-identity-w4-delivery\n2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 262c4663dbdbdf6358d2f626e038b0c4f9b70b2f Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (pick): wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\n262c4663dbdbdf6358d2f626e038b0c4f9b70b2f 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (pick): feat(msg-identity): W5 F-038 — endpoint startup defaults (run --save + daemon-boot replay)\n315caf7df04773ae11161da189d2f10dc11a2e1b 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (finish): returning to refs/heads/build/msg-identity-w5-f038\n315caf7df04773ae11161da189d2f10dc11a2e1b 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783735037 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w6-f039\n315caf7df04773ae11161da189d2f10dc11a2e1b 4e0916cdf223756ce368fca934d3b7c7fd05688e Reavo End <decidel@gmail.com> 1783735239 -0700\tcommit: docs(msg-identity): W6 F-039 REQ moves — RCA-lock addendum + attribution/harness mints (doyle W6 LOCK)\n4e0916cdf223756ce368fca934d3b7c7fd05688e fc048bb41560b629c8cb1d158f8587f67f895e39 Reavo End <decidel@gmail.com> 1783736665 -0700\tcommit: feat(msg-identity): W6 F-039 legs a-d — poison token split + conn attribution + canary isolation\nfc048bb41560b629c8cb1d158f8587f67f895e39 8951793a58dc7db5cb05c38b322e604aaf080be2 Reavo End <decidel@gmail.com> 1783736859 -0700\tcommit: test(msg-identity): W6 rider — r4 gate accepts poison-beats-evict tally race (doyle ruling)\n8951793a58dc7db5cb05c38b322e604aaf080be2 fba81ec78484804b341e5ce726bf3dd9fbddf82f Reavo End <decidel@gmail.com> 1783737642 -0700\tcommit: test(msg-identity): W6 F-039 leg e — black-holed-controller lifecycle harness\nfba81ec78484804b341e5ce726bf3dd9fbddf82f 06a2d74d0740577f740e0d94f31dc78982dfbcca Reavo End <decidel@gmail.com> 1783739468 -0700\tcommit: test(msg-identity): W6 leg-e rework — survivor-identity snapshot closes the leaked-handler false-green (doyle R1+R2)\n06a2d74d0740577f740e0d94f31dc78982dfbcca 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783740437 -0700\tcheckout: moving from build/msg-identity-w6-f039 to main\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740438 -0700\tpull origin main: Fast-forward\na6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740541 -0700\tcheckout: moving from main to main\na6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740809 -0700\tcheckout: moving from main to release/v0.31.0\na6ff50193646ddf8c68deee300ce535565297840 97a0e4f7d14c8a260e600c455e3286579cb0f85e Reavo End <decidel@gmail.com> 1783741119 -0700\tcommit: release: v0.31.0 — messaging delivery-integrity + endpoint identity (counter 58)\n97a0e4f7d14c8a260e600c455e3286579cb0f85e a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783743756 -0700\tcheckout: moving from release/v0.31.0 to main\na6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\nc9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784085971 -0700\tcheckout: moving from main to build/forkening-w1-gh-transport\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 2552ee65f9dd04b5ee199968adfbe5ba062abe84 Reavo End <decidel@gmail.com> 1784089127 -0700\tcommit: build(forkening-w1): gh transport for self-update + spt install bootstrap verb — REQ-UPDATE-GH-TRANSPORT + REQ-INSTALL-BOOTSTRAP-VERB\n2552ee65f9dd04b5ee199968adfbe5ba062abe84 a841f5a44d0fe881f10725e6eb43ea5a80f50b08 Reavo End <decidel@gmail.com> 1784092104 -0700\tcommit: fix(forkening-w1): consent-notif test asserts CONSENT_CHANGELOG_URL const, not a hardcoded channel substring\na841f5a44d0fe881f10725e6eb43ea5a80f50b08 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784093957 -0700\tcheckout: moving from build/forkening-w1-gh-transport to main\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093961 -0700\tpull origin main: Fast-forward\n832b982069e4e473db79637d894e528c9018d3ef 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093965 -0700\tcheckout: moving from main to build/forkening-w2-docs-release\n832b982069e4e473db79637d894e528c9018d3ef 8981d816132884933df3e02d9c12d556d772f807 Reavo End <decidel@gmail.com> 1784099507 -0700\tcommit: build(forkening-w2): signed docs bundle + private release channel — REQ-DOCS-RELEASE-ASSET + REQ-RELEASE-CHANNEL-PRIVATE\n8981d816132884933df3e02d9c12d556d772f807 052f7ad9d5ff96ea2ed942fda9797ef9af258532 Reavo End <decidel@gmail.com> 1784102514 -0700\tcommit: fix(forkening-w2): allow too_many_arguments on build_signed_update_set\n052f7ad9d5ff96ea2ed942fda9797ef9af258532 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784110026 -0700\tcheckout: moving from build/forkening-w2-docs-release to main\n832b982069e4e473db79637d894e528c9018d3ef f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110030 -0700\tpull origin main: Fast-forward\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110032 -0700\tcheckout: moving from main to build/forkening-w3-docs-server\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 053e00867b0ce74c746114446be1eb0478cb5549 Reavo End <decidel@gmail.com> 1784111869 -0700\tcommit: build(forkening-w3): node-local docs server on loopback:5474 — REQ-DOCS-LOCAL-SERVER\n053e00867b0ce74c746114446be1eb0478cb5549 f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784114265 -0700\tcheckout: moving from build/forkening-w3-docs-server to main\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114266 -0700\tpull origin main: Fast-forward\ncff7c1f5b1f6c072e794a38127835df88f960ee4 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114361 -0700\tcheckout: moving from main to build/forkening-w4-update-ux\ncff7c1f5b1f6c072e794a38127835df88f960ee4 66e930ce3a3c356f5ad4d9391c5625eedc15a3ca Reavo End <decidel@gmail.com> 1784116363 -0700\tcommit: build(forkening-w4): update-UX cluster — one-command update + daemon refresh\n66e930ce3a3c356f5ad4d9391c5625eedc15a3ca 5199464b6d4fff29b34a0555a1573d5985148660 Reavo End <decidel@gmail.com> 1784118326 -0700\tcommit: ci(forkening-w4): FLAKE-LEDGER #15 — daemon-tree e2es to the HEAVY class\n5199464b6d4fff29b34a0555a1573d5985148660 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784119444 -0700\tcheckout: moving from build/forkening-w4-update-ux to main\ncff7c1f5b1f6c072e794a38127835df88f960ee4 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119445 -0700\tpull origin main: Fast-forward\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tcheckout: moving from main to release/v0.32.0\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n6f9df02d3ef85a8bb1077a74690880dacff1e404 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784122293 -0700\tcheckout: moving from release/v0.32.0 to main\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\nca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162126 -0700\tcheckout: moving from main to build/project-index-w1-identity-store\ne5f053fd130f7eed1e40e927c0bab87d88bb007a 13bf7a262ac6c5132be22042d8549f2ec437aa94 Reavo End <decidel@gmail.com> 1784164947 -0700\tcommit: build(project-index-w1): identity-only whoami + the materialized index store\n13bf7a262ac6c5132be22042d8549f2ec437aa94 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784166403 -0700\tcheckout: moving from build/project-index-w1-identity-store to main\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166404 -0700\tpull --ff-only origin main: Fast-forward\ne4573279c6389023f5fc4f61ad88e27a12202a6f e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166405 -0700\tcheckout: moving from main to main\ne4573279c6389023f5fc4f61ad88e27a12202a6f e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166662 -0700\tcheckout: moving from main to build/project-index-w2-daemon-writer\ne4573279c6389023f5fc4f61ad88e27a12202a6f 1a68900fd2ebd01a0a934c2484c819883e478008 Reavo End <decidel@gmail.com> 1784169159 -0700\tcommit: build(project-index-w2): daemon single-flight index writer + event-driven invalidation\n1a68900fd2ebd01a0a934c2484c819883e478008 e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784170611 -0700\tcheckout: moving from build/project-index-w2-daemon-writer to main\ne4573279c6389023f5fc4f61ad88e27a12202a6f e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170613 -0700\tpull --ff-only origin main: Fast-forward\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170622 -0700\tcheckout: moving from main to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170731 -0700\tcheckout: moving from main to build/project-index-w3-reader-cutover\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784178494 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784178588 -0700\tcheckout: moving from main to build/project-index-w3-reader-cutover\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 53bae678013031f01391c68e736f730f5bda148e Reavo End <decidel@gmail.com> 1784185206 -0700\tcommit: build(project-index-w3): reader cutover — list/picker/endpoint-info consume the materialized index\n53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700\tcommit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover\nafd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700\tcommit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag\nd88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700\tcheckout: moving from main to main\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\neb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\nb7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\nd0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 37f0b38862a876e38d2ff4793746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700\tcommit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section\n37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700\tcommit: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700\tcheckout: moving from fix/changelog-0330-heading to main\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\nd41ceaab197af6ebf1da842400dff3a6518084bb 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239433 -0700\tcherry-pick: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n0bb37f352d871f288d3c1ab5af6133020d57c674 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239506 -0700\tcheckout: moving from main to main\n0bb37f352d871f288d3c1ab5af6133020d57c674 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239542 -0700\tcheckout: moving from main to build/mesh-recovery-w1\n0bb37f352d871f288d3c1ab5af6133020d57c674 18489c5ae579d68f91ad665552100cc43a3c3774 Reavo End <decidel@gmail.com> 1784243262 -0700\tcommit: build(mesh-recovery-w1): peer-route resolution truth — the route chain, nondestructive retention, cache invariant, stage-split health\n18489c5ae579d68f91ad665552100cc43a3c3774 66d9dc645d73dac49fd0e7c8f10b7bd079d647a9 Reavo End <decidel@gmail.com> 1784243788 -0700\tcommit: fix(mesh-recovery-w1): gate round 1 — the registry-admit stamp survives daemon restarts\n66d9dc645d73dac49fd0e7c8f10b7bd079d647a9 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784246970 -0700\tcheckout: moving from build/mesh-recovery-w1 to main\n0bb37f352d871f288d3c1ab5af6133020d57c674 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784246971 -0700\tmerge origin/main: Fast-forward\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247027 -0700\tcheckout: moving from main to main\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247046 -0700\tcheckout: moving from main to release/v0.35.0\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 83f90e7e622a0693ae8e6f929c5129352f08b818 Reavo End <decidel@gmail.com> 1784247250 -0700\tcommit: release: v0.35.0 (counter 62) — MESH-RECOVERY W1\n83f90e7e622a0693ae8e6f929c5129352f08b818 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784248182 -0700\tcheckout: moving from release/v0.35.0 to main\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248636 -0700\tcommit: triage(redispatch-stall): wave JIT — ADR-0038 amendment (replay/poison stall composition) + KH 7.43 + 5 REQs registry-first\n7488c1ac5111e38da469478f44886da9c0f7267e 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248676 -0700\tcheckout: moving from main to main\n7488c1ac5111e38da469478f44886da9c0f7267e 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248695 -0700\tcheckout: moving from main to build/redispatch-stall-w1\n7488c1ac5111e38da469478f44886da9c0f7267e cb70090bf20963a89d814cf8f5d8e750d4413afa Reavo End <decidel@gmail.com> 1784254956 -0700\tcommit: build(redispatch-stall-w1): wedged-subscriber stall composition killed — ADR-0038 Amendment fixes 1-9 + T1-T7\ncb70090bf20963a89d814cf8f5d8e750d4413afa 73b29b858bbffb2c758382c80d74def0aabb328f Reavo End <decidel@gmail.com> 1784255745 -0700\tcommit: fix(redispatch-stall-w1): gate round 1 — the gap-resume re-subscribe keeps its generation\n73b29b858bbffb2c758382c80d74def0aabb328f 8d5dcab22a4fe8e5a283c7ce182c3a57cf2fb402 Reavo End <decidel@gmail.com> 1784257372 -0700\tcheckout: moving from build/redispatch-stall-w1 to main\n8d5dcab22a4fe8e5a283c7ce182c3a57cf2fb402 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784257414 -0700\tcommit: test(redispatch-stall-w1): rider — scrub inject-echo env in the wedge rig init\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784258507 -0700\tcheckout: moving from main to release/v0.36.0\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784258639 -0700\tcommit: release: v0.36.0 (counter 63) — REDISPATCH-STALL W1\nb677030328cf7684d2b89076e8b03858d3ae0661 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784259901 -0700\tcheckout: moving from release/v0.36.0 to fix/redispatch-stall-w1-replay-order\nb677030328cf7684d2b89076e8b03858d3ae0661 fc2579964647cfbf26d26bf045b20c162cb44b54 Reavo End <decidel@gmail.com> 1784261857 -0700\tcommit: fix(redispatch-stall-w1): replay-then-live order is structural — the seat writer drains the replay first\nfc2579964647cfbf26d26bf045b20c162cb44b54 f4727cdd3da014fcd8097cbba3c63203fb6b38b2 Reavo End <decidel@gmail.com> 1784262300 -0700\tcommit: fix(redispatch-stall-w1): gate round 1 — every seat install mints its own lease generation\nf4727cdd3da014fcd8097cbba3c63203fb6b38b2 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784263680 -0700\tcheckout: moving from fix/redispatch-stall-w1-replay-order to main\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263681 -0700\tpull --ff-only: Fast-forward\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263736 -0700\tcheckout: moving from main to main\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784270311 -0700\tcheckout: moving from main to docs/v0360-notes-plain-language\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 d903b5de1a5728b8b5938714b52bf64017551fbc Reavo End <decidel@gmail.com> 1784270368 -0700\tcommit: docs(changelog): rewrite v0.36.0 notes in plain end-user language\n"}],"details":{"displayContent":{"text":"f16e2e0282c48192f01d94f0f87f4267388e511b 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 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573219 -0700\treset: moving to HEAD\n134f0fea01d9fe606ee08b9db0013274ade7862c 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783576224 -0700\tcommit: triage(f-034): register 3 core legs for /sptc:live bringup cascade (perri/hertz field finding)\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783658323 -0700\tcheckout: moving from main to triage/update-wedge-rca-trace\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700\tcommit: chore(rca): trace PTY wedge boundaries\n59cf48212449e4f198826e14f2808c014db2f99e 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\nc3a86a6cf21683a2262f99f4938b1376d46678b0 17b770a18c858182038284f08e8ba0d93bb91263 Reavo End <decidel@gmail.com> 1783710844 -0700\tcheckout: moving from triage/update-wedge-rca-trace to build/msg-identity-w1-f036\n17b770a18c858182038284f08e8ba0d93bb91263 abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783712376 -0700\tcommit: feat(msg-identity): W1 F-036 — identity-env sanitize + psyche-custody squat guard\nabaf08f7e77338cb525c79feb29f821c772c1eca abaf08f7e77338cb525c79feb29f821c772c1eca Reavo End <decidel@gmail.com> 1783722307 -0700\tcheckout: moving from build/msg-identity-w1-f036 to build/msg-identity-w2-f034\nabaf08f7e77338cb525c79feb29f821c772c1eca 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)\n32b134ef314c84cc737124085c615cf8a2ab60d6 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783723638 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w3-f032\n32b134ef314c84cc737124085c615cf8a2ab60d6 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783724233 -0700\tcommit: fix(msg-identity): W3 F-032 — commune ingest never deletes an un-committed project slice\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783724295 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783726485 -0700\tcommit: fix(msg-identity): W4 buildable legs — live_agent inject exclusion + shelled-send agent-id stamp\neb8a8c2a3c82e15394e63087dc312def8fe66841 eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783726557 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w5-f038\neb8a8c2a3c82e15394e63087dc312def8fe66841 c260122ff8d696553a15514d084d5279133e5c9b Reavo End <decidel@gmail.com> 1783727510 -0700\tcommit: wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\nc260122ff8d696553a15514d084d5279133e5c9b eb8a8c2a3c82e15394e63087dc312def8fe66841 Reavo End <decidel@gmail.com> 1783727511 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w4-delivery\neb8a8c2a3c82e15394e63087dc312def8fe66841 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783728207 -0700\tcommit: fix(msg-identity): W4 carrier-claim — one atomic cross-carrier take, audit stays truthful\n4b29512434c02998467d594dedd8965eb79212c2 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783728275 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w2-f034\n32b134ef314c84cc737124085c615cf8a2ab60d6 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783729426 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w4-delivery\n4b29512434c02998467d594dedd8965eb79212c2 32b134ef314c84cc737124085c615cf8a2ab60d6 Reavo End <decidel@gmail.com> 1783729571 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w2-f034\n32b134ef314c84cc737124085c615cf8a2ab60d6 53b1d59e1e0bcdd3c5e3aa501015e879fceba179 Reavo End <decidel@gmail.com> 1783729675 -0700\tcommit: fix(msg-identity): W2 rework — seed spend-vs-restore split, sid bearer provenance, squat guard on all three rotation seams\n53b1d59e1e0bcdd3c5e3aa501015e879fceba179 4b29512434c02998467d594dedd8965eb79212c2 Reavo End <decidel@gmail.com> 1783729702 -0700\tcheckout: moving from build/msg-identity-w2-f034 to build/msg-identity-w4-delivery\n4b29512434c02998467d594dedd8965eb79212c2 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783729929 -0700\tcommit: revert(msg-identity): W4 — drop the blanket live_agent inject exclusion (F-033 leg)\n21928cb66a05f5eca81f78b9351d1332615d93a9 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783730009 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w3-f032\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783730496 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n21928cb66a05f5eca81f78b9351d1332615d93a9 6b5a35f122c6ffc03b2d51455feb82359420a1b0 Reavo End <decidel@gmail.com> 1783730525 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w3-f032\n6b5a35f122c6ffc03b2d51455feb82359420a1b0 17dc68ea7d72e00d77bb9ff0622028dd5f9fa878 Reavo End <decidel@gmail.com> 1783730525 -0700\tcommit: fix(msg-identity): W3 rework — deferred pending always rides the commune suffix\n17dc68ea7d72e00d77bb9ff0622028dd5f9fa878 21928cb66a05f5eca81f78b9351d1332615d93a9 Reavo End <decidel@gmail.com> 1783730541 -0700\tcheckout: moving from build/msg-identity-w3-f032 to build/msg-identity-w4-delivery\n21928cb66a05f5eca81f78b9351d1332615d93a9 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783730602 -0700\tcommit: docs(msg-identity): re-scope REQ-LIVE-AGENT-NO-INJECT-DELIVERY — hosting-mode class split (doyle re-ruling)\na632e2b19802a82f72ad2523ea064e36aab423b1 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (start): checkout build/msg-identity-w4-delivery\na632e2b19802a82f72ad2523ea064e36aab423b1 42ecada13ad198393aad5b8b8764f16c3c5b04c8 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (pick): wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\n42ecada13ad198393aad5b8b8764f16c3c5b04c8 42ecada13ad198393aad5b8b8764f16c3c5b04c8 Reavo End <decidel@gmail.com> 1783730779 -0700\trebase (finish): returning to refs/heads/build/msg-identity-w5-f038\n42ecada13ad198393aad5b8b8764f16c3c5b04c8 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783732066 -0700\tcommit: feat(msg-identity): W5 F-038 — endpoint startup defaults (run --save + daemon-boot replay)\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 a632e2b19802a82f72ad2523ea064e36aab423b1 Reavo End <decidel@gmail.com> 1783734139 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w4-delivery\na632e2b19802a82f72ad2523ea064e36aab423b1 6236b39753ff9afebc1d7d6029ca02e9e51b3a94 Reavo End <decidel@gmail.com> 1783734338 -0700\tcommit: fix(msg-identity): W4 echo-scrub — ambient inject-echo knobs die at daemon startup (F-033 leg b)\n6236b39753ff9afebc1d7d6029ca02e9e51b3a94 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783734395 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w6-f039\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 6236b39753ff9afebc1d7d6029ca02e9e51b3a94 Reavo End <decidel@gmail.com> 1783734461 -0700\tcheckout: moving from build/msg-identity-w6-f039 to build/msg-identity-w4-delivery\n6236b39753ff9afebc1d7d6029ca02e9e51b3a94 916bd2af87969b3b64e083070ff8ef54cf2aa4da Reavo End <decidel@gmail.com> 1783734738 -0700\tcommit: docs(msg-identity): W4 F-033 residual-class flip — REQ-LIVE-AGENT-NO-INJECT-DELIVERY [impl,unit] (doyle ruling)\n916bd2af87969b3b64e083070ff8ef54cf2aa4da 2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 Reavo End <decidel@gmail.com> 1783734952 -0700\tcommit: test(msg-identity): W4 carrier-race rig hardening — DatabaseBusy is yield+retry, not a panic (doyle ruling)\n2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 c9ca08f081273a995adbe9c44d6e3f27d58a4a52 Reavo End <decidel@gmail.com> 1783734968 -0700\tcheckout: moving from build/msg-identity-w4-delivery to build/msg-identity-w5-f038\nc9ca08f081273a995adbe9c44d6e3f27d58a4a52 2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (start): checkout build/msg-identity-w4-delivery\n2fb1f96af867b3d52f4c65fb874f7638f8ea7d30 262c4663dbdbdf6358d2f626e038b0c4f9b70b2f Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (pick): wip(msg-identity): W5 F-038 autostart shape (a) — impl only (tests+doc+stage-flip follow)\n262c4663dbdbdf6358d2f626e038b0c4f9b70b2f 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (pick): feat(msg-identity): W5 F-038 — endpoint startup defaults (run --save + daemon-boot replay)\n315caf7df04773ae11161da189d2f10dc11a2e1b 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783734968 -0700\trebase (finish): returning to refs/heads/build/msg-identity-w5-f038\n315caf7df04773ae11161da189d2f10dc11a2e1b 315caf7df04773ae11161da189d2f10dc11a2e1b Reavo End <decidel@gmail.com> 1783735037 -0700\tcheckout: moving from build/msg-identity-w5-f038 to build/msg-identity-w6-f039\n315caf7df04773ae11161da189d2f10dc11a2e1b 4e0916cdf223756ce368fca934d3b7c7fd05688e Reavo End <decidel@gmail.com> 1783735239 -0700\tcommit: docs(msg-identity): W6 F-039 REQ moves — RCA-lock addendum + attribution/harness mints (doyle W6 LOCK)\n4e0916cdf223756ce368fca934d3b7c7fd05688e fc048bb41560b629c8cb1d158f8587f67f895e39 Reavo End <decidel@gmail.com> 1783736665 -0700\tcommit: feat(msg-identity): W6 F-039 legs a-d — poison token split + conn attribution + canary isolation\nfc048bb41560b629c8cb1d158f8587f67f895e39 8951793a58dc7db5cb05c38b322e604aaf080be2 Reavo End <decidel@gmail.com> 1783736859 -0700\tcommit: test(msg-identity): W6 rider — r4 gate accepts poison-beats-evict tally race (doyle ruling)\n8951793a58dc7db5cb05c38b322e604aaf080be2 fba81ec78484804b341e5ce726bf3dd9fbddf82f Reavo End <decidel@gmail.com> 1783737642 -0700\tcommit: test(msg-identity): W6 F-039 leg e — black-holed-controller lifecycle harness\nfba81ec78484804b341e5ce726bf3dd9fbddf82f 06a2d74d0740577f740e0d94f31dc78982dfbcca Reavo End <decidel@gmail.com> 1783739468 -0700\tcommit: test(msg-identity): W6 leg-e rework — survivor-identity snapshot closes the leaked-handler false-green (doyle R1+R2)\n06a2d74d0740577f740e0d94f31dc78982dfbcca 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783740437 -0700\tcheckout: moving from build/msg-identity-w6-f039 to main\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740438 -0700\tpull origin main: Fast-forward\na6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740541 -0700\tcheckout: moving from main to main\na6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740809 -0700\tcheckout: moving from main to release/v0.31.0\na6ff50193646ddf8c68deee300ce535565297840 97a0e4f7d14c8a260e600c455e3286579cb0f85e Reavo End <decidel@gmail.com> 1783741119 -0700\tcommit: release: v0.31.0 — messaging delivery-integrity + endpoint identity (counter 58)\n97a0e4f7d14c8a260e600c455e3286579cb0f85e a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783743756 -0700\tcheckout: moving from release/v0.31.0 to main\na6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\nc9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784085971 -0700\tcheckout: moving from main to build/forkening-w1-gh-transport\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 2552ee65f9dd04b5ee199968adfbe5ba062abe84 Reavo End <decidel@gmail.com> 1784089127 -0700\tcommit: build(forkening-w1): gh transport for self-update + spt install bootstrap verb — REQ-UPDATE-GH-TRANSPORT + REQ-INSTALL-BOOTSTRAP-VERB\n2552ee65f9dd04b5ee199968adfbe5ba062abe84 a841f5a44d0fe881f10725e6eb43ea5a80f50b08 Reavo End <decidel@gmail.com> 1784092104 -0700\tcommit: fix(forkening-w1): consent-notif test asserts CONSENT_CHANGELOG_URL const, not a hardcoded channel substring\na841f5a44d0fe881f10725e6eb43ea5a80f50b08 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784093957 -0700\tcheckout: moving from build/forkening-w1-gh-transport to main\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093961 -0700\tpull origin main: Fast-forward\n832b982069e4e473db79637d894e528c9018d3ef 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093965 -0700\tcheckout: moving from main to build/forkening-w2-docs-release\n832b982069e4e473db79637d894e528c9018d3ef 8981d816132884933df3e02d9c12d556d772f807 Reavo End <decidel@gmail.com> 1784099507 -0700\tcommit: build(forkening-w2): signed docs bundle + private release channel — REQ-DOCS-RELEASE-ASSET + REQ-RELEASE-CHANNEL-PRIVATE\n8981d816132884933df3e02d9c12d556d772f807 052f7ad9d5ff96ea2ed942fda9797ef9af258532 Reavo End <decidel@gmail.com> 1784102514 -0700\tcommit: fix(forkening-w2): allow too_many_arguments on build_signed_update_set\n052f7ad9d5ff96ea2ed942fda9797ef9af258532 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784110026 -0700\tcheckout: moving from build/forkening-w2-docs-release to main\n832b982069e4e473db79637d894e528c9018d3ef f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110030 -0700\tpull origin main: Fast-forward\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110032 -0700\tcheckout: moving from main to build/forkening-w3-docs-server\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 053e00867b0ce74c746114446be1eb0478cb5549 Reavo End <decidel@gmail.com> 1784111869 -0700\tcommit: build(forkening-w3): node-local docs server on loopback:5474 — REQ-DOCS-LOCAL-SERVER\n053e00867b0ce74c746114446be1eb0478cb5549 f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784114265 -0700\tcheckout: moving from build/forkening-w3-docs-server to main\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114266 -0700\tpull origin main: Fast-forward\ncff7c1f5b1f6c072e794a38127835df88f960ee4 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114361 -0700\tcheckout: moving from main to build/forkening-w4-update-ux\ncff7c1f5b1f6c072e794a38127835df88f960ee4 66e930ce3a3c356f5ad4d9391c5625eedc15a3ca Reavo End <decidel@gmail.com> 1784116363 -0700\tcommit: build(forkening-w4): update-UX cluster — one-command update + daemon refresh\n66e930ce3a3c356f5ad4d9391c5625eedc15a3ca 5199464b6d4fff29b34a0555a1573d5985148660 Reavo End <decidel@gmail.com> 1784118326 -0700\tcommit: ci(forkening-w4): FLAKE-LEDGER #15 — daemon-tree e2es to the HEAVY class\n5199464b6d4fff29b34a0555a1573d5985148660 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784119444 -0700\tcheckout: moving from build/forkening-w4-update-ux to main\ncff7c1f5b1f6c072e794a38127835df88f960ee4 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119445 -0700\tpull origin main: Fast-forward\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tcheckout: moving from main to release/v0.32.0\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n6f9df02d3ef85a8bb1077a74690880dacff1e404 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784122293 -0700\tcheckout: moving from release/v0.32.0 to main\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\nca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162126 -0700\tcheckout: moving from main to build/project-index-w1-identity-store\ne5f053fd130f7eed1e40e927c0bab87d88bb007a 13bf7a262ac6c5132be22042d8549f2ec437aa94 Reavo End <decidel@gmail.com> 1784164947 -0700\tcommit: build(project-index-w1): identity-only whoami + the materialized index store\n13bf7a262ac6c5132be22042d8549f2ec437aa94 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784166403 -0700\tcheckout: moving from build/project-index-w1-identity-store to main\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166404 -0700\tpull --ff-only origin main: Fast-forward\ne4573279c6389023f5fc4f61ad88e27a12202a6f e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166405 -0700\tcheckout: moving from main to main\ne4573279c6389023f5fc4f61ad88e27a12202a6f e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166662 -0700\tcheckout: moving from main to build/project-index-w2-daemon-writer\ne4573279c6389023f5fc4f61ad88e27a12202a6f 1a68900fd2ebd01a0a934c2484c819883e478008 Reavo End <decidel@gmail.com> 1784169159 -0700\tcommit: build(project-index-w2): daemon single-flight index writer + event-driven invalidation\n1a68900fd2ebd01a0a934c2484c819883e478008 e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784170611 -0700\tcheckout: moving from build/project-index-w2-daemon-writer to main\ne4573279c6389023f5fc4f61ad88e27a12202a6f e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170613 -0700\tpull --ff-only origin main: Fast-forward\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170622 -0700\tcheckout: moving from main to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170731 -0700\tcheckout: moving from main to build/project-index-w3-reader-cutover\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784178494 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784178588 -0700\tcheckout: moving from main to build/project-index-w3-reader-cutover\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 53bae678013031f01391c68e736f730f5bda148e Reavo End <decidel@gmail.com> 1784185206 -0700\tcommit: build(project-index-w3): reader cutover — list/picker/endpoint-info consume the materialized index\n53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700\tcommit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover\nafd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700\tcommit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag\nd88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700\tcheckout: moving from main to main\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\neb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\nb7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\nd0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 37f0b38862a876e38d2ff4793746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700\tcommit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section\n37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700\tcommit: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700\tcheckout: moving from fix/changelog-0330-heading to main\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\nd41ceaab197af6ebf1da842400dff3a6518084bb 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239433 -0700\tcherry-pick: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n0bb37f352d871f288d3c1ab5af6133020d57c674 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239506 -0700\tcheckout: moving from main to main\n0bb37f352d871f288d3c1ab5af6133020d57c674 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239542 -0700\tcheckout: moving from main to build/mesh-recovery-w1\n0bb37f352d871f288d3c1ab5af6133020d57c674 18489c5ae579d68f91ad665552100cc43a3c3774 Reavo End <decidel@gmail.com> 1784243262 -0700\tcommit: build(mesh-recovery-w1): peer-route resolution truth — the route chain, nondestructive retention, cache invariant, stage-split health\n18489c5ae579d68f91ad665552100cc43a3c3774 66d9dc645d73dac49fd0e7c8f10b7bd079d647a9 Reavo End <decidel@gmail.com> 1784243788 -0700\tcommit: fix(mesh-recovery-w1): gate round 1 — the registry-admit stamp survives daemon restarts\n66d9dc645d73dac49fd0e7c8f10b7bd079d647a9 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784246970 -0700\tcheckout: moving from build/mesh-recovery-w1 to main\n0bb37f352d871f288d3c1ab5af6133020d57c674 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784246971 -0700\tmerge origin/main: Fast-forward\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247027 -0700\tcheckout: moving from main to main\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247046 -0700\tcheckout: moving from main to release/v0.35.0\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 83f90e7e622a0693ae8e6f929c5129352f08b818 Reavo End <decidel@gmail.com> 1784247250 -0700\tcommit: release: v0.35.0 (counter 62) — MESH-RECOVERY W1\n83f90e7e622a0693ae8e6f929c5129352f08b818 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784248182 -0700\tcheckout: moving from release/v0.35.0 to main\n7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248636 -0700\tcommit: triage(redispatch-stall): wave JIT — ADR-0038 amendment (replay/poison stall composition) + KH 7.43 + 5 REQs registry-first\n7488c1ac5111e38da469478f44886da9c0f7267e 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248676 -0700\tcheckout: moving from main to main\n7488c1ac5111e38da469478f44886da9c0f7267e 7488c1ac5111e38da469478f44886da9c0f7267e Reavo End <decidel@gmail.com> 1784248695 -0700\tcheckout: moving from main to build/redispatch-stall-w1\n7488c1ac5111e38da469478f44886da9c0f7267e cb70090bf20963a89d814cf8f5d8e750d4413afa Reavo End <decidel@gmail.com> 1784254956 -0700\tcommit: build(redispatch-stall-w1): wedged-subscriber stall composition killed — ADR-0038 Amendment fixes 1-9 + T1-T7\ncb70090bf20963a89d814cf8f5d8e750d4413afa 73b29b858bbffb2c758382c80d74def0aabb328f Reavo End <decidel@gmail.com> 1784255745 -0700\tcommit: fix(redispatch-stall-w1): gate round 1 — the gap-resume re-subscribe keeps its generation\n73b29b858bbffb2c758382c80d74def0aabb328f 8d5dcab22a4fe8e5a283c7ce182c3a57cf2fb402 Reavo End <decidel@gmail.com> 1784257372 -0700\tcheckout: moving from build/redispatch-stall-w1 to main\n8d5dcab22a4fe8e5a283c7ce182c3a57cf2fb402 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784257414 -0700\tcommit: test(redispatch-stall-w1): rider — scrub inject-echo env in the wedge rig init\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784258507 -0700\tcheckout: moving from main to release/v0.36.0\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784258639 -0700\tcommit: release: v0.36.0 (counter 63) — REDISPATCH-STALL W1\nb677030328cf7684d2b89076e8b03858d3ae0661 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784259901 -0700\tcheckout: moving from release/v0.36.0 to fix/redispatch-stall-w1-replay-order\nb677030328cf7684d2b89076e8b03858d3ae0661 fc2579964647cfbf26d26bf045b20c162cb44b54 Reavo End <decidel@gmail.com> 1784261857 -0700\tcommit: fix(redispatch-stall-w1): replay-then-live order is structural — the seat writer drains the replay first\nfc2579964647cfbf26d26bf045b20c162cb44b54 f4727cdd3da014fcd8097cbba3c63203fb6b38b2 Reavo End <decidel@gmail.com> 1784262300 -0700\tcommit: fix(redispatch-stall-w1): gate round 1 — every seat install mints its own lease generation\nf4727cdd3da014fcd8097cbba3c63203fb6b38b2 0c7025338db716a5ed9cc4ceaa8d85dd04a38840 Reavo End <decidel@gmail.com> 1784263680 -0700\tcheckout: moving from fix/redispatch-stall-w1-replay-order to main\n0c7025338db716a5ed9cc4ceaa8d85dd04a38840 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263681 -0700\tpull --ff-only: Fast-forward\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263736 -0700\tcheckout: moving from main to main\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784270311 -0700\tcheckout: moving from main to docs/v0360-notes-plain-language\nfa3e474783a6e3ebbb178a478f03e4ebc8ce3928 d903b5de1a5728b8b5938714b52bf64017551fbc Reavo End <decidel@gmail.com> 1784270368 -0700\tcommit: docs(changelog): rewrite v0.36.0 notes in plain end-user language\n","startLine":1349,"lineNumbers":[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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\HEAD"}}},"isError":false,"timestamp":1784271556520}}
{"type":"message","id":"12d8d509","parentId":"a9167da8","timestamp":"2026-07-17T06:59:16.527Z","message":{"role":"toolResult","toolCallId":"call_roVxHQn7aMgx4dM1EPK84jaD|fc_089095e18118ee7d016a59d2c46734819091c0a2186c1323ee","toolName":"grep","content":[{"type":"text","text":"# docs/\n## REDISPATCH-STALL-TRIAGE.md\n 15|**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n*16|17–62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all\n*17|15,000–15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach\n 18|records/generation; per-stream, not broker-wide (unrelated conns progressed\n 19|mid-poison — broker-wide theory falsified).\n 20|\n...\n 29|| Stale-worker detach | serve/detach path (`detach_if` by ptr identity only) | no ownership/generation validation before attach/detach — a stale worker can clear a replacement controller (the UNFINISHED-stale steal shape, kin emphasys C2 raw-close no-FIN viewports) |\n*30|| Dispatcher hygiene | `dispatch.rs` per-stream `thread::spawn`, claims/sessions locks near I/O | unbounded worker fan-out on cold enumeration; lock-across-I/O kin KH 7.12 |\n 31|\n 32|## Fix set (P0, accepted as filed — hertz design, doyle-ruled)\n 33|\n...\n 93|**Missing-coverage census (fair, rides the wave):** D3 never poisons an\n*94|installed subscriber; `conn_blackhole` exercises conns, not StreamLog\n 95|subscribers; `daemon_refresh` doesn't cover cross-node attach continuity —\n 96|the matrix above closes these, don't re-count them as new scope.\n 97|\n...\n 129|\n*130|1. **Poison-window census:** the 15,000–15,154 ms `CONN_WRITE_POISONED`\n*131|   fingerprint disappears from live logs; stream-sub-attach per generation\n 132|   drops from ~4361 to O(active streams).\n 133|2. **Stall census:** no 17–62s DISPATCH tails across a daemon refresh + one\n 134|   week of live traffic.\n## KNOWN-HAZARDS.md\n 201|- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202|- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n 203|- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204|- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205|\n...\n 228|### 5.9 `Instant - Duration` underflow-panics on a freshly-booted host  `[REQ-HAZARD-INSTANT-UNDERFLOW]`\n*229|- **Failure:** `Instant::now() - Duration::from_secs(N)` panics `overflow when subtracting duration from instant` when the process's monotonic clock is younger than `N` — i.e. the host booted less than `N` ago. The peer pump primed its cadence legs with `Instant::now() - 86_400s` to mean \"everything due now\"; on a Windows runner with sub-24h uptime the pump thread panicked at startup, so the subnet never converged (CI `pump_and_dispatch_self_drive_the_subnet` failed, run 27082417706). It is *environmen...\n 230|- **Invariant:** NEVER compute an instant in the past by subtracting from `Instant::now()`. Represent \"never run / due now\" as `Option<Instant> = None` and gate on forward `now.duration_since(past)` only (`peerloop::due`). No backward instant arithmetic anywhere in scheduling.\n 231|- **Test seam caveat:** the convergence E2E only reproduces on a sub-offset-uptime host (it passed everywhere with >24h uptime). The deterministic guard is the `due(None, ..)`/`due(Some(now), ..)` unit on the extracted gate — it asserts first-tick-due with zero instant subtraction, independent of host uptime.\n 232|- **spt-core mapping:** `spt-daemon::peerloop::due` (the sole cadence gate behind `due_reg`/`due_notif`/`due_sync`/`due_upd`); cadence legs are `Option<Instant>` seeded `None`.\n...\n 274|- **Invariant:** concurrent atomic writers to the same target must never share a tmp name. Stage a **unique** tmp per write — `{name}.tmp.{pid}-{seq}`, `seq` from a process-local static `AtomicU64` (no clock, no rand → resume/replay-safe) — on BOTH the durable and non-durable paths (the collision is generic to `atomic_write`, not the fsync). Keep `rename_with_retry` + best-effort tmp cleanup on rename error. The new names still lack a `.json` extension, so exact-name / `*.json` loaders stay blind t...\n*275|- **spt-core mapping:** `spt_store::atomic::write_then_rename` (the shared core behind `atomic_write_bytes`/`_string` and the `_durable` siblings). Regression guard: a ≥4-thread `Barrier`-aligned hammer on one target (`concurrent_writers_never_collide_on_tmp`) — reds on the fixed-tmp code, greens on unique-tmp; a hammer that can't red is decorative. Cross-ref 5.13: the fsync widened the window, the fixed tmp name was the defect.\n 276|- **Source:** RCA 2026-07-01 (doyle gate rig) — write-trace side-channel + `cmd_bind` step probes pinned `PROBE cmd_bind establish ERR: Io(os error 2)`; explains the counter-39 gate-1 blanket failure, the dummy_harness ONLINE-race DIAGs, and the nondeterminism. Lesson: a widened timing window doesn't create a race, it reveals one.\n 277|\n 278|<!-- [doc->REQ-HAZARD-INFO-RMW-LOST-UPDATE] -->\n...\n 325|### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n*326|- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n 327|- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328|- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329|- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n...\n 358|- **Invariant:** the candidate-binary default is the canonical exe path **captured once at broker start** (before any `apply` can rename under the process), never a per-spawn `current_exe()` — giving Linux the path-at-start semantics Windows already had. AND promotion is **bytes-gated**: a trial promotes only if the candidate's stamped `brain.ready` `exe_hash` equals the staged artifact's hash for this platform; a mismatch is a failed trial → auto-rollback + loud notif (readiness alone is not proof t...\n*359|- **spt-core mapping:** v0.4.2 fix (`V042-PLAN.md`). Half 1 = `spawn_brain_supervisor` canonical-exe capture threaded into `spawn_brain_child`'s `None` default; Half 2 = the promotion bytes-gate in `supervise_brain`'s `Promoted` arm (`TrialEnv::ready_exe_hash` + `staged_artifact_hash`). The rollback path (`Some(.old-N)` selection) is unchanged. Sibling of 6.7 — the broker *process* is correct; the bytes it respawned the brain ONTO were not.\n 360|- **Origin:** ADR-0018 Q3 silently assumed `current_exe()` path-string semantics; surfaced by the v0.4.1 fleet-roll `exe_hash` bytes assert (agents `todlando` + `doyle` + `deployah`, 2026-06-11). ADR-0018 Q3 amended.\n 361|<!-- [doc->REQ-HAZARD-BRAIN-RESPAWN-PATH] -->\n 362|\n...\n 387|### 7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents  `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`\n*388|- **Failure:** echo-commune (`run_bounded_stdin`) and the live-Psyche turn driver (D7.5) are **bounded LLM calls that block their calling thread** until the child answers or the timeout fires. Today each agent drives its own pulse from its **own process** (`spt/src/api/{live,startup}.rs`), so blocking is isolated. When the daemon hosts **N per-agent loops** (ADR-0004 target; the `run_pulse_loop` fan-out is currently def+test only), a **single serial driver** that calls these invocations inline lets one a...\n*389|- **Invariant:** each agent's bounded LLM-bearing work (echo-commune summarizer, Psyche turn) runs on its **own thread / off the shared scheduler** — no single-threaded driver iterating all agents may call a blocking invocation inline. One agent's slow/timed-out call must not delay another agent's next tick beyond tolerance.\n 390|- **spt-core mapping:** the daemon's multi-agent pulse/psyche hosting (ADR-0004, \"all Psyche/pulse loops\" consolidated); `run_pulse_loop` fan-out; echo-commune + the D7.5 Psyche driver.\n 391|- **Source:** grill-with-docs 2026-06-03 (forward invariant — the multi-agent fan-out is not yet wired). Distinct from ADR-0002 SERIOUS #6 (crash blast-radius, not scheduling latency).\n 392|\n...\n 402|<!-- [doc->REQ-HAZARD-PUMP-IPC-DEADLINE] -->\n*403|- **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits the dead peer and never sends the reply, so the brain's `read_frame` blocks FOREVER and the pump freezes mid-round. The heartbeat (loop...\n*404|- **Invariant:** in PUMP mode the brain carrier is **SPLIT at construction** — a dedicated `pump-ipc-reader` thread does blocking `read_frame` on the `RecvHalf` and forwards each framed result down a channel; the main thread writes on the `SendHalf` and reads with `Receiver::recv_timeout`. Every IPC reply read is bounded by a per-call **total-wait** deadline (`PUMP_PEER_IO_TIMEOUT` = 30s, > any legitimate round-trip, < the 60s QUIC idle; re-armed on stream progress for the streaming pull legs so a heal...\n*405|- **spt-core mapping:** `Brain::cold_start_pump` (splits the carrier + arms the deadline) / `BrainConn::Split` (the `SendHalf` + reader-thread channel) / `call_deadline` / `read_event_until` / `read_frame_until` (the `recv_timeout` dispatch), `pump::run_peer_pump` (connects in pump mode) + `pump::peer_outcome` (the tier-split), the `request_sync`/`request_update` pull loops (deadline re-armed on progress). The broker-side half — the broker must never make a brain wait unbounded on a QUIC op (bound the ...\n 406|- **Source:** field diagnosis 2026-06-11 (the 2.2h hfenduleam wedge); doyle ruling A-now / B-deferred. The stall warning (M8 decision 23) was the band-aid; this is the fix. The B-half landed v0.8.3 (§7.8) after the 2026-06-16 recurrence.\n 407|\n 408|### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain  `[REQ-HAZARD-VIEWER-ISOLATION]`\n 409|<!-- [doc->REQ-HAZARD-VIEWER-ISOLATION] -->\n*410|- **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain — freezing the controller's stream and backing up the PTY child. A single watcher must n...\n*411|- **Invariant:** the drain writes the **controller** on the authoritative blocking bounded path (it alone advances `delivered_through`), but each **viewer** gets an **isolated bounded SPSC queue + a dedicated writer thread**; the drain `try_send`s under the log lock and **evicts** any viewer whose queue is `Full` (fell behind the live stream) or `Disconnected` (its writer died on a dead socket) — the drain thread **never touches a viewer socket**, so no viewer write can backpressure it. A **soft cap** ...\n*412|- **spt-core mapping:** `OutputLog::append` (controller blocking + `viewer_send_evicts` `try_send` fan-out), `OutputLog::add_viewer` (bounded `sync_channel(VIEWER_CHANNEL_DEPTH)` + `viewer_writer` thread), `MAX_VIEWERS` soft cap, `ViewerSink`. Unit: `viewer_overflow_or_disconnect_evicts_never_blocks`. Int: `wedged_viewer_does_not_stall_controller` (a non-reading viewer is evicted while the controller keeps receiving past a 200KB burst).\n 413|- **Source:** M12 W2.5 controller/viewer model (doyle ruling 2026-06-14, Q1).\n 414|\n 415|### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half)  `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`\n...\n 417|- **Failure:** the broker's brain-facing QUIC handlers (`dispatch_net_dial` / `dispatch_net_stream_open` / `dispatch_net_stream_send`) call into `NetHost::dial` / `open_stream` / `send_stream`, whose iroh awaits (`endpoint.connect` + `prove_membership`; `open_bi`; `write_all`/`finish`) had NO bound of their own. A dead/black-holed roster peer (its process gone, or a mixed-pair that accepts the conn but never answers the seed-proof) makes the broker await its QUIC path FOREVER, so the brain escapes only v...\n*418|- **Invariant:** every brain-waiting QUIC op is wrapped in a broker-side deadline (`NetHost::bounded_block_on` → `tokio::time::timeout`, `BROKER_QUIC_OP_TIMEOUT_MS` = 10s). On elapse the future is DROPPED (cancelling the in-flight connect/stream op, so nothing is half-registered) and a non-`TimedOut` `io::Error` is returned, which the broker REPLIES as an ordinary error frame. The bound (10s) sits comfortably above any legitimate LAN/relay round-trip and 20s below the brain's 30s `PUMP_PEER_IO_TIMEOUT`...\n 419|- **spt-core mapping:** `NetHost::bounded_block_on` (the timeout wrapper) wrapping `NetHost::dial` / `open_stream` (QUIC branch) / `send_stream`; `BROKER_QUIC_OP_TIMEOUT_MS` + `set_quic_op_timeout` (test override, off `NetConfig` — mirrors `set_roster_exchange`). Unit: `bounded_block_on_cuts_a_never_completing_op_with_an_ordinary_error` (a never-completing op → prompt non-`TimedOut` error; a ready op untouched). Int: `dial_to_a_black_holing_peer_fails_with_a_bounded_ordinary_error` (the broker REPLIE...\n 420|- **Source:** the 7.6 B-half — deferred 2026-06-11 (doyle ruling A-now / B-deferred, DEFERRED.md) and shipped in v0.8.3 after the 2026-06-16 hfenduleam recurrence.\n 421|\n...\n 437|- **Failure (hypothesized, v0.12.0):** a legitimately dead PTY child (crash/kill) plus an `rc` pump dropped without a clean detach (closed tab) was thought to make the broker's loopback forward `write_all` block forever on a full 64 KB duplex, park a worker in the 2-worker net runtime, saturate both, and stall every new attach / `endpoint run` (with `daemon stop` unable to join).\n*438|- **Invariant — DISPOSITION = PROVE-DON'T-CHANGE** (doyle GATE-PASS @e883f45, 2026-06-18): the post-L0 code ALREADY prevents the wedge; NO fail-fast / worker-count code was added. (1) `serve_attach` forwards fire-and-forget (`net_stream_send` `op_id=None`) and the broker-side `send_stream` is already deadline-bounded (`bounded_block_on`, `BROKER_QUIC_OP_TIMEOUT_MS` = 10s — hazard 7.8), not forever. (2) the loopback duplex is drained broker-INTERNALLY by the operator row's OWN read pump (`nethost.rs` ...\n 439|- **spt-core mapping:** int `crates/spt/tests/attach_wedge_e2e.rs` (REAL detached daemon + dummy-harness fixture): serve the victim (rc sees its tick), abruptly kill rc (dropped pump) + kill the PTY child → a NEW endpoint still comes online + is served (no wedge), the dead endpoint is offlined within a tick (`LIVENESS_RECONCILE_OFFLINE`), `daemon stop` bounded. Leans on 7.8 (the QUIC-op deadline) + 7.7 (viewer isolation).\n 440|- **Source:** v0.12.0 real-harness finding (operator) → v0.12.1 L2 @e883f45; doyle GATE-PASS 2026-06-18.\n 441|\n*442|### 7.12 Controller output must NOT be written inline on the drain thread — a backed-up controller wedges the session  `[REQ-HAZARD-INJECT-CONTROL-COEXIST]`\n*443|- **Failure (v0.12.x, operator dogfooding + doyle /diagnose 2026-06-19):** `OutputLog::append` fanned each live chunk to the CONTROLLER via a SYNCHRONOUS, blocking `write_frame` held INLINE on the session's single drain thread while `Mutex<OutputLog>` was locked (viewers already had a dedicated writer thread + bounded evicting channel). A backed-up controller socket — a slow operator, or the full 64 KB `rc` loopback duplex under heavy TUI redraw — parked the drain thread WITH THE LOG LOCK HELD: outpu...\n*444|- **Invariant:** controller delivery runs OFF the drain thread — a dedicated controller writer thread + bounded channel (the 7.7 viewer_writer pattern) does the blocking socket write; the drain hands each chunk off with a BOUNDED, OFF-LOCK send (deadline → detach + `clear_controller`, never park forever, never silently evict a LIVE operator's authoritative view). The controller is AUTHORITATIVE (unlike a viewer): its writer advances the `delivered_through` resume cursor (atomic, only-on-success, mono...\n 445|- **spt-core mapping:** impl `broker.rs` `OutputLog::append`/`controller_writer`/`become_controller`/`ControllerJob::deliver`/`clear_controller`; int `crates/spt-daemon/tests/inject_control_wedge.rs` (a fully-backed-up controller keeps `KIND_SESSIONS` answering = no wedge); unit the bounded-deliver + monotonic-cursor kernels. The input-side parks (write_input `write_all` on a full buffer; DSR-answer writer-mutex contention) are BENIGN on Windows ConPTY (absorbs a large inject), real only on Unix forkpty ...\n 446|- **Source:** v0.12.x operator wedge → v0.13.0 W1 (doyle GATE-PASS 2026-06-19).\n 447|\n...\n 455|### 7.14 The effect journal must NOT hold its lock across the PTY write (or fsync every keystroke) — interactive input stutters then wedges  `[REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]`\n*456|- **Failure (operator dogfooding + doyle /diagnose, MEASURED on the real Windows box, 2026-06-19, post-W1 escape):** `EffectJournal::apply_once` held its global `inner` mutex ACROSS `write_line(PENDING)` → `effect()` → `write_line(DONE)`, and `write_line` does `flush()+sync_all()` (a full fsync). Every operator keystroke is a `PtyWrite` effect (via `send_effect` → `dispatch_input` op_id branch), so each paid TWO fsyncs serialized under a GLOBAL lock with the blocking PTY write INSIDE the lock. Two ...\n*457|- **Invariant:** `apply_once` RELEASES the inner lock across `effect()` — reserve the key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the effect OFF the lock, then re-acquire to finalize (fsync `DONE` for durable, mark applied). Crash-idempotency comes from the per-key reservation + the applied-set, not from holding the lock across the effect (the brain-crash guarantee is unchanged: the broker survives the brain, so reserve→effect→finalize runs uninterrupted on the broker threa...\n 458|- **spt-core mapping:** impl `effect.rs` `apply_once` (reserve/release/finalize) + `EffectKind::is_durable`; unit (`effect.rs`) a barrier proving the lock is NOT held across `effect()` (a different key's `apply_once` completes while one effect blocks) + `PtyWrite` writes no journal line while a durable kind (`NetSend`) does (the no-fsync proxy); int `crates/spt-daemon/tests/inject_control_wedge.rs` (real broker+PTY, a stalled consumer + sustained blocking input effect: a concurrent `spt rc` attach stays ...\n 459|- **Source:** v0.13.0 W1b (operator dogfooding post-W1 escape; doyle /diagnose + measurement 2026-06-19).\n 460|\n\n# crates/spt-daemon/src/\n## conn.rs\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...\n 55|//!\n*56|//! No new output queue (the bounded per-sink queues + isolated writer threads\n 57|//! already exist — the block this bounds is BELOW them), and no `PIPE_NOWAIT`\n 58|//! (recorded mid-frame corruption risk). Unix keeps its existing semantics for\n 59|//! healthy connections; only the poison/retire path is new.\n...\n 64|use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock};\n*65|use std::thread::{self, JoinHandle};\n 66|use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};\n 67|\n 68|use interprocess::local_socket::SendHalf;\n...\n 74|/// [`BrokerConn::new`]. Before this the only conn identity was `Arc::ptr_eq`,\n*75|/// which does not survive a log line: per-line CONN_WRITE_POISONED churn was\n 76|/// UNDECIDABLE between fresh-carrier churn and stderr interleave (the F-039\n 77|/// RCA's terminal fork). Every lifecycle record carries `conn=<id>`.\n 78|// [impl->REQ-CONN-POISON-ATTRIBUTION]\n*79|static CONN_ID_SEQ: AtomicU64 = AtomicU64::new(0);\n 80|\n 81|/// The shared monotonic anchor for `mono_ms` stamps. Set explicitly at daemon\n 82|/// startup ([`init_log_anchor`], both broker and brain entries) so every\n...\n 138|\n*139|/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\n 140|/// the watchdog must not keep the conn alive).\n 141|struct Inner {\n*142|    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143|    id: u64,\n 144|    /// Accreted attribution facts (subscriber role, endpoint/session where\n 145|    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\n...\n 229|    /// Render the once-per-conn retirement record — the F-039 token split\n*230|    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231|    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232|    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233|    /// already-dead counterpart, the offline-peer dial churn) emits the\n*234|    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n 235|    /// SAME attribution fields — quieter class, never thinner (the retired\n 236|    /// class is what discriminates fresh-carrier churn from interleave\n 237|    /// artifact in the field). Behavior is identical either way: poison,\n...\n 242|            format!(\n*243|                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244|                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245|                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246|                self.attribution(),\n...\n 254|            format!(\n*255|                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n 256|                 broker conn poisoned + retired, never reused (same retire behavior \\\n 257|                 as POISONED — only the failure class differs) \\\n 258|                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n...\n 272|        eprintln!(\n*273|            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n 274|            self.attribution()\n 275|        );\n 276|    }\n...\n 287|            // First poison of this conn: loud, once. The token is class-split\n*288|            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n*289|            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290|            eprintln!(\n 291|                \"{}\",\n 292|                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\n...\n 428|        let inner = Arc::new(Inner {\n*429|            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\n 430|            label: Mutex::new(String::new()),\n 431|            first_write_logged: AtomicBool::new(false),\n 432|            gate: Mutex::new(Gate { half: Some(half) }),\n...\n 447|        let dog_inner = Arc::clone(&inner);\n*448|        let dog = thread::Builder::new()\n 449|            .name(\"conn-watchdog\".into())\n 450|            .spawn(move || dog_inner.watchdog())\n 451|            .ok();\n...\n 707|    // [unit->REQ-CONN-POISON-DIAL-SCOPE] the F-039 leg-(a) token split: the loud\n*708|    // CONN_WRITE_POISONED token is RESERVED for the deadline (timed_out) class;\n*709|    // an organic write failure renders the distinct CONN_WRITE_RETIRED token\n 710|    // carrying the error kind — and (doyle amendment 1) the SAME attribution\n 711|    // fields as POISONED: conn id, accreted role/endpoint/session facts, wall +\n 712|    // monotonic stamps. Quieter class, never thinner.\n...\n 721|        assert!(\n*722|            poisoned.starts_with(\"CONN_WRITE_POISONED:\"),\n 723|            \"deadline class keeps the loud wedge-observable token: {poisoned}\"\n 724|        );\n 725|        let retired = conn\n...\n 728|        assert!(\n*729|            retired.starts_with(\"CONN_WRITE_RETIRED:\"),\n 730|            \"organic class emits the DISTINCT retired token: {retired}\"\n 731|        );\n 732|        assert!(\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"docs/REDISPATCH-STALL-TRIAGE.md, docs/KNOWN-HAZARDS.md, crates/spt-daemon/src/conn.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":46,"fileCount":3,"files":["docs/REDISPATCH-STALL-TRIAGE.md","docs/KNOWN-HAZARDS.md","crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"docs/REDISPATCH-STALL-TRIAGE.md","count":6},{"path":"docs/KNOWN-HAZARDS.md","count":20},{"path":"crates/spt-daemon/src/conn.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# docs/\n## REDISPATCH-STALL-TRIAGE.md\n  15│**Field incident:** live v0.34 boxes, recurrent 20–30s freezes, DISPATCH tails\n *16│17–62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all\n *17│15,000–15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach\n  18│records/generation; per-stream, not broker-wide (unrelated conns progressed\n  19│mid-poison — broker-wide theory falsified).\n  20│\n    │...\n  29│| Stale-worker detach | serve/detach path (`detach_if` by ptr identity only) | no ownership/generation validation before attach/detach — a stale worker can clear a replacement controller (the UNFINISHED-stale steal shape, kin emphasys C2 raw-close no-FIN viewports) |\n *30│| Dispatcher hygiene | `dispatch.rs` per-stream `thread::spawn`, claims/sessions locks near I/O | unbounded worker fan-out on cold enumeration; lock-across-I/O kin KH 7.12 |\n  31│\n  32│## Fix set (P0, accepted as filed — hertz design, doyle-ruled)\n  33│\n    │...\n  93│**Missing-coverage census (fair, rides the wave):** D3 never poisons an\n *94│installed subscriber; `conn_blackhole` exercises conns, not StreamLog\n  95│subscribers; `daemon_refresh` doesn't cover cross-node attach continuity —\n  96│the matrix above closes these, don't re-count them as new scope.\n  97│\n    │...\n 129│\n*130│1. **Poison-window census:** the 15,000–15,154 ms `CONN_WRITE_POISONED`\n*131│   fingerprint disappears from live logs; stream-sub-attach per generation\n 132│   drops from ~4361 to O(active streams).\n 133│2. **Stall census:** no 17–62s DISPATCH tails across a daemon refresh + one\n 134│   week of live traffic.\n## KNOWN-HAZARDS.md\n 201│- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202│- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n 203│- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204│- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205│\n    │...\n 228│### 5.9 `Instant - Duration` underflow-panics on a freshly-booted host  `[REQ-HAZARD-INSTANT-UNDERFLOW]`\n*229│- **Failure:** `Instant::now() - Duration::from_secs(N)` panics `overflow when subtracting duration from instant` when the process's monotonic clock is younger than `N` — i.e. the host booted less than `N` ago. The peer pump primed its cadence legs with `Instant::now() - 86_400s` to mean \"everything due now\"; on a Windows runner with sub-24h uptime the pump thread panicked at startup, so the subnet never converged (CI `pump_and_dispatch_self_drive_the_subnet` failed, run 27082417706). It is *environmen...\n 230│- **Invariant:** NEVER compute an instant in the past by subtracting from `Instant::now()`. Represent \"never run / due now\" as `Option<Instant> = None` and gate on forward `now.duration_since(past)` only (`peerloop::due`). No backward instant arithmetic anywhere in scheduling.\n 231│- **Test seam caveat:** the convergence E2E only reproduces on a sub-offset-uptime host (it passed everywhere with >24h uptime). The deterministic guard is the `due(None, ..)`/`due(Some(now), ..)` unit on the extracted gate — it asserts first-tick-due with zero instant subtraction, independent of host uptime.\n 232│- **spt-core mapping:** `spt-daemon::peerloop::due` (the sole cadence gate behind `due_reg`/`due_notif`/`due_sync`/`due_upd`); cadence legs are `Option<Instant>` seeded `None`.\n    │...\n 274│- **Invariant:** concurrent atomic writers to the same target must never share a tmp name. Stage a **unique** tmp per write — `{name}.tmp.{pid}-{seq}`, `seq` from a process-local static `AtomicU64` (no clock, no rand → resume/replay-safe) — on BOTH the durable and non-durable paths (the collision is generic to `atomic_write`, not the fsync). Keep `rename_with_retry` + best-effort tmp cleanup on rename error. The new names still lack a `.json` extension, so exact-name / `*.json` loaders stay blind t...\n*275│- **spt-core mapping:** `spt_store::atomic::write_then_rename` (the shared core behind `atomic_write_bytes`/`_string` and the `_durable` siblings). Regression guard: a ≥4-thread `Barrier`-aligned hammer on one target (`concurrent_writers_never_collide_on_tmp`) — reds on the fixed-tmp code, greens on unique-tmp; a hammer that can't red is decorative. Cross-ref 5.13: the fsync widened the window, the fixed tmp name was the defect.\n 276│- **Source:** RCA 2026-07-01 (doyle gate rig) — write-trace side-channel + `cmd_bind` step probes pinned `PROBE cmd_bind establish ERR: Io(os error 2)`; explains the counter-39 gate-1 blanket failure, the dummy_harness ONLINE-race DIAGs, and the nondeterminism. Lesson: a widened timing window doesn't create a race, it reveals one.\n 277│\n 278│<!-- [doc->REQ-HAZARD-INFO-RMW-LOST-UPDATE] -->\n    │...\n 325│### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n*326│- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n 327│- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328│- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329│- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n    │...\n 358│- **Invariant:** the candidate-binary default is the canonical exe path **captured once at broker start** (before any `apply` can rename under the process), never a per-spawn `current_exe()` — giving Linux the path-at-start semantics Windows already had. AND promotion is **bytes-gated**: a trial promotes only if the candidate's stamped `brain.ready` `exe_hash` equals the staged artifact's hash for this platform; a mismatch is a failed trial → auto-rollback + loud notif (readiness alone is not proof t...\n*359│- **spt-core mapping:** v0.4.2 fix (`V042-PLAN.md`). Half 1 = `spawn_brain_supervisor` canonical-exe capture threaded into `spawn_brain_child`'s `None` default; Half 2 = the promotion bytes-gate in `supervise_brain`'s `Promoted` arm (`TrialEnv::ready_exe_hash` + `staged_artifact_hash`). The rollback path (`Some(.old-N)` selection) is unchanged. Sibling of 6.7 — the broker *process* is correct; the bytes it respawned the brain ONTO were not.\n 360│- **Origin:** ADR-0018 Q3 silently assumed `current_exe()` path-string semantics; surfaced by the v0.4.1 fleet-roll `exe_hash` bytes assert (agents `todlando` + `doyle` + `deployah`, 2026-06-11). ADR-0018 Q3 amended.\n 361│<!-- [doc->REQ-HAZARD-BRAIN-RESPAWN-PATH] -->\n 362│\n    │...\n 387│### 7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents  `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`\n*388│- **Failure:** echo-commune (`run_bounded_stdin`) and the live-Psyche turn driver (D7.5) are **bounded LLM calls that block their calling thread** until the child answers or the timeout fires. Today each agent drives its own pulse from its **own process** (`spt/src/api/{live,startup}.rs`), so blocking is isolated. When the daemon hosts **N per-agent loops** (ADR-0004 target; the `run_pulse_loop` fan-out is currently def+test only), a **single serial driver** that calls these invocations inline lets one a...\n*389│- **Invariant:** each agent's bounded LLM-bearing work (echo-commune summarizer, Psyche turn) runs on its **own thread / off the shared scheduler** — no single-threaded driver iterating all agents may call a blocking invocation inline. One agent's slow/timed-out call must not delay another agent's next tick beyond tolerance.\n 390│- **spt-core mapping:** the daemon's multi-agent pulse/psyche hosting (ADR-0004, \"all Psyche/pulse loops\" consolidated); `run_pulse_loop` fan-out; echo-commune + the D7.5 Psyche driver.\n 391│- **Source:** grill-with-docs 2026-06-03 (forward invariant — the multi-agent fan-out is not yet wired). Distinct from ADR-0002 SERIOUS #6 (crash blast-radius, not scheduling latency).\n 392│\n    │...\n 402│<!-- [doc->REQ-HAZARD-PUMP-IPC-DEADLINE] -->\n*403│- **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits the dead peer and never sends the reply, so the brain's `read_frame` blocks FOREVER and the pump freezes mid-round. The heartbeat (loop...\n*404│- **Invariant:** in PUMP mode the brain carrier is **SPLIT at construction** — a dedicated `pump-ipc-reader` thread does blocking `read_frame` on the `RecvHalf` and forwards each framed result down a channel; the main thread writes on the `SendHalf` and reads with `Receiver::recv_timeout`. Every IPC reply read is bounded by a per-call **total-wait** deadline (`PUMP_PEER_IO_TIMEOUT` = 30s, > any legitimate round-trip, < the 60s QUIC idle; re-armed on stream progress for the streaming pull legs so a heal...\n*405│- **spt-core mapping:** `Brain::cold_start_pump` (splits the carrier + arms the deadline) / `BrainConn::Split` (the `SendHalf` + reader-thread channel) / `call_deadline` / `read_event_until` / `read_frame_until` (the `recv_timeout` dispatch), `pump::run_peer_pump` (connects in pump mode) + `pump::peer_outcome` (the tier-split), the `request_sync`/`request_update` pull loops (deadline re-armed on progress). The broker-side half — the broker must never make a brain wait unbounded on a QUIC op (bound the ...\n 406│- **Source:** field diagnosis 2026-06-11 (the 2.2h hfenduleam wedge); doyle ruling A-now / B-deferred. The stall warning (M8 decision 23) was the band-aid; this is the fix. The B-half landed v0.8.3 (§7.8) after the 2026-06-16 recurrence.\n 407│\n 408│### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain  `[REQ-HAZARD-VIEWER-ISOLATION]`\n 409│<!-- [doc->REQ-HAZARD-VIEWER-ISOLATION] -->\n*410│- **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain — freezing the controller's stream and backing up the PTY child. A single watcher must n...\n*411│- **Invariant:** the drain writes the **controller** on the authoritative blocking bounded path (it alone advances `delivered_through`), but each **viewer** gets an **isolated bounded SPSC queue + a dedicated writer thread**; the drain `try_send`s under the log lock and **evicts** any viewer whose queue is `Full` (fell behind the live stream) or `Disconnected` (its writer died on a dead socket) — the drain thread **never touches a viewer socket**, so no viewer write can backpressure it. A **soft cap** ...\n*412│- **spt-core mapping:** `OutputLog::append` (controller blocking + `viewer_send_evicts` `try_send` fan-out), `OutputLog::add_viewer` (bounded `sync_channel(VIEWER_CHANNEL_DEPTH)` + `viewer_writer` thread), `MAX_VIEWERS` soft cap, `ViewerSink`. Unit: `viewer_overflow_or_disconnect_evicts_never_blocks`. Int: `wedged_viewer_does_not_stall_controller` (a non-reading viewer is evicted while the controller keeps receiving past a 200KB burst).\n 413│- **Source:** M12 W2.5 controller/viewer model (doyle ruling 2026-06-14, Q1).\n 414│\n 415│### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half)  `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`\n    │...\n 417│- **Failure:** the broker's brain-facing QUIC handlers (`dispatch_net_dial` / `dispatch_net_stream_open` / `dispatch_net_stream_send`) call into `NetHost::dial` / `open_stream` / `send_stream`, whose iroh awaits (`endpoint.connect` + `prove_membership`; `open_bi`; `write_all`/`finish`) had NO bound of their own. A dead/black-holed roster peer (its process gone, or a mixed-pair that accepts the conn but never answers the seed-proof) makes the broker await its QUIC path FOREVER, so the brain escapes only v...\n*418│- **Invariant:** every brain-waiting QUIC op is wrapped in a broker-side deadline (`NetHost::bounded_block_on` → `tokio::time::timeout`, `BROKER_QUIC_OP_TIMEOUT_MS` = 10s). On elapse the future is DROPPED (cancelling the in-flight connect/stream op, so nothing is half-registered) and a non-`TimedOut` `io::Error` is returned, which the broker REPLIES as an ordinary error frame. The bound (10s) sits comfortably above any legitimate LAN/relay round-trip and 20s below the brain's 30s `PUMP_PEER_IO_TIMEOUT`...\n 419│- **spt-core mapping:** `NetHost::bounded_block_on` (the timeout wrapper) wrapping `NetHost::dial` / `open_stream` (QUIC branch) / `send_stream`; `BROKER_QUIC_OP_TIMEOUT_MS` + `set_quic_op_timeout` (test override, off `NetConfig` — mirrors `set_roster_exchange`). Unit: `bounded_block_on_cuts_a_never_completing_op_with_an_ordinary_error` (a never-completing op → prompt non-`TimedOut` error; a ready op untouched). Int: `dial_to_a_black_holing_peer_fails_with_a_bounded_ordinary_error` (the broker REPLIE...\n 420│- **Source:** the 7.6 B-half — deferred 2026-06-11 (doyle ruling A-now / B-deferred, DEFERRED.md) and shipped in v0.8.3 after the 2026-06-16 hfenduleam recurrence.\n 421│\n    │...\n 437│- **Failure (hypothesized, v0.12.0):** a legitimately dead PTY child (crash/kill) plus an `rc` pump dropped without a clean detach (closed tab) was thought to make the broker's loopback forward `write_all` block forever on a full 64 KB duplex, park a worker in the 2-worker net runtime, saturate both, and stall every new attach / `endpoint run` (with `daemon stop` unable to join).\n*438│- **Invariant — DISPOSITION = PROVE-DON'T-CHANGE** (doyle GATE-PASS @e883f45, 2026-06-18): the post-L0 code ALREADY prevents the wedge; NO fail-fast / worker-count code was added. (1) `serve_attach` forwards fire-and-forget (`net_stream_send` `op_id=None`) and the broker-side `send_stream` is already deadline-bounded (`bounded_block_on`, `BROKER_QUIC_OP_TIMEOUT_MS` = 10s — hazard 7.8), not forever. (2) the loopback duplex is drained broker-INTERNALLY by the operator row's OWN read pump (`nethost.rs` ...\n 439│- **spt-core mapping:** int `crates/spt/tests/attach_wedge_e2e.rs` (REAL detached daemon + dummy-harness fixture): serve the victim (rc sees its tick), abruptly kill rc (dropped pump) + kill the PTY child → a NEW endpoint still comes online + is served (no wedge), the dead endpoint is offlined within a tick (`LIVENESS_RECONCILE_OFFLINE`), `daemon stop` bounded. Leans on 7.8 (the QUIC-op deadline) + 7.7 (viewer isolation).\n 440│- **Source:** v0.12.0 real-harness finding (operator) → v0.12.1 L2 @e883f45; doyle GATE-PASS 2026-06-18.\n 441│\n*442│### 7.12 Controller output must NOT be written inline on the drain thread — a backed-up controller wedges the session  `[REQ-HAZARD-INJECT-CONTROL-COEXIST]`\n*443│- **Failure (v0.12.x, operator dogfooding + doyle /diagnose 2026-06-19):** `OutputLog::append` fanned each live chunk to the CONTROLLER via a SYNCHRONOUS, blocking `write_frame` held INLINE on the session's single drain thread while `Mutex<OutputLog>` was locked (viewers already had a dedicated writer thread + bounded evicting channel). A backed-up controller socket — a slow operator, or the full 64 KB `rc` loopback duplex under heavy TUI redraw — parked the drain thread WITH THE LOG LOCK HELD: outpu...\n*444│- **Invariant:** controller delivery runs OFF the drain thread — a dedicated controller writer thread + bounded channel (the 7.7 viewer_writer pattern) does the blocking socket write; the drain hands each chunk off with a BOUNDED, OFF-LOCK send (deadline → detach + `clear_controller`, never park forever, never silently evict a LIVE operator's authoritative view). The controller is AUTHORITATIVE (unlike a viewer): its writer advances the `delivered_through` resume cursor (atomic, only-on-success, mono...\n 445│- **spt-core mapping:** impl `broker.rs` `OutputLog::append`/`controller_writer`/`become_controller`/`ControllerJob::deliver`/`clear_controller`; int `crates/spt-daemon/tests/inject_control_wedge.rs` (a fully-backed-up controller keeps `KIND_SESSIONS` answering = no wedge); unit the bounded-deliver + monotonic-cursor kernels. The input-side parks (write_input `write_all` on a full buffer; DSR-answer writer-mutex contention) are BENIGN on Windows ConPTY (absorbs a large inject), real only on Unix forkpty ...\n 446│- **Source:** v0.12.x operator wedge → v0.13.0 W1 (doyle GATE-PASS 2026-06-19).\n 447│\n    │...\n 455│### 7.14 The effect journal must NOT hold its lock across the PTY write (or fsync every keystroke) — interactive input stutters then wedges  `[REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]`\n*456│- **Failure (operator dogfooding + doyle /diagnose, MEASURED on the real Windows box, 2026-06-19, post-W1 escape):** `EffectJournal::apply_once` held its global `inner` mutex ACROSS `write_line(PENDING)` → `effect()` → `write_line(DONE)`, and `write_line` does `flush()+sync_all()` (a full fsync). Every operator keystroke is a `PtyWrite` effect (via `send_effect` → `dispatch_input` op_id branch), so each paid TWO fsyncs serialized under a GLOBAL lock with the blocking PTY write INSIDE the lock. Two ...\n*457│- **Invariant:** `apply_once` RELEASES the inner lock across `effect()` — reserve the key (fsync `PENDING` for DURABLE kinds) under the lock, RELEASE, run the effect OFF the lock, then re-acquire to finalize (fsync `DONE` for durable, mark applied). Crash-idempotency comes from the per-key reservation + the applied-set, not from holding the lock across the effect (the brain-crash guarantee is unchanged: the broker survives the brain, so reserve→effect→finalize runs uninterrupted on the broker threa...\n 458│- **spt-core mapping:** impl `effect.rs` `apply_once` (reserve/release/finalize) + `EffectKind::is_durable`; unit (`effect.rs`) a barrier proving the lock is NOT held across `effect()` (a different key's `apply_once` completes while one effect blocks) + `PtyWrite` writes no journal line while a durable kind (`NetSend`) does (the no-fsync proxy); int `crates/spt-daemon/tests/inject_control_wedge.rs` (real broker+PTY, a stalled consumer + sustained blocking input effect: a concurrent `spt rc` attach stays ...\n 459│- **Source:** v0.13.0 W1b (operator dogfooding post-W1 escape; doyle /diagnose + measurement 2026-06-19).\n 460│\n\n# crates/spt-daemon/src/\n## conn.rs\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    │...\n  55│//!\n *56│//! No new output queue (the bounded per-sink queues + isolated writer threads\n  57│//! already exist — the block this bounds is BELOW them), and no `PIPE_NOWAIT`\n  58│//! (recorded mid-frame corruption risk). Unix keeps its existing semantics for\n  59│//! healthy connections; only the poison/retire path is new.\n    │...\n  64│use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock};\n *65│use std::thread::{self, JoinHandle};\n  66│use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};\n  67│\n  68│use interprocess::local_socket::SendHalf;\n    │...\n  74│/// [`BrokerConn::new`]. Before this the only conn identity was `Arc::ptr_eq`,\n *75│/// which does not survive a log line: per-line CONN_WRITE_POISONED churn was\n  76│/// UNDECIDABLE between fresh-carrier churn and stderr interleave (the F-039\n  77│/// RCA's terminal fork). Every lifecycle record carries `conn=<id>`.\n  78│// [impl->REQ-CONN-POISON-ATTRIBUTION]\n *79│static CONN_ID_SEQ: AtomicU64 = AtomicU64::new(0);\n  80│\n  81│/// The shared monotonic anchor for `mono_ms` stamps. Set explicitly at daemon\n  82│/// startup ([`init_log_anchor`], both broker and brain entries) so every\n    │...\n 138│\n*139│/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\n 140│/// the watchdog must not keep the conn alive).\n 141│struct Inner {\n*142│    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143│    id: u64,\n 144│    /// Accreted attribution facts (subscriber role, endpoint/session where\n 145│    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\n    │...\n 229│    /// Render the once-per-conn retirement record — the F-039 token split\n*230│    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231│    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232│    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233│    /// already-dead counterpart, the offline-peer dial churn) emits the\n*234│    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n 235│    /// SAME attribution fields — quieter class, never thinner (the retired\n 236│    /// class is what discriminates fresh-carrier churn from interleave\n 237│    /// artifact in the field). Behavior is identical either way: poison,\n    │...\n 242│            format!(\n*243│                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244│                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245│                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246│                self.attribution(),\n    │...\n 254│            format!(\n*255│                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n 256│                 broker conn poisoned + retired, never reused (same retire behavior \\\n 257│                 as POISONED — only the failure class differs) \\\n 258│                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n    │...\n 272│        eprintln!(\n*273│            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n 274│            self.attribution()\n 275│        );\n 276│    }\n    │...\n 287│            // First poison of this conn: loud, once. The token is class-split\n*288│            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n*289│            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290│            eprintln!(\n 291│                \"{}\",\n 292│                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\n    │...\n 428│        let inner = Arc::new(Inner {\n*429│            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\n 430│            label: Mutex::new(String::new()),\n 431│            first_write_logged: AtomicBool::new(false),\n 432│            gate: Mutex::new(Gate { half: Some(half) }),\n    │...\n 447│        let dog_inner = Arc::clone(&inner);\n*448│        let dog = thread::Builder::new()\n 449│            .name(\"conn-watchdog\".into())\n 450│            .spawn(move || dog_inner.watchdog())\n 451│            .ok();\n    │...\n 707│    // [unit->REQ-CONN-POISON-DIAL-SCOPE] the F-039 leg-(a) token split: the loud\n*708│    // CONN_WRITE_POISONED token is RESERVED for the deadline (timed_out) class;\n*709│    // an organic write failure renders the distinct CONN_WRITE_RETIRED token\n 710│    // carrying the error kind — and (doyle amendment 1) the SAME attribution\n 711│    // fields as POISONED: conn id, accreted role/endpoint/session facts, wall +\n 712│    // monotonic stamps. Quieter class, never thinner.\n    │...\n 721│        assert!(\n*722│            poisoned.starts_with(\"CONN_WRITE_POISONED:\"),\n 723│            \"deadline class keeps the loud wedge-observable token: {poisoned}\"\n 724│        );\n 725│        let retired = conn\n    │...\n 728│        assert!(\n*729│            retired.starts_with(\"CONN_WRITE_RETIRED:\"),\n 730│            \"organic class emits the DISTINCT retired token: {retired}\"\n 731│        );\n 732│        assert!(","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784271556527}}
{"type":"message","id":"a480a185","parentId":"12d8d509","timestamp":"2026-07-17T06:59:27.000Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Inspecting Drop implementation for watchdog thread cleanup**"},{"type":"toolCall","id":"call_tzjuEdR2tMd3CQQeqLbxqAzj|fc_089095e18118ee7d016a59d2ceee34819097498d6e473fcc22","name":"grep","arguments":{"i":"Tracing watchdog teardown","pattern":"impl Drop for BrokerConn|fn watchdog|stop|shutdown|dog|JoinHandle|Condvar|wait_timeout|while","path":"crates/spt-daemon/src/conn.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_yBDl3eCun8QpzM3VumefUs7z|fc_089095e18118ee7d016a59d2ceee4c819090ce3e4e4c30f54c","name":"read","arguments":{"i":"Reading broker connection watchdog","path":"crates/spt-daemon/src/conn.rs","selector":"130-480"}},{"type":"toolCall","id":"call_SExgg9kvA7xaBtPBGw3bj5mT|fc_089095e18118ee7d016a59d2ceee58819080cfdfd3946f50b6","name":"read","arguments":{"i":"Reading broker handler cleanup","path":"crates/spt-daemon/src/broker.rs","selector":"2560-2650,2780-2825,2910-2935,3015-3220"}},{"type":"toolCall","id":"call_BrpxetdBFDnUizGLcpcv63mx|fc_089095e18118ee7d016a59d2ceee6081909eff87c1cada02e7","name":"read","arguments":{"i":"Reading retirement implementation","path":"crates/spt-daemon/src/nethost.rs","selector":"910-980,1760-1860,1935-1990"}},{"type":"toolCall","id":"call_3WVGBWm7pcP9WbqOe7wR76Zj|fc_089095e18118ee7d016a59d2ceee94819089fb1a636b7385e5","name":"read","arguments":{"i":"Reading retire dispatch","path":"crates/spt-daemon/src/broker.rs","selector":"4360-4410"}},{"type":"toolCall","id":"call_nwJJqsISTr9220pJSyBK3wT3|fc_089095e18118ee7d016a59d2ceee988190bba4aa42200d0b5c","name":"read","arguments":{"i":"Reading brain connection construction","path":"crates/spt-daemon/src/brain.rs","selector":"210-370,1430-1505,1725-1760"}},{"type":"toolCall","id":"call_fc1tUUytdK6KRSXWRn2fyEKD|fc_089095e18118ee7d016a59d2ceeea08190ab480fe661145c4a","name":"read","arguments":{"i":"Reading attach two-connection path","path":"crates/spt-daemon/src/attach.rs","selector":"270-430,570-630"}},{"type":"toolCall","id":"call_WTWmUh0B8LnRXncf7w5GUju4|fc_089095e18118ee7d016a59d2ceeea48190abd9aab0fdfb82bf","name":"read","arguments":{"i":"Reading pool unit coverage","path":"crates/spt-daemon/src/dispatch.rs","selector":"1270-1530"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":42076,"output":434,"cacheRead":98816,"cacheWrite":0,"totalTokens":141326,"reasoningTokens":40,"cost":{"input":0.21038,"output":0.01302,"cacheRead":0.049408,"cacheWrite":0,"total":0.272808}},"stopReason":"toolUse","timestamp":1784271556574,"responseId":"resp_089095e18118ee7d016a59d2c4f12c81909cf0cc12257122bf","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2c70fc08190932559f70206b2c1","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdLOCJQIHXtxf6GBiwriOkqTyQ3PTHrU4rqQEPlN0r_gTM3RlIocgZ3XToJLPfGRrf9_u6C682J2ZZvigEuHbru-L8IWqhrDj4lkSkMhoAQEAn6lEVacpwGltHVD0BMm745dreRu04SZVzQNd_d_h5mmyMreZKezIs7iRZr-RJgZTAE_QwIvZKhQ7hc3jndn7qHxXq-Fl6mNJDBPJz0g0B2zBgMSV2m-ZEAVMynRz2bvCY87ZBFzKCEt9xDDbWDpkRx1n4L1dY3C4pKluR6i0kNdJPrtJHkucjcZ4LoiDzhLvVl7ReP6sxwFcantgRg0ixndQGcCGlQd8tqrcWtKTIcWeZ02rQH6w53NS-a97B0xJJ2AmN7cJUB2gOaJWxfBXPQYoBoFAMLWCg18Xihpd3fyN9YA1Sbo2kIo2v_hUOoE-5X1-zLW0AUbk_TkVqc-iPZ9Npn7c4ykPJ7_MWF2fLMqWIJ2GCzclt4gS_w80wmu-Vpf_dhh7Nfn3HpLzBbNg1f6advfYkP4o6z-I_b5InWormi2K9reiq0YHCKujWXNdZbFRJZO4qEX-n4LDZOelmpys-NIgD5lUY3GVGYzDoq9JsMCEgvedIxhyPb_sgfjkbUoczbVGyeLQob22UIC962-NR1EOl3CG-R8JiJ0T4efyjU2_hZp45KZrqsUMfEU5WtDidlb11A6knT4nd9d19gorgSIthCeLz3Wyv76FXO8jfGo_NRtapj4FR8jxm_IuHqsb3wLPe1DNXnZg3RzLxbdT1Ui6iOau6Rf0tfc65wmLPAU9QomN9_4oVqrBFm8ONtl-71tfWVmphzqffB5VueC9pG2YySXzh14RTrDVrMgA6e9KGSgwnxW4rlCTy4afF_sEBw3rQGZ8CCcIGgIYUP-QVxe5cBx3o8v938QTC0gj9911z5xyvYSe-LLr2hnGUKaSx5rVmFjRrutyTEiDvRrieVPlByxcjKQY7l6XUTSTvzfIVOp9Dg2JQoNwVQxQAD7Qo6PnzV3wQuQRp4XvSkxcaEBWHIsbJdr-1pokcO4xEm6zJGhk9yQ3mC7nYCGVHKbdWtKLavZroCnAzPrkUQcQ0jC1LdgEQ6XB9gIbwsDmPcVrtWXiaijoy_igygkBFc=","summary":[{"type":"summary_text","text":"**Inspecting Drop implementation for watchdog thread cleanup**"}]},{"id":"fc_089095e18118ee7d016a59d2ceee34819097498d6e473fcc22","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing watchdog teardown\",\"pattern\":\"impl Drop for BrokerConn|fn watchdog|stop|shutdown|dog|JoinHandle|Condvar|wait_timeout|while\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_tzjuEdR2tMd3CQQeqLbxqAzj","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2ceee4c819090ce3e4e4c30f54c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading broker connection watchdog\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"130-480\"}","call_id":"call_yBDl3eCun8QpzM3VumefUs7z","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceee58819080cfdfd3946f50b6","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading broker handler cleanup\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"2560-2650,2780-2825,2910-2935,3015-3220\"}","call_id":"call_SExgg9kvA7xaBtPBGw3bj5mT","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceee6081909eff87c1cada02e7","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading retirement implementation\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"910-980,1760-1860,1935-1990\"}","call_id":"call_BrpxetdBFDnUizGLcpcv63mx","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceee94819089fb1a636b7385e5","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading retire dispatch\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"4360-4410\"}","call_id":"call_3WVGBWm7pcP9WbqOe7wR76Zj","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceee988190bba4aa42200d0b5c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain connection construction\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"210-370,1430-1505,1725-1760\"}","call_id":"call_nwJJqsISTr9220pJSyBK3wT3","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceeea08190ab480fe661145c4a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading attach two-connection path\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":\"270-430,570-630\"}","call_id":"call_fc1tUUytdK6KRSXWRn2fyEKD","name":"read"},{"id":"fc_089095e18118ee7d016a59d2ceeea48190abd9aab0fdfb82bf","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pool unit coverage\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"1270-1530\"}","call_id":"call_WTWmUh0B8LnRXncf7w5GUju4","name":"read"}]},"duration":10425.37539999187,"ttft":2499.8182999938726,"contextSnapshot":{"promptTokens":140892,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tzjuEdR2tMd3CQQeqLbxqAzj|fc_089095e18118ee7d016a59d2ceee34819097498d6e473fcc22","toolName":"grep","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Tracing watchdog teardown"},"id":"b2499649","parentId":"a480a185","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yBDl3eCun8QpzM3VumefUs7z|fc_089095e18118ee7d016a59d2ceee4c819090ce3e4e4c30f54c","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Reading broker connection watchdog"},"id":"01e5ece6","parentId":"b2499649","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_SExgg9kvA7xaBtPBGw3bj5mT|fc_089095e18118ee7d016a59d2ceee58819080cfdfd3946f50b6","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading broker handler cleanup"},"id":"9b80969d","parentId":"01e5ece6","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BrpxetdBFDnUizGLcpcv63mx|fc_089095e18118ee7d016a59d2ceee6081909eff87c1cada02e7","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading retirement implementation"},"id":"dd2ac89e","parentId":"9b80969d","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_3WVGBWm7pcP9WbqOe7wR76Zj|fc_089095e18118ee7d016a59d2ceee94819089fb1a636b7385e5","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading retire dispatch"},"id":"1737c407","parentId":"dd2ac89e","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_nwJJqsISTr9220pJSyBK3wT3|fc_089095e18118ee7d016a59d2ceee988190bba4aa42200d0b5c","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading brain connection construction"},"id":"90a73ce7","parentId":"1737c407","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fc1tUUytdK6KRSXWRn2fyEKD|fc_089095e18118ee7d016a59d2ceeea08190ab480fe661145c4a","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Reading attach two-connection path"},"id":"a91d5fd5","parentId":"90a73ce7","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_WTWmUh0B8LnRXncf7w5GUju4|fc_089095e18118ee7d016a59d2ceeea48190abd9aab0fdfb82bf","toolName":"read","startedAt":"2026-07-17T06:59:27.001Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading pool unit coverage"},"id":"24361914","parentId":"a91d5fd5","timestamp":"2026-07-17T06:59:27.001Z"}
{"type":"message","id":"603b5030","parentId":"24361914","timestamp":"2026-07-17T06:59:27.009Z","message":{"role":"toolResult","toolCallId":"call_tzjuEdR2tMd3CQQeqLbxqAzj|fc_089095e18118ee7d016a59d2ceee34819097498d6e473fcc22","toolName":"grep","content":[{"type":"text","text":" 9|//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\n*10|//! A connected consumer that stops reading (an `rc --take` whose process\n 11|//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the\n 12|//! logical stall-evict (`stall_evict_controller`) releases only the controller\n 13|//! ROLE — it never cancels the in-flight pipe write or closes the physical\n...\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...\n 46|//! synchronous return IS the completion handshake); (4) the retired half is\n*47|//! parked in a graveyard slot — never dropped while the watchdog might still\n 48|//! touch the raw handle (drop = `CloseHandle`, and a recycled handle value\n 49|//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\n 50|//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\n...\n 63|use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\n*64|use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock};\n*65|use std::thread::{self, JoinHandle};\n 66|use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};\n 67|\n 68|use interprocess::local_socket::SendHalf;\n...\n 130|\n*131|/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\n 132|struct Dog {\n*133|    /// `Some((op_id, deadline))` while a write is in flight.\n 134|    inflight: Option<(u64, Instant)>,\n*135|    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\n*136|    shutdown: bool,\n 137|}\n 138|\n*139|/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\n*140|/// the watchdog must not keep the conn alive).\n 141|struct Inner {\n 142|    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143|    id: u64,\n...\n 149|    gate: Mutex<Gate>,\n*150|    gate_cv: Condvar,\n*151|    dog: Mutex<Dog>,\n*152|    dog_cv: Condvar,\n 153|    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\n 154|    /// conn refuses every future write and is never re-gated.\n 155|    poisoned: AtomicBool,\n*156|    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\n 157|    /// gate-waiter exhausted its bound) — as opposed to an organic I/O error\n 158|    /// (peer death). Callers see it as `io::ErrorKind::TimedOut` and can\n 159|    /// count the wedge (the W2 stall-evict tally) without conflating it with\n...\n 164|    /// A retired (poisoned) half parks here so its OS handle stays alive\n*165|    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\n 166|    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\n 167|    graveyard: Mutex<Option<SendHalf>>,\n*168|    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\n 169|    op_seq: AtomicU64,\n 170|    /// The absolute per-write bound (gate-wait + OS write completion).\n 171|    bound: Duration,\n...\n 301|        #[cfg(unix)]\n*302|        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\n 303|        // fails the blocked write (EPIPE), EOFs the parked read, and fails\n 304|        // every future op — on Unix this one call IS the whole teardown.\n 305|        unsafe {\n*306|            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\n 307|        }\n 308|        // Wake gate-waiters so they observe the poison instead of sleeping\n*309|        // out their full deadline, and the watchdog so it re-evaluates.\n 310|        self.gate_cv.notify_all();\n*311|        self.dog_cv.notify_all();\n 312|    }\n 313|\n 314|    /// Sever the physical connection (exactly once), AFTER the canceled op\n...\n 319|    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\n*320|    /// `shutdown` in [`poison_and_cancel`] already did all of this.\n 321|    // On Unix the cfg(windows) teardown strips out and the idempotence\n 322|    // guard's early `return` becomes the function tail — needless_return\n 323|    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n...\n 330|        #[cfg(windows)]\n*331|        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\n 332|        unsafe {\n 333|            let _ = win::DisconnectNamedPipe(self.raw);\n 334|            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n...\n 337|\n*338|    /// The watchdog body: park until a write is in flight, then sleep to its\n 339|    /// absolute deadline. If the op is still in flight at the deadline,\n 340|    /// poison + cancel, re-canceling periodically until the op reports\n 341|    /// completion (the writer clears `inflight` the instant `write_frame`\n 342|    /// returns — that return IS the buffer-release handshake), then sever.\n*343|    fn watchdog(&self) {\n*344|        let mut d = recover(&self.dog);\n 345|        loop {\n*346|            // Park until armed (or shutdown).\n 347|            let (op, deadline) = loop {\n*348|                if d.shutdown {\n 349|                    return;\n 350|                }\n 351|                if let Some(armed) = d.inflight {\n...\n 354|                d = self\n*355|                    .dog_cv\n 356|                    .wait(d)\n 357|                    .unwrap_or_else(|p| p.into_inner());\n 358|            };\n*359|            // Sleep toward the deadline while THIS op stays in flight.\n 360|            let fired = loop {\n*361|                if d.shutdown {\n 362|                    return;\n 363|                }\n 364|                match d.inflight {\n...\n 373|                let (g, _) = self\n*374|                    .dog_cv\n*375|                    .wait_timeout(d, deadline - now)\n 376|                    .unwrap_or_else(|p| p.into_inner());\n 377|                d = g;\n 378|            };\n...\n 390|            self.poison_and_cancel(None);\n*391|            d = recover(&self.dog);\n*392|            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\n 393|                let (g, _) = self\n*394|                    .dog_cv\n*395|                    .wait_timeout(d, Duration::from_millis(100))\n 396|                    .unwrap_or_else(|p| p.into_inner());\n 397|                d = g;\n 398|                drop(d);\n 399|                self.poison_and_cancel(None);\n*400|                d = recover(&self.dog);\n 401|            }\n*402|            if d.shutdown {\n 403|                return;\n 404|            }\n 405|            drop(d);\n 406|            self.sever();\n*407|            d = recover(&self.dog);\n 408|        }\n 409|    }\n 410|}\n...\n 413|/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n*414|/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\n 415|pub(crate) struct BrokerConn {\n 416|    inner: Arc<Inner>,\n*417|    /// Joined on drop — after `shutdown` is raised — so the raw handle the\n*418|    /// watchdog aborts through strictly outlives every raw use.\n*419|    dog: Option<JoinHandle<()>>,\n 420|}\n 421|\n 422|impl BrokerConn {\n...\n 432|            gate: Mutex::new(Gate { half: Some(half) }),\n*433|            gate_cv: Condvar::new(),\n*434|            dog: Mutex::new(Dog {\n 435|                inflight: None,\n*436|                shutdown: false,\n 437|            }),\n*438|            dog_cv: Condvar::new(),\n 439|            poisoned: AtomicBool::new(false),\n 440|            timed_out: AtomicBool::new(false),\n 441|            severed: AtomicBool::new(false),\n...\n 446|        });\n*447|        let dog_inner = Arc::clone(&inner);\n*448|        let dog = thread::Builder::new()\n*449|            .name(\"conn-watchdog\".into())\n*450|            .spawn(move || dog_inner.watchdog())\n 451|            .ok();\n*452|        BrokerConn { inner, dog }\n 453|    }\n 454|\n 455|    /// The stable per-physical-conn id — what lifecycle records key on\n...\n 522|                // that op is the wedge. Abort it out of band and fail — the\n*523|                // watchdog's own deadline (armed at that op's entry) is also\n 524|                // in flight; both paths converge on the same idempotent\n 525|                // poison/cancel.\n 526|                drop(g);\n...\n 532|                .gate_cv\n*533|                .wait_timeout(g, deadline - now)\n 534|                .unwrap_or_else(|p| p.into_inner());\n 535|            g = g2;\n 536|        }\n...\n 549|\n*550|        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\n 551|        let mut half = g.half.take().expect(\"gate half present at checkout\");\n 552|        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\n 553|        {\n*554|            let mut d = recover(&inner.dog);\n 555|            d.inflight = Some((op, deadline));\n*556|            inner.dog_cv.notify_all();\n 557|        }\n 558|        drop(g);\n 559|\n...\n 563|        // canceled or not — reported completion and the frame buffer is\n*564|        // released. Clear the arm BEFORE deciding health, so the watchdog\n*565|        // never severs while the op is formally in flight.\n 566|        {\n*567|            let mut d = recover(&inner.dog);\n 568|            d.inflight = None;\n*569|            inner.dog_cv.notify_all();\n 570|        }\n 571|\n 572|        let mut g = recover(&inner.gate);\n...\n 589|        inner.gate_cv.notify_all();\n*590|        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\n 591|        // already deadline-poisoned) surfaces as TimedOut — the caller can\n 592|        // count the wedge (W2 stall-evict tally). An organic I/O failure\n 593|        // (peer death) keeps its own error.\n...\n 625|impl BrokerConn {\n*626|    /// Check the half out of the gate without arming the watchdog — writers\n 627|    /// queue behind the gate (bounded by their own deadlines) until the\n 628|    /// returned pin drops. Test-only; never a production path.\n 629|    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\n...\n 638|\n*639|impl Drop for BrokerConn {\n 640|    fn drop(&mut self) {\n 641|        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n 642|        // is about to close — the terminal record of this conn id's life, with\n...\n 652|        {\n*653|            let mut d = recover(&self.inner.dog);\n*654|            d.shutdown = true;\n*655|            self.inner.dog_cv.notify_all();\n 656|        }\n*657|        if let Some(dog) = self.dog.take() {\n*658|            let _ = dog.join();\n 659|        }\n*660|        // Halves (gate or graveyard) drop AFTER the watchdog is gone — no raw\n 661|        // handle use can race the CloseHandle/limbo path.\n 662|    }\n 663|}"}],"details":{"scopePath":"crates/spt-daemon/src/conn.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":73,"fileCount":1,"files":["crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/conn.rs","count":73}],"truncated":false,"displayContent":"   9│//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\n *10│//! A connected consumer that stops reading (an `rc --take` whose process\n  11│//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the\n  12│//! logical stall-evict (`stall_evict_controller`) releases only the controller\n  13│//! ROLE — it never cancels the in-flight pipe write or closes the physical\n    │...\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    │...\n  46│//! synchronous return IS the completion handshake); (4) the retired half is\n *47│//! parked in a graveyard slot — never dropped while the watchdog might still\n  48│//! touch the raw handle (drop = `CloseHandle`, and a recycled handle value\n  49│//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\n  50│//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\n    │...\n  63│use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};\n *64│use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock};\n *65│use std::thread::{self, JoinHandle};\n  66│use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};\n  67│\n  68│use interprocess::local_socket::SendHalf;\n    │...\n 130│\n*131│/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.\n 132│struct Dog {\n*133│    /// `Some((op_id, deadline))` while a write is in flight.\n 134│    inflight: Option<(u64, Instant)>,\n*135│    /// Raised by [`BrokerConn::drop`]; the watchdog exits.\n*136│    shutdown: bool,\n 137│}\n 138│\n*139│/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle —\n*140│/// the watchdog must not keep the conn alive).\n 141│struct Inner {\n 142│    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143│    id: u64,\n    │...\n 149│    gate: Mutex<Gate>,\n*150│    gate_cv: Condvar,\n*151│    dog: Mutex<Dog>,\n*152│    dog_cv: Condvar,\n 153│    /// Idempotently latched on the first deadline/failure/cancel. A poisoned\n 154│    /// conn refuses every future write and is never re-gated.\n 155│    poisoned: AtomicBool,\n*156│    /// Latched when the retirement was DEADLINE-driven (watchdog fired, or a\n 157│    /// gate-waiter exhausted its bound) — as opposed to an organic I/O error\n 158│    /// (peer death). Callers see it as `io::ErrorKind::TimedOut` and can\n 159│    /// count the wedge (the W2 stall-evict tally) without conflating it with\n    │...\n 164│    /// A retired (poisoned) half parks here so its OS handle stays alive\n*165│    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\n 166│    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\n 167│    graveyard: Mutex<Option<SendHalf>>,\n*168│    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\n 169│    op_seq: AtomicU64,\n 170│    /// The absolute per-write bound (gate-wait + OS write completion).\n 171│    bound: Duration,\n    │...\n 301│        #[cfg(unix)]\n*302│        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\n 303│        // fails the blocked write (EPIPE), EOFs the parked read, and fails\n 304│        // every future op — on Unix this one call IS the whole teardown.\n 305│        unsafe {\n*306│            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\n 307│        }\n 308│        // Wake gate-waiters so they observe the poison instead of sleeping\n*309│        // out their full deadline, and the watchdog so it re-evaluates.\n 310│        self.gate_cv.notify_all();\n*311│        self.dog_cv.notify_all();\n 312│    }\n 313│\n 314│    /// Sever the physical connection (exactly once), AFTER the canceled op\n    │...\n 319│    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\n*320│    /// `shutdown` in [`poison_and_cancel`] already did all of this.\n 321│    // On Unix the cfg(windows) teardown strips out and the idempotence\n 322│    // guard's early `return` becomes the function tail — needless_return\n 323│    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n    │...\n 330│        #[cfg(windows)]\n*331│        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\n 332│        unsafe {\n 333│            let _ = win::DisconnectNamedPipe(self.raw);\n 334│            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n    │...\n 337│\n*338│    /// The watchdog body: park until a write is in flight, then sleep to its\n 339│    /// absolute deadline. If the op is still in flight at the deadline,\n 340│    /// poison + cancel, re-canceling periodically until the op reports\n 341│    /// completion (the writer clears `inflight` the instant `write_frame`\n 342│    /// returns — that return IS the buffer-release handshake), then sever.\n*343│    fn watchdog(&self) {\n*344│        let mut d = recover(&self.dog);\n 345│        loop {\n*346│            // Park until armed (or shutdown).\n 347│            let (op, deadline) = loop {\n*348│                if d.shutdown {\n 349│                    return;\n 350│                }\n 351│                if let Some(armed) = d.inflight {\n    │...\n 354│                d = self\n*355│                    .dog_cv\n 356│                    .wait(d)\n 357│                    .unwrap_or_else(|p| p.into_inner());\n 358│            };\n*359│            // Sleep toward the deadline while THIS op stays in flight.\n 360│            let fired = loop {\n*361│                if d.shutdown {\n 362│                    return;\n 363│                }\n 364│                match d.inflight {\n    │...\n 373│                let (g, _) = self\n*374│                    .dog_cv\n*375│                    .wait_timeout(d, deadline - now)\n 376│                    .unwrap_or_else(|p| p.into_inner());\n 377│                d = g;\n 378│            };\n    │...\n 390│            self.poison_and_cancel(None);\n*391│            d = recover(&self.dog);\n*392│            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\n 393│                let (g, _) = self\n*394│                    .dog_cv\n*395│                    .wait_timeout(d, Duration::from_millis(100))\n 396│                    .unwrap_or_else(|p| p.into_inner());\n 397│                d = g;\n 398│                drop(d);\n 399│                self.poison_and_cancel(None);\n*400│                d = recover(&self.dog);\n 401│            }\n*402│            if d.shutdown {\n 403│                return;\n 404│            }\n 405│            drop(d);\n 406│            self.sever();\n*407│            d = recover(&self.dog);\n 408│        }\n 409│    }\n 410│}\n    │...\n 413│/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n*414│/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\n 415│pub(crate) struct BrokerConn {\n 416│    inner: Arc<Inner>,\n*417│    /// Joined on drop — after `shutdown` is raised — so the raw handle the\n*418│    /// watchdog aborts through strictly outlives every raw use.\n*419│    dog: Option<JoinHandle<()>>,\n 420│}\n 421│\n 422│impl BrokerConn {\n    │...\n 432│            gate: Mutex::new(Gate { half: Some(half) }),\n*433│            gate_cv: Condvar::new(),\n*434│            dog: Mutex::new(Dog {\n 435│                inflight: None,\n*436│                shutdown: false,\n 437│            }),\n*438│            dog_cv: Condvar::new(),\n 439│            poisoned: AtomicBool::new(false),\n 440│            timed_out: AtomicBool::new(false),\n 441│            severed: AtomicBool::new(false),\n    │...\n 446│        });\n*447│        let dog_inner = Arc::clone(&inner);\n*448│        let dog = thread::Builder::new()\n*449│            .name(\"conn-watchdog\".into())\n*450│            .spawn(move || dog_inner.watchdog())\n 451│            .ok();\n*452│        BrokerConn { inner, dog }\n 453│    }\n 454│\n 455│    /// The stable per-physical-conn id — what lifecycle records key on\n    │...\n 522│                // that op is the wedge. Abort it out of band and fail — the\n*523│                // watchdog's own deadline (armed at that op's entry) is also\n 524│                // in flight; both paths converge on the same idempotent\n 525│                // poison/cancel.\n 526│                drop(g);\n    │...\n 532│                .gate_cv\n*533│                .wait_timeout(g, deadline - now)\n 534│                .unwrap_or_else(|p| p.into_inner());\n 535│            g = g2;\n 536│        }\n    │...\n 549│\n*550│        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\n 551│        let mut half = g.half.take().expect(\"gate half present at checkout\");\n 552│        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\n 553│        {\n*554│            let mut d = recover(&inner.dog);\n 555│            d.inflight = Some((op, deadline));\n*556│            inner.dog_cv.notify_all();\n 557│        }\n 558│        drop(g);\n 559│\n    │...\n 563│        // canceled or not — reported completion and the frame buffer is\n*564│        // released. Clear the arm BEFORE deciding health, so the watchdog\n*565│        // never severs while the op is formally in flight.\n 566│        {\n*567│            let mut d = recover(&inner.dog);\n 568│            d.inflight = None;\n*569│            inner.dog_cv.notify_all();\n 570│        }\n 571│\n 572│        let mut g = recover(&inner.gate);\n    │...\n 589│        inner.gate_cv.notify_all();\n*590│        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\n 591│        // already deadline-poisoned) surfaces as TimedOut — the caller can\n 592│        // count the wedge (W2 stall-evict tally). An organic I/O failure\n 593│        // (peer death) keeps its own error.\n    │...\n 625│impl BrokerConn {\n*626│    /// Check the half out of the gate without arming the watchdog — writers\n 627│    /// queue behind the gate (bounded by their own deadlines) until the\n 628│    /// returned pin drops. Test-only; never a production path.\n 629│    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\n    │...\n 638│\n*639│impl Drop for BrokerConn {\n 640│    fn drop(&mut self) {\n 641│        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n 642│        // is about to close — the terminal record of this conn id's life, with\n    │...\n 652│        {\n*653│            let mut d = recover(&self.inner.dog);\n*654│            d.shutdown = true;\n*655│            self.inner.dog_cv.notify_all();\n 656│        }\n*657│        if let Some(dog) = self.dog.take() {\n*658│            let _ = dog.join();\n 659│        }\n*660│        // Halves (gate or graveyard) drop AFTER the watchdog is gone — no raw\n 661│        // handle use can race the CloseHandle/limbo path.\n 662│    }\n 663│}"},"isError":false,"timestamp":1784271567009}}
{"type":"message","id":"3345dd74","parentId":"603b5030","timestamp":"2026-07-17T06:59:27.051Z","message":{"role":"toolResult","toolCallId":"call_SExgg9kvA7xaBtPBGw3bj5mT|fc_089095e18118ee7d016a59d2ceee58819080cfdfd3946f50b6","toolName":"read","content":[{"type":"text","text":"/// the brain (Spike #6 constraint 1).\npub struct Broker {\n    listener: LocalSocketTransport,\n    /// The namespaced listener name — held so [`Broker::stop`] can dial a throwaway\n    /// connection to unblock the blocked `accept()` in [`serve`](Self::serve) (there is\n    /// no other wakeup for the accept loop).\n    name: String,\n    /// Graceful-stop latch (C1, REQ-HAZARD-DAEMON-STOP-BARRIER teardown-hygiene):\n    /// [`serve`](Self::serve) checks it after each accept and returns. Without it\n    /// `serve` is an unstoppable accept loop, so a test (or any host) that stands a\n    /// broker up leaks the serve thread AND every accepted-conn handler thread at\n    /// teardown — the load population that starved the seed stop barrier.\n    stop: AtomicBool,\n    sessions: Arc<Mutex<HashMap<u64, HostedSession>>>,\n    next_id: AtomicU64,\n    log_cap_chunks: usize,\n    /// The exactly-once anchor for idempotent (`op_id`-carrying) effects.\n    journal: Arc<EffectJournal>,\n    /// The broker-owned net endpoint + live QUIC connections (D4a, ADR-0004 §B).\n    /// Empty when bound without a network host (net-disabled / most tests) OR\n    /// when net is still being brought up (REQ-DAEMON-9 boot-race self-heal):\n    /// a `OnceLock` so the background net-retry can ATTACH the host after bind\n    /// without a relock on the hot effect-dispatch read path (`.get()` is\n    /// lock-free). Settable exactly once — the first host wins.\n    net: std::sync::OnceLock<NetHost>,\n    /// The broker→supervisor planned-restart signal (ADR-0018 D3-3): a\n    /// `KIND_BRAIN_RESTART` handler raises it so the brain supervisor cycles the\n    /// child onto a freshly-swapped binary. Empty on a bare broker (a test broker\n    /// with no supervisor) — the verb is then accepted but unhonored. Set once by\n    /// `Daemon::run` after it spawns the supervisor.\n    brain_restart: std::sync::OnceLock<Arc<crate::brainproc::BrainRestart>>,\n    /// Held meet results for the two-phase join (REQ-JOIN-TWO-PHASE): a\n    /// `session_id → (subnet, real address, 5-min deadline)` between\n    /// [`KIND_PAIR_MEET`] and [`KIND_PAIR_CODE_SUBMIT`]. The code phase dials the\n    /// held address (no re-search); a wrong-code retry keeps the entry. Pairing\n    /// is never journaled (the code must not persist) — in-memory only, and a\n    /// daemon restart simply makes the operator re-run the meet.\n    pair_holds: Mutex<HashMap<u64, PairHold>>,\n    /// In-flight single-flight WAKE claims, keyed by endpoint id (W4,\n    /// REQ-SPAWN-COLLISION-GUARD-LIVE-DUP). The broker is the sole spawner post-W3 (the\n    /// rc WMI auto-launch retired by REQ-RC-RECONNECT-TRUTH), so its own spawn choke\n    /// ([`dispatch_spawn`](Self::dispatch_spawn)) is the single-flight authority — no\n    /// perch-record claim (that would add another info.json writer, the exact\n    /// write-stomp this REQ kills). An id is claimed for the duration of one spawn;\n    /// a duplicate wake for a claimed-or-already-live endpoint stands down (no-op ack).\n    wake_inflight: Mutex<HashSet<String>>,\n    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n    /// UPDATE-WEDGE round-4 int gate's observability seam\n    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n    /// physical conn must drive its handler to EOF cleanup within the write\n    /// bound WITHOUT the client dropping — the test watches this count fall.\n    conn_handlers: AtomicUsize,\n}\n\n/// One held meet result for the two-phase join (see [`Broker::pair_holds`]).\nstruct PairHold {\n    subnet: String,\n    addr: EndpointAddr,\n    /// When the wait-for-code window expires (meet + 5 minutes).\n    deadline: Instant,\n}\n\n/// The two-phase join's wait-for-code window: the daemon holds the met address\n/// this long before a code must arrive (ADR-0030 / REQ-JOIN-TWO-PHASE).\nconst PAIR_CODE_WAIT: Duration = Duration::from_secs(5 * 60);\n\n/// Whether a held meet is still within its wait-for-code window\n/// (REQ-JOIN-TWO-PHASE). Pure (the testable core of the timeout).\nfn hold_is_live(deadline: Instant, now: Instant) -> bool {\n    deadline > now\n}\n\n/// Whether a failed code submission should KEEP the held meet for another try\n/// (REQ-JOIN-TWO-PHASE): only a wrong code — the operator re-enters it and the\n/// ceremony re-runs against the same held address (no re-search). Every other\n/// verdict (rate-limited, clock-skew, unknown-subnet, plumbing) is terminal.\nfn fail_keeps_hold(fail: &crate::pairhost::JoinFail) -> bool {\n    matches!(fail, crate::pairhost::JoinFail::WrongCode)\n}\n\n/// Render a [`JoinFail`](crate::pairhost::JoinFail) as the classified\n/// `pair-joined` failure frame the CLI's guided UX reads — single-sourced so the\n/// one-shot join, the meet phase, and the code phase all classify identically.\nfn pair_fail_frame(fail: &crate::pairhost::JoinFail) -> Envelope {\n    use crate::pairhost::JoinFail;\n    let (class, retry) = match fail {\n        JoinFail::NoSeedHolder(_) => (\"no-seed-holder\", None),\n        JoinFail::WrongCode => (\"wrong-code\", None),\n        JoinFail::RateLimited(retry) => (\"rate-limited\", *retry),\n        JoinFail::UnknownSubnet(_) => (\"unknown-subnet\", None),\n…\n    };\n…\n}\n…\n    /// is the long-lived process; this is its main loop. Per-connection\n    /// threading is half the KH 7.4 isolation stance: one brain's blocking\n    /// dispatch can never stall another's (the per-session half is\n    /// [`Broker::dispatch_spawn`]'s drain/waiter threads).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n        loop {\n            let conn = self.listener.accept()?;\n            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n            // throwaway connection that woke this accept — observe it and return so the\n            // serve thread (and, with it, the listener) tears down instead of leaking.\n            if self.stop.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            let me = Arc::clone(self);\n            thread::spawn(move || {\n                let _ = me.handle_conn(conn);\n            });\n        }\n    }\n\n    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n    /// load that starved the seed stop barrier — REQ-HAZARD-DAEMON-STOP-BARRIER).\n    // [impl->REQ-HAZARD-DAEMON-STOP-BARRIER]\n    pub fn stop(&self) {\n        self.stop.store(true, Ordering::Release);\n        let _ = LocalSocketTransport::connect(&self.name);\n    }\n\n    /// Accept exactly one connection and handle it on a spawned thread, returning\n    /// that thread's handle. The single-brain entry point the tests exercise.\n    pub fn serve_once(self: &Arc<Self>) -> io::Result<JoinHandle<()>> {\n        let conn = self.listener.accept()?;\n        let me = Arc::clone(self);\n        Ok(thread::spawn(move || {\n            let _ = me.handle_conn(conn);\n        }))\n    }\n\n    /// Number of currently hosted sessions (for tests / introspection).\n    pub fn session_count(&self) -> usize {\n        recover(&self.sessions).len()\n    }\n…\n    pub fn test_session_viewer_count(&self, id: u64) -> Option<usize> {\n        let log = {\n…\n            match sessions.get(&id) {\n…\n                None => return None,\n            }\n        };\n        let count = recover_log(&log).viewers.len();\n        Some(count)\n    }\n\n    /// Live per-connection handler threads (for tests / introspection). The\n    /// UPDATE-WEDGE round-4 gate watches this fall when a poisoned physical conn\n    /// drives its handler to EOF cleanup within the write bound — without the\n    /// black-holed client dropping its end.\n    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n    pub fn conn_handler_count(&self) -> usize {\n        self.conn_handlers.load(Ordering::Acquire)\n    }\n\n    /// The live **resident adapter children** (translation binaries) for\n    /// `endpoint` — the processes a live adapter update (W3d) stops and restarts\n    /// so the install dir's locked binaries free up. The broker `sessions` map IS\n    /// the per-endpoint resident registry (W3a, ADR-0025): each `HostedSession`\n    /// already carries its `endpoint` + lifecycle-managed `translation` child.\n    // [impl->REQ-ADAPTER-LIVE-UPDATE]\n    #[allow(dead_code)] // consumed by W3d `dispatch_adapter_apply` (next sub-commit)\n    fn resident_translations_for_endpoint(&self, endpoint: &str) -> Vec<Arc<Translation>> {\n        let map = recover(&self.sessions);\n        map.values()\n            .filter(|s| s.endpoint == endpoint)\n            .filter_map(|s| s.translation.clone())\n            .collect()\n    }\n…\n    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\n        self.sessions\n            .lock()\n            .unwrap()\n            .get(&id)\n            .map(|h| recover_log(&h.log).next_seq)\n    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    // TEST-RIG-ONLY (T7 mixed-image N-1): with\n                    // SPT_TEST_STREAM_OPENER_UNSUPPORTED set this broker\n                    // answers the verb EXACTLY like an N-1 broker that\n                    // predates it — the forward-compat unknown-kind reply\n                    // below, byte-identical (the stable KH-2.3 wire fact the\n                    // dispatcher's fallback gate keys on). Never set in\n                    // production.\n                    if std::env::var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\").is_ok() {\n                        send_error(&send, &format!(\"unknown command kind {:?}\", env.kind));\n                    } else if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n…\n                }\n…\n            }\n        }\n…\n    }"}],"details":{"displayContent":{"text":"/// the brain (Spike #6 constraint 1).\npub struct Broker {\n    listener: LocalSocketTransport,\n    /// The namespaced listener name — held so [`Broker::stop`] can dial a throwaway\n    /// connection to unblock the blocked `accept()` in [`serve`](Self::serve) (there is\n    /// no other wakeup for the accept loop).\n    name: String,\n    /// Graceful-stop latch (C1, REQ-HAZARD-DAEMON-STOP-BARRIER teardown-hygiene):\n    /// [`serve`](Self::serve) checks it after each accept and returns. Without it\n    /// `serve` is an unstoppable accept loop, so a test (or any host) that stands a\n    /// broker up leaks the serve thread AND every accepted-conn handler thread at\n    /// teardown — the load population that starved the seed stop barrier.\n    stop: AtomicBool,\n    sessions: Arc<Mutex<HashMap<u64, HostedSession>>>,\n    next_id: AtomicU64,\n    log_cap_chunks: usize,\n    /// The exactly-once anchor for idempotent (`op_id`-carrying) effects.\n    journal: Arc<EffectJournal>,\n    /// The broker-owned net endpoint + live QUIC connections (D4a, ADR-0004 §B).\n    /// Empty when bound without a network host (net-disabled / most tests) OR\n    /// when net is still being brought up (REQ-DAEMON-9 boot-race self-heal):\n    /// a `OnceLock` so the background net-retry can ATTACH the host after bind\n    /// without a relock on the hot effect-dispatch read path (`.get()` is\n    /// lock-free). Settable exactly once — the first host wins.\n    net: std::sync::OnceLock<NetHost>,\n    /// The broker→supervisor planned-restart signal (ADR-0018 D3-3): a\n    /// `KIND_BRAIN_RESTART` handler raises it so the brain supervisor cycles the\n    /// child onto a freshly-swapped binary. Empty on a bare broker (a test broker\n    /// with no supervisor) — the verb is then accepted but unhonored. Set once by\n    /// `Daemon::run` after it spawns the supervisor.\n    brain_restart: std::sync::OnceLock<Arc<crate::brainproc::BrainRestart>>,\n    /// Held meet results for the two-phase join (REQ-JOIN-TWO-PHASE): a\n    /// `session_id → (subnet, real address, 5-min deadline)` between\n    /// [`KIND_PAIR_MEET`] and [`KIND_PAIR_CODE_SUBMIT`]. The code phase dials the\n    /// held address (no re-search); a wrong-code retry keeps the entry. Pairing\n    /// is never journaled (the code must not persist) — in-memory only, and a\n    /// daemon restart simply makes the operator re-run the meet.\n    pair_holds: Mutex<HashMap<u64, PairHold>>,\n    /// In-flight single-flight WAKE claims, keyed by endpoint id (W4,\n    /// REQ-SPAWN-COLLISION-GUARD-LIVE-DUP). The broker is the sole spawner post-W3 (the\n    /// rc WMI auto-launch retired by REQ-RC-RECONNECT-TRUTH), so its own spawn choke\n    /// ([`dispatch_spawn`](Self::dispatch_spawn)) is the single-flight authority — no\n    /// perch-record claim (that would add another info.json writer, the exact\n    /// write-stomp this REQ kills). An id is claimed for the duration of one spawn;\n    /// a duplicate wake for a claimed-or-already-live endpoint stands down (no-op ack).\n    wake_inflight: Mutex<HashSet<String>>,\n    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n    /// UPDATE-WEDGE round-4 int gate's observability seam\n    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n    /// physical conn must drive its handler to EOF cleanup within the write\n    /// bound WITHOUT the client dropping — the test watches this count fall.\n    conn_handlers: AtomicUsize,\n}\n\n/// One held meet result for the two-phase join (see [`Broker::pair_holds`]).\nstruct PairHold {\n    subnet: String,\n    addr: EndpointAddr,\n    /// When the wait-for-code window expires (meet + 5 minutes).\n    deadline: Instant,\n}\n\n/// The two-phase join's wait-for-code window: the daemon holds the met address\n/// this long before a code must arrive (ADR-0030 / REQ-JOIN-TWO-PHASE).\nconst PAIR_CODE_WAIT: Duration = Duration::from_secs(5 * 60);\n\n/// Whether a held meet is still within its wait-for-code window\n/// (REQ-JOIN-TWO-PHASE). Pure (the testable core of the timeout).\nfn hold_is_live(deadline: Instant, now: Instant) -> bool {\n    deadline > now\n}\n\n/// Whether a failed code submission should KEEP the held meet for another try\n/// (REQ-JOIN-TWO-PHASE): only a wrong code — the operator re-enters it and the\n/// ceremony re-runs against the same held address (no re-search). Every other\n/// verdict (rate-limited, clock-skew, unknown-subnet, plumbing) is terminal.\nfn fail_keeps_hold(fail: &crate::pairhost::JoinFail) -> bool {\n    matches!(fail, crate::pairhost::JoinFail::WrongCode)\n}\n\n/// Render a [`JoinFail`](crate::pairhost::JoinFail) as the classified\n/// `pair-joined` failure frame the CLI's guided UX reads — single-sourced so the\n/// one-shot join, the meet phase, and the code phase all classify identically.\nfn pair_fail_frame(fail: &crate::pairhost::JoinFail) -> Envelope {\n    use crate::pairhost::JoinFail;\n    let (class, retry) = match fail {\n        JoinFail::NoSeedHolder(_) => (\"no-seed-holder\", None),\n        JoinFail::WrongCode => (\"wrong-code\", None),\n        JoinFail::RateLimited(retry) => (\"rate-limited\", *retry),\n        JoinFail::UnknownSubnet(_) => (\"unknown-subnet\", None),\n…\n    };\n…\n}\n…\n    /// is the long-lived process; this is its main loop. Per-connection\n    /// threading is half the KH 7.4 isolation stance: one brain's blocking\n    /// dispatch can never stall another's (the per-session half is\n    /// [`Broker::dispatch_spawn`]'s drain/waiter threads).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n        loop {\n            let conn = self.listener.accept()?;\n            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n            // throwaway connection that woke this accept — observe it and return so the\n            // serve thread (and, with it, the listener) tears down instead of leaking.\n            if self.stop.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            let me = Arc::clone(self);\n            thread::spawn(move || {\n                let _ = me.handle_conn(conn);\n            });\n        }\n    }\n\n    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n    /// load that starved the seed stop barrier — REQ-HAZARD-DAEMON-STOP-BARRIER).\n    // [impl->REQ-HAZARD-DAEMON-STOP-BARRIER]\n    pub fn stop(&self) {\n        self.stop.store(true, Ordering::Release);\n        let _ = LocalSocketTransport::connect(&self.name);\n    }\n\n    /// Accept exactly one connection and handle it on a spawned thread, returning\n    /// that thread's handle. The single-brain entry point the tests exercise.\n    pub fn serve_once(self: &Arc<Self>) -> io::Result<JoinHandle<()>> {\n        let conn = self.listener.accept()?;\n        let me = Arc::clone(self);\n        Ok(thread::spawn(move || {\n            let _ = me.handle_conn(conn);\n        }))\n    }\n\n    /// Number of currently hosted sessions (for tests / introspection).\n    pub fn session_count(&self) -> usize {\n        recover(&self.sessions).len()\n    }\n…\n    pub fn test_session_viewer_count(&self, id: u64) -> Option<usize> {\n        let log = {\n…\n            match sessions.get(&id) {\n…\n                None => return None,\n            }\n        };\n        let count = recover_log(&log).viewers.len();\n        Some(count)\n    }\n\n    /// Live per-connection handler threads (for tests / introspection). The\n    /// UPDATE-WEDGE round-4 gate watches this fall when a poisoned physical conn\n    /// drives its handler to EOF cleanup within the write bound — without the\n    /// black-holed client dropping its end.\n    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n    pub fn conn_handler_count(&self) -> usize {\n        self.conn_handlers.load(Ordering::Acquire)\n    }\n\n    /// The live **resident adapter children** (translation binaries) for\n    /// `endpoint` — the processes a live adapter update (W3d) stops and restarts\n    /// so the install dir's locked binaries free up. The broker `sessions` map IS\n    /// the per-endpoint resident registry (W3a, ADR-0025): each `HostedSession`\n    /// already carries its `endpoint` + lifecycle-managed `translation` child.\n    // [impl->REQ-ADAPTER-LIVE-UPDATE]\n    #[allow(dead_code)] // consumed by W3d `dispatch_adapter_apply` (next sub-commit)\n    fn resident_translations_for_endpoint(&self, endpoint: &str) -> Vec<Arc<Translation>> {\n        let map = recover(&self.sessions);\n        map.values()\n            .filter(|s| s.endpoint == endpoint)\n            .filter_map(|s| s.translation.clone())\n            .collect()\n    }\n…\n    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\n        self.sessions\n            .lock()\n            .unwrap()\n            .get(&id)\n            .map(|h| recover_log(&h.log).next_seq)\n    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    // TEST-RIG-ONLY (T7 mixed-image N-1): with\n                    // SPT_TEST_STREAM_OPENER_UNSUPPORTED set this broker\n                    // answers the verb EXACTLY like an N-1 broker that\n                    // predates it — the forward-compat unknown-kind reply\n                    // below, byte-identical (the stable KH-2.3 wire fact the\n                    // dispatcher's fallback gate keys on). Never set in\n                    // production.\n                    if std::env::var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\").is_ok() {\n                        send_error(&send, &format!(\"unknown command kind {:?}\", env.kind));\n                    } else if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n…\n                }\n…\n            }\n        }\n…\n    }","startLine":2560,"lineNumbers":[2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,null,2653,null,2664,null,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,null,2905,2906,null,2908,null,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,null,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,null,3239,null,3258,3259,null,3289]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"}}},"isError":false,"timestamp":1784271567051}}
{"type":"message","id":"8617bbac","parentId":"3345dd74","timestamp":"2026-07-17T06:59:27.081Z","message":{"role":"toolResult","toolCallId":"call_fc1tUUytdK6KRSXWRn2fyEKD|fc_089095e18118ee7d016a59d2ceeea08190ab480fe661145c4a","toolName":"read","content":[{"type":"text","text":"\n/// Serve one attach stream against this brain's broker, blocking until the\n/// session exits, the operator detaches, or the gate refuses.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // This worker's attach GENERATION (ADR-0038 Amendment fix 6) — set when\n    // its Request lands; every terminal release carries it so a STALE worker\n    // (an older generation) can never clear a replacement controller.\n    let mut serve_gen: u64 = 0;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n        gen: req_gen,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            req_gen,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n            &mut serve_gen,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, serve_gen, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n…\n            BrokerEvent::ViewerEvicted { session_id: sid }\n…\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_gen(sid, Some(serve_gen)); // release ONLY our generation\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                return Err(io::Error::other(message));\n            }\n            // Applied/NetSent acks and unrelated events: not ours to handle.\n            _ => {}\n        }\n    }\n}\n\n/// Operator-side: open an attach stream on a dialed connection and send the"}],"details":{"displayContent":{"text":"\n/// Serve one attach stream against this brain's broker, blocking until the\n/// session exits, the operator detaches, or the gate refuses.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // This worker's attach GENERATION (ADR-0038 Amendment fix 6) — set when\n    // its Request lands; every terminal release carries it so a STALE worker\n    // (an older generation) can never clear a replacement controller.\n    let mut serve_gen: u64 = 0;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n        gen: req_gen,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            req_gen,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n            &mut serve_gen,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, serve_gen, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n…\n            BrokerEvent::ViewerEvicted { session_id: sid }\n…\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_gen(sid, Some(serve_gen)); // release ONLY our generation\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                return Err(io::Error::other(message));\n            }\n            // Applied/NetSent acks and unrelated events: not ours to handle.\n            _ => {}\n        }\n    }\n}\n\n/// Operator-side: open an attach stream on a dialed connection and send the","startLine":270,"lineNumbers":[270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,null,567,null,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs"}}},"isError":false,"timestamp":1784271567081}}
{"type":"message","id":"26edd49b","parentId":"8617bbac","timestamp":"2026-07-17T06:59:27.081Z","message":{"role":"toolResult","toolCallId":"call_BrpxetdBFDnUizGLcpcv63mx|fc_089095e18118ee7d016a59d2ceee6081909eff87c1cada02e7","toolName":"read","content":[{"type":"text","text":"\n// Local alias so the stage constants read namespaced at the use sites without\n// a wildcard import.\nuse crate::seedproofx as spt_seedproof_stage;\nuse crate::seedproofx::DialStage;\n\nfn register_conn(\n    shared: &Arc<NetShared>,\n    conn: Connection,\n    proven_subnets: HashSet<String>,\n) -> (u64, String) {\n    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())\n        .expect(\"an iroh EndpointId is always a valid ed25519 public key\")\n        .to_hex();\n    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n    shared.conns.lock().unwrap().insert(\n        id,\n        ConnEntry {\n            kind: ConnKind::Quic(conn.clone()),\n            remote_id_hex: remote_id_hex.clone(),\n            proven_subnets,\n        },\n    );\n\n    // Presence: announce the row BEFORE spawning the closed-watcher, so the\n    // `connected` event can never race behind its own `disconnected` (D4c). The\n    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\n    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\n    // did (id-only / path-less → `null`, nothing worth seeding).\n    let observed = observed_addr_json(&conn);\n    shared\n        .presence\n        .lock()\n        .unwrap()\n        .append_connected(id, &remote_id_hex, observed);\n\n    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\n    // close, conn loss, or our own endpoint close), removes the row — conns\n    // finally LEAVE the table — then appends the `disconnected` event (row\n    // gone before the event is observable).\n    // [impl->REQ-EP-4]\n    let sh_close = Arc::clone(shared);\n    let conn_close = conn.clone();\n    let hex_close = remote_id_hex.clone();\n    tokio::spawn(async move {\n        conn_close.closed().await;\n        sh_close.conns.lock().unwrap().remove(&id);\n        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n        // — nothing can ever be served on them again, and leaving them makes\n        // every future dispatcher generation replay them (the control-steal\n        // hazard, KNOWN-HAZARDS 7.41). Presence keeps its own log; nothing\n        // else reads a dead conn's stream ring.\n        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n        sh_close\n            .streams\n            .lock()\n            .unwrap()\n            .retain(|_, e| e.conn_id != id);\n        sh_close\n            .presence\n            .lock()\n            .unwrap()\n            .append(PRESENCE_DISCONNECTED, id, &hex_close);\n    });\n\n    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\n    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes\n    // visible here on its first write.) Ends when the connection does.\n    let sh = Arc::clone(shared);\n    let hex = remote_id_hex.clone();\n    tokio::spawn(async move {\n…\n    });\n…\n}\n…\n    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n        let entry = {\n…\n            Arc::clone(streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?)\n        };\n        // Serve-lease gate (ADR-0038 Amendment fix 4): a stream whose\n        // subscriber POISONED has its lease canceled — refusing the send here\n        // is what terminates the paired serve/wire worker at its next\n        // forward instead of leaving an orphan forwarding leg running.\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        if entry.log.lock().unwrap().lease.is_canceled() {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id} lease canceled (subscriber poisoned) — serve leg terminated\"\n            )));\n        }\n        let bytes = bytes.to_vec();\n        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a\n        // black-holed QUIC stream blocks on flow-control the dead peer never grants.\n        // Loopback (in-process) never hangs, so the bound is a no-op for it.\n        self.bounded_block_on(async move {\n            let mut send = entry.send.lock().await;\n            if !bytes.is_empty() {\n                send.write_all(&bytes).await?;\n            }\n            if finish {\n                send.finish().await?;\n            }\n            Ok(())\n        })\n    }\n\n    /// A stream's serve lease (ADR-0038 Amendment fix 4) — the cancellation\n    /// handle the DAEMON-LIFECYCLE C2 work consumes. `None` = no such stream.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .get(&stream_id)\n            .map(|e| Arc::clone(&e.log.lock().unwrap().lease))\n    }\n\n    /// Every held stream's info — the query a re-attaching brain uses to find\n    /// streams (including peer-initiated ones that arrived while it was down).\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n    /// enumeration is exactly what a future dispatcher generation would replay.\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .iter()\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n            .map(|(id, e)| {\n                let log = e.log.lock().unwrap();\n                NetStreamInfo {\n                    stream_id: *id,\n                    conn_id: e.conn_id,\n                    remote_id_hex: e.remote_id_hex.clone(),\n                    next_seq: log.next_seq,\n                    finished: log.finished,\n                    initiated_locally: e.initiated_locally,\n                    floor_seq: log.floor_seq(),\n                }\n            })\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, independent of the evictable data ring. Outer\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\n…\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\n…\n        let (conn_id, _) = self.dial_loopback();\n        let before = self.inbound_rows_on(conn_id);\n        let owner_stream = self.open_stream(conn_id)?;\n        let shell_stream = self\n            .inbound_rows_on(conn_id)\n            .difference(&before)\n            .copied()\n            .next()\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\n        Ok((owner_stream, shell_stream))\n    }\n\n    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`\n    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).\n    // [impl->REQ-DAEMON-2]\n    pub(crate) fn subscribe_stream(\n        &self,\n        stream_id: u64,\n        sub: SharedSend,\n        from_seq: u64,\n    ) -> io::Result<()> {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            let e = streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?;\n            Arc::clone(&e.log)\n        };\n        // Install the seat WITH its replay snapshot in one atomic critical\n        // section (ADR-0038 Amendment fix 3, gate follow-up): the snapshot\n        // handoff is a memory move; the seat's writer thread performs every\n        // conn write outside the mutex, replay first, live after — so a\n        // producer appending concurrently can never slot a live frame into\n        // the middle of the replay (the \"got seq 134 want 6\" re-serve red).\n        // A replay write failure poisons the seat writer-side; the next\n        // producer enqueue halt-and-removes it (the T1 discipline).\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\n        Ok(())\n    }\n\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\n    /// disconnect cleanup; the stream + its ring persist).\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n        };\n        if let Some(log) = log {\n            log.lock().unwrap().detach_if(sub);\n        }\n    }\n"}],"details":{"displayContent":{"text":"\n// Local alias so the stage constants read namespaced at the use sites without\n// a wildcard import.\nuse crate::seedproofx as spt_seedproof_stage;\nuse crate::seedproofx::DialStage;\n\nfn register_conn(\n    shared: &Arc<NetShared>,\n    conn: Connection,\n    proven_subnets: HashSet<String>,\n) -> (u64, String) {\n    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())\n        .expect(\"an iroh EndpointId is always a valid ed25519 public key\")\n        .to_hex();\n    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n    shared.conns.lock().unwrap().insert(\n        id,\n        ConnEntry {\n            kind: ConnKind::Quic(conn.clone()),\n            remote_id_hex: remote_id_hex.clone(),\n            proven_subnets,\n        },\n    );\n\n    // Presence: announce the row BEFORE spawning the closed-watcher, so the\n    // `connected` event can never race behind its own `disconnected` (D4c). The\n    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\n    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\n    // did (id-only / path-less → `null`, nothing worth seeding).\n    let observed = observed_addr_json(&conn);\n    shared\n        .presence\n        .lock()\n        .unwrap()\n        .append_connected(id, &remote_id_hex, observed);\n\n    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\n    // close, conn loss, or our own endpoint close), removes the row — conns\n    // finally LEAVE the table — then appends the `disconnected` event (row\n    // gone before the event is observable).\n    // [impl->REQ-EP-4]\n    let sh_close = Arc::clone(shared);\n    let conn_close = conn.clone();\n    let hex_close = remote_id_hex.clone();\n    tokio::spawn(async move {\n        conn_close.closed().await;\n        sh_close.conns.lock().unwrap().remove(&id);\n        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n        // — nothing can ever be served on them again, and leaving them makes\n        // every future dispatcher generation replay them (the control-steal\n        // hazard, KNOWN-HAZARDS 7.41). Presence keeps its own log; nothing\n        // else reads a dead conn's stream ring.\n        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n        sh_close\n            .streams\n            .lock()\n            .unwrap()\n            .retain(|_, e| e.conn_id != id);\n        sh_close\n            .presence\n            .lock()\n            .unwrap()\n            .append(PRESENCE_DISCONNECTED, id, &hex_close);\n    });\n\n    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\n    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes\n    // visible here on its first write.) Ends when the connection does.\n    let sh = Arc::clone(shared);\n    let hex = remote_id_hex.clone();\n    tokio::spawn(async move {\n…\n    });\n…\n}\n…\n    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n        let entry = {\n…\n            Arc::clone(streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?)\n        };\n        // Serve-lease gate (ADR-0038 Amendment fix 4): a stream whose\n        // subscriber POISONED has its lease canceled — refusing the send here\n        // is what terminates the paired serve/wire worker at its next\n        // forward instead of leaving an orphan forwarding leg running.\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        if entry.log.lock().unwrap().lease.is_canceled() {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id} lease canceled (subscriber poisoned) — serve leg terminated\"\n            )));\n        }\n        let bytes = bytes.to_vec();\n        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a\n        // black-holed QUIC stream blocks on flow-control the dead peer never grants.\n        // Loopback (in-process) never hangs, so the bound is a no-op for it.\n        self.bounded_block_on(async move {\n            let mut send = entry.send.lock().await;\n            if !bytes.is_empty() {\n                send.write_all(&bytes).await?;\n            }\n            if finish {\n                send.finish().await?;\n            }\n            Ok(())\n        })\n    }\n\n    /// A stream's serve lease (ADR-0038 Amendment fix 4) — the cancellation\n    /// handle the DAEMON-LIFECYCLE C2 work consumes. `None` = no such stream.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .get(&stream_id)\n            .map(|e| Arc::clone(&e.log.lock().unwrap().lease))\n    }\n\n    /// Every held stream's info — the query a re-attaching brain uses to find\n    /// streams (including peer-initiated ones that arrived while it was down).\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n    /// enumeration is exactly what a future dispatcher generation would replay.\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .iter()\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n            .map(|(id, e)| {\n                let log = e.log.lock().unwrap();\n                NetStreamInfo {\n                    stream_id: *id,\n                    conn_id: e.conn_id,\n                    remote_id_hex: e.remote_id_hex.clone(),\n                    next_seq: log.next_seq,\n                    finished: log.finished,\n                    initiated_locally: e.initiated_locally,\n                    floor_seq: log.floor_seq(),\n                }\n            })\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, independent of the evictable data ring. Outer\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\n…\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\n…\n        let (conn_id, _) = self.dial_loopback();\n        let before = self.inbound_rows_on(conn_id);\n        let owner_stream = self.open_stream(conn_id)?;\n        let shell_stream = self\n            .inbound_rows_on(conn_id)\n            .difference(&before)\n            .copied()\n            .next()\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\n        Ok((owner_stream, shell_stream))\n    }\n\n    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`\n    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).\n    // [impl->REQ-DAEMON-2]\n    pub(crate) fn subscribe_stream(\n        &self,\n        stream_id: u64,\n        sub: SharedSend,\n        from_seq: u64,\n    ) -> io::Result<()> {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            let e = streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?;\n            Arc::clone(&e.log)\n        };\n        // Install the seat WITH its replay snapshot in one atomic critical\n        // section (ADR-0038 Amendment fix 3, gate follow-up): the snapshot\n        // handoff is a memory move; the seat's writer thread performs every\n        // conn write outside the mutex, replay first, live after — so a\n        // producer appending concurrently can never slot a live frame into\n        // the middle of the replay (the \"got seq 134 want 6\" re-serve red).\n        // A replay write failure poisons the seat writer-side; the next\n        // producer enqueue halt-and-removes it (the T1 discipline).\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\n        Ok(())\n    }\n\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\n    /// disconnect cleanup; the stream + its ring persist).\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n        };\n        if let Some(log) = log {\n            log.lock().unwrap().detach_if(sub);\n        }\n    }\n","startLine":910,"lineNumbers":[910,911,912,913,914,915,916,917,918,919,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,null,991,null,994,null,1755,1756,null,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,null,1933,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,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"}}},"isError":false,"timestamp":1784271567081}}
{"type":"message","id":"776161b7","parentId":"26edd49b","timestamp":"2026-07-17T06:59:27.082Z","message":{"role":"toolResult","toolCallId":"call_nwJJqsISTr9220pJSyBK3wT3|fc_089095e18118ee7d016a59d2ceee988190bba4aa42200d0b5c","toolName":"read","content":[{"type":"text","text":"enum BrainConn {\n    /// The ordinary blocking carrier — every non-pump brain.\n    Whole(Stream),\n    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over\n    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).\n    Split {\n        send: SendHalf,\n        frames: Receiver<io::Result<Envelope>>,\n        /// The reader-thread handle — held so the thread is owned, not detached\n        /// silently. On a supervised restart the whole [`Brain`] drops; this\n        /// handle drops with it (the thread, blocked in `read_frame`, is\n        /// abandoned and exits when the old conn closes — KH 7.6).\n        _reader: JoinHandle<()>,\n    },\n}\n\nimpl BrainConn {\n    /// Split a connected+handshaked stream into the pump carrier: spawn the\n    /// `pump-ipc-reader` thread (blocking [`read_frame`] → channel) and keep the\n    /// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)\n    /// reader is diagnosable in the field (KH 7.6).\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn split_with_reader(stream: Stream) -> BrainConn {\n        let (mut recv, send) = stream.split();\n        let (tx, frames) = mpsc::channel();\n        let reader = thread::Builder::new()\n            .name(\"pump-ipc-reader\".to_string())\n            .spawn(move || {\n                eprintln!(\"PUMP_IPC_READER: spawned\");\n                loop {\n                    let framed = read_frame(&mut recv);\n                    let was_err = framed.is_err();\n                    // Receiver gone (Brain dropped) → stop reading.\n                    if tx.send(framed).is_err() {\n                        break;\n                    }\n                    // A transport error (conn closed / hung up) is terminal —\n                    // forward it once (above), then exit so the thread never\n                    // spins on a dead half.\n                    if was_err {\n                        break;\n                    }\n                }\n                eprintln!(\"PUMP_IPC_READER: exited\");\n            })\n            .expect(\"spawn pump-ipc-reader thread\");\n        BrainConn::Split {\n            send,\n            frames,\n            _reader: reader,\n        }\n    }\n}\n\n/// A live brain attached to a broker.\npub struct Brain {\n    conn: BrainConn,\n    generation: u64,\n    gen_start_ms: u64,\n    session_id: Option<u64>,\n    /// The next output sequence still needed (resume cursor / dedup floor).\n    next_seq: u64,\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// Durability across a handoff is the logic layer's job (a re-attaching\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n    net_cursors: std::collections::HashMap<u64, u64>,\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// `None` until subscribed (or the first event arrives).\n    presence_cursor: Option<u64>,\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n    /// continuity state a cold-started brain rebuilds from the broker via\n    /// [`Brain::resume_sessions`], replacing the retired single-session\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\n    /// never a real gap). A session **absent** here uses the strict single-session\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\n    session_cursors: std::collections::HashMap<u64, u64>,\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n    /// into the reader-thread form so the deadline can be honored over a channel\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\n    io_timeout: Option<Duration>,\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\n    /// replay + strict reject-gap, entirely unchanged.\n    baseline_next_output: bool,\n}\n\nimpl Brain {\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::Whole(conn),\n            generation: 0,\n            gen_start_ms: now_ms,\n            session_id: None,\n            next_seq: 0,\n            net_cursors: std::collections::HashMap::new(),\n            presence_cursor: None,\n            session_cursors: std::collections::HashMap::new(),\n            io_timeout: None,\n            baseline_next_output: false,\n        })\n    }\n\n    /// 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…\n    pub fn net_stream_send(\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n…\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n    /// The dispatcher calls this when a serve loop runs to completion, so no\n    /// future dispatcher generation rediscovers and replays the stream\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n    /// broker to cycle onto the new binary).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n        self.send(\n            KIND_NET_STREAM_RETIRE,\n            serde_json::to_value(NetStreamRetireReq { stream_id })\n                .expect(\"NetStreamRetireReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(reply.removed);\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, read instead of peeking ring seq 0 (which a\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\n    /// stream but no opener is pinned (no newline yet / capture gave up).\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n    /// the dispatcher falls back to the ring peek).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n        self.send(\n            KIND_NET_STREAM_OPENER,\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\n                .expect(\"NetStreamOpenerReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return match reply.opener_b64 {\n                        Some(b64) => decode_bytes(&b64)\n                            .map(Some)\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\n                        None => Ok(None),\n                    };\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\n…\n    fn subscribe_with(\n…\n    ) -> io::Result<()> {\n…\n        self.send(\n…\n            serde_json::to_value(SubscribeReq {\n                session_id,\n                from_seq,\n                intent,\n                by,\n                gen,\n            })\n            .expect(\"SubscribeReq serializes\"),\n        )\n    }\n\n    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n        let env = Envelope::new(kind, payload);\n        match &mut self.conn {\n            BrainConn::Whole(stream) => write_frame(stream, &env),\n            BrainConn::Split { send, .. } => write_frame(send, &env),\n        }\n    }\n\n    /// Read one framed [`Envelope`], bounded by an optional wall-clock\n    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):\n    ///\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\n    ///   `io_timeout`, so callers always pass `None` here).\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n        match &mut self.conn {\n            BrainConn::Whole(stream) => read_frame(stream),\n            BrainConn::Split { frames, .. } => match deadline {\n                Some(dl) => {\n…\n                }\n…\n            },\n        }\n    }\n…\n}"}],"details":{"displayContent":{"text":"enum BrainConn {\n    /// The ordinary blocking carrier — every non-pump brain.\n    Whole(Stream),\n    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over\n    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).\n    Split {\n        send: SendHalf,\n        frames: Receiver<io::Result<Envelope>>,\n        /// The reader-thread handle — held so the thread is owned, not detached\n        /// silently. On a supervised restart the whole [`Brain`] drops; this\n        /// handle drops with it (the thread, blocked in `read_frame`, is\n        /// abandoned and exits when the old conn closes — KH 7.6).\n        _reader: JoinHandle<()>,\n    },\n}\n\nimpl BrainConn {\n    /// Split a connected+handshaked stream into the pump carrier: spawn the\n    /// `pump-ipc-reader` thread (blocking [`read_frame`] → channel) and keep the\n    /// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)\n    /// reader is diagnosable in the field (KH 7.6).\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn split_with_reader(stream: Stream) -> BrainConn {\n        let (mut recv, send) = stream.split();\n        let (tx, frames) = mpsc::channel();\n        let reader = thread::Builder::new()\n            .name(\"pump-ipc-reader\".to_string())\n            .spawn(move || {\n                eprintln!(\"PUMP_IPC_READER: spawned\");\n                loop {\n                    let framed = read_frame(&mut recv);\n                    let was_err = framed.is_err();\n                    // Receiver gone (Brain dropped) → stop reading.\n                    if tx.send(framed).is_err() {\n                        break;\n                    }\n                    // A transport error (conn closed / hung up) is terminal —\n                    // forward it once (above), then exit so the thread never\n                    // spins on a dead half.\n                    if was_err {\n                        break;\n                    }\n                }\n                eprintln!(\"PUMP_IPC_READER: exited\");\n            })\n            .expect(\"spawn pump-ipc-reader thread\");\n        BrainConn::Split {\n            send,\n            frames,\n            _reader: reader,\n        }\n    }\n}\n\n/// A live brain attached to a broker.\npub struct Brain {\n    conn: BrainConn,\n    generation: u64,\n    gen_start_ms: u64,\n    session_id: Option<u64>,\n    /// The next output sequence still needed (resume cursor / dedup floor).\n    next_seq: u64,\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// Durability across a handoff is the logic layer's job (a re-attaching\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n    net_cursors: std::collections::HashMap<u64, u64>,\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// `None` until subscribed (or the first event arrives).\n    presence_cursor: Option<u64>,\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n    /// continuity state a cold-started brain rebuilds from the broker via\n    /// [`Brain::resume_sessions`], replacing the retired single-session\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\n    /// never a real gap). A session **absent** here uses the strict single-session\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\n    session_cursors: std::collections::HashMap<u64, u64>,\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n    /// into the reader-thread form so the deadline can be honored over a channel\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\n    io_timeout: Option<Duration>,\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\n    /// replay + strict reject-gap, entirely unchanged.\n    baseline_next_output: bool,\n}\n\nimpl Brain {\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::Whole(conn),\n            generation: 0,\n            gen_start_ms: now_ms,\n            session_id: None,\n            next_seq: 0,\n            net_cursors: std::collections::HashMap::new(),\n            presence_cursor: None,\n            session_cursors: std::collections::HashMap::new(),\n            io_timeout: None,\n            baseline_next_output: false,\n        })\n    }\n\n    /// 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…\n    pub fn net_stream_send(\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n…\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n    /// The dispatcher calls this when a serve loop runs to completion, so no\n    /// future dispatcher generation rediscovers and replays the stream\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n    /// broker to cycle onto the new binary).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n        self.send(\n            KIND_NET_STREAM_RETIRE,\n            serde_json::to_value(NetStreamRetireReq { stream_id })\n                .expect(\"NetStreamRetireReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(reply.removed);\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, read instead of peeking ring seq 0 (which a\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\n    /// stream but no opener is pinned (no newline yet / capture gave up).\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n    /// the dispatcher falls back to the ring peek).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n        self.send(\n            KIND_NET_STREAM_OPENER,\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\n                .expect(\"NetStreamOpenerReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return match reply.opener_b64 {\n                        Some(b64) => decode_bytes(&b64)\n                            .map(Some)\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\n                        None => Ok(None),\n                    };\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\n…\n    fn subscribe_with(\n…\n    ) -> io::Result<()> {\n…\n        self.send(\n…\n            serde_json::to_value(SubscribeReq {\n                session_id,\n                from_seq,\n                intent,\n                by,\n                gen,\n            })\n            .expect(\"SubscribeReq serializes\"),\n        )\n    }\n\n    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n        let env = Envelope::new(kind, payload);\n        match &mut self.conn {\n            BrainConn::Whole(stream) => write_frame(stream, &env),\n            BrainConn::Split { send, .. } => write_frame(send, &env),\n        }\n    }\n\n    /// Read one framed [`Envelope`], bounded by an optional wall-clock\n    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):\n    ///\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\n    ///   `io_timeout`, so callers always pass `None` here).\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n        match &mut self.conn {\n            BrainConn::Whole(stream) => read_frame(stream),\n            BrainConn::Split { frames, .. } => match deadline {\n                Some(dl) => {\n…\n                }\n…\n            },\n        }\n    }\n…\n}","startLine":209,"lineNumbers":[209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,null,1402,null,1408,null,1425,1426,null,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,1696,null,1703,null,1722,null,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,null,1778,null,1785,1786,1787,null,1793]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"}}},"isError":false,"timestamp":1784271567081}}
{"type":"message","id":"2b0cc334","parentId":"776161b7","timestamp":"2026-07-17T06:59:27.093Z","message":{"role":"toolResult","toolCallId":"call_yBDl3eCun8QpzM3VumefUs7z|fc_089095e18118ee7d016a59d2ceee4c819090ce3e4e4c30f54c","toolName":"read","content":[{"type":"text","text":"struct Gate {\n…\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    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n    id: u64,\n    /// Accreted attribution facts (subscriber role, endpoint/session where\n    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\n    label: Mutex<String>,\n    /// Latch: the once-per-conn `write-start` lifecycle record fired.\n    first_write_logged: AtomicBool,\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    /// The attribution snapshot every lifecycle record carries: stable conn id,\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    fn attribution(&self) -> String {\n        let label = recover(&self.label);\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\n    }\n\n    /// Render the once-per-conn retirement record — the F-039 token split\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n    /// already-dead counterpart, the offline-peer dial churn) emits the\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n    /// SAME attribution fields — quieter class, never thinner (the retired\n    /// class is what discriminates fresh-carrier churn from interleave\n    /// artifact in the field). Behavior is identical either way: poison,\n    /// abort, retire, never reuse.\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\n        if timed_out {\n            format!(\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n                self.attribution(),\n                self.bound.as_millis()\n            )\n        } else {\n            let cause = match cause {\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\n                None => \"write failed\".to_string(),\n            };\n            format!(\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n                 broker conn poisoned + retired, never reused (same retire behavior \\\n                 as POISONED — only the failure class differs) \\\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n                self.attribution()\n            )\n        }\n    }\n\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\n    /// timeout/cancel leg is the retirement record itself.\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    fn lifecycle(&self, event: &str, extra: &str) {\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\n        eprintln!(\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n            self.attribution()\n        );\n    }\n\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\n    /// the halves share one OS object) and latch the poison. Idempotent and\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\n    /// no-op, and re-cancel closes the race where a write was issued a beat\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\n    /// `cause` (the organic caller's write error) rides the retirement record\n    /// when the retirement is NOT deadline-driven.\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\n            // First poison of this conn: loud, once. The token is class-split\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n            eprintln!(\n                \"{}\",\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\n            );\n        }\n        #[cfg(windows)]\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\n        unsafe {\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n        }\n        #[cfg(unix)]\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\n        // every future op — on Unix this one call IS the whole teardown.\n        unsafe {\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\n        }\n        // Wake gate-waiters so they observe the poison instead of sleeping\n        // out their full deadline, and the watchdog so it re-evaluates.\n        self.gate_cv.notify_all();\n        self.dog_cv.notify_all();\n    }\n\n    /// Sever the physical connection (exactly once), AFTER the canceled op\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\n    /// client off (its next access fails), makes a re-issued server-side\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\n    // On Unix the cfg(windows) teardown strips out and the idempotence\n    // guard's early `return` becomes the function tail — needless_return\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\n    #[cfg_attr(unix, allow(clippy::needless_return))]\n    fn sever(&self) {\n        if self.severed.swap(true, Ordering::AcqRel) {\n            return;\n        }\n        #[cfg(windows)]\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\n        unsafe {\n            let _ = win::DisconnectNamedPipe(self.raw);\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n        }\n    }\n\n    /// The watchdog body: park until a write is in flight, then sleep to its\n    /// absolute deadline. If the op is still in flight at the deadline,\n    /// poison + cancel, re-canceling periodically until the op reports\n    /// completion (the writer clears `inflight` the instant `write_frame`\n    /// returns — that return IS the buffer-release handshake), then sever.\n    fn watchdog(&self) {\n        let mut d = recover(&self.dog);\n        loop {\n            // Park until armed (or shutdown).\n            let (op, deadline) = loop {\n                if d.shutdown {\n                    return;\n                }\n                if let Some(armed) = d.inflight {\n                    break armed;\n                }\n                d = self\n                    .dog_cv\n                    .wait(d)\n                    .unwrap_or_else(|p| p.into_inner());\n            };\n            // Sleep toward the deadline while THIS op stays in flight.\n            let fired = loop {\n                if d.shutdown {\n                    return;\n                }\n                match d.inflight {\n                    Some((cur, _)) if cur == op => {}\n                    // Completed (or a newer op armed) before the deadline.\n                    _ => break false,\n                }\n                let now = Instant::now();\n                if now >= deadline {\n                    break true;\n                }\n                let (g, _) = self\n                    .dog_cv\n                    .wait_timeout(d, deadline - now)\n                    .unwrap_or_else(|p| p.into_inner());\n                d = g;\n            };\n            if !fired {\n                continue;\n            }\n            // Deadline: abort out of band, then wait for the canceled op to\n            // report completion before severing (re-cancel each slice — a\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\n            // one shot is not enough). Latch timed_out FIRST so the aborted\n            // writer observes its failure as a DEADLINE retirement (wedge),\n            // not an organic conn death.\n            drop(d);\n            self.timed_out.store(true, Ordering::Release);\n            self.poison_and_cancel(None);\n            d = recover(&self.dog);\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\n                let (g, _) = self\n                    .dog_cv\n                    .wait_timeout(d, Duration::from_millis(100))\n                    .unwrap_or_else(|p| p.into_inner());\n                d = g;\n                drop(d);\n                self.poison_and_cancel(None);\n                d = recover(&self.dog);\n            }\n            if d.shutdown {\n                return;\n            }\n            drop(d);\n            self.sever();\n            d = recover(&self.dog);\n        }\n    }\n}\n\n/// The shared, serialized, BOUNDED send side of one physical broker\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\npub(crate) struct BrokerConn {\n    inner: Arc<Inner>,\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\n    /// watchdog aborts through strictly outlives every raw use.\n    dog: Option<JoinHandle<()>>,\n}\n\nimpl BrokerConn {\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\n    /// deadline (gate-wait + OS write completion) — the broker passes its\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n        let raw = raw_of(&half);\n        let inner = Arc::new(Inner {\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\n            label: Mutex::new(String::new()),\n            first_write_logged: AtomicBool::new(false),\n            gate: Mutex::new(Gate { half: Some(half) }),\n            gate_cv: Condvar::new(),\n            dog: Mutex::new(Dog {\n                inflight: None,\n                shutdown: false,\n            }),\n            dog_cv: Condvar::new(),\n            poisoned: AtomicBool::new(false),\n            timed_out: AtomicBool::new(false),\n            severed: AtomicBool::new(false),\n            graveyard: Mutex::new(None),\n            op_seq: AtomicU64::new(0),\n            bound,\n            raw,\n        });\n        let dog_inner = Arc::clone(&inner);\n        let dog = thread::Builder::new()\n            .name(\"conn-watchdog\".into())\n            .spawn(move || dog_inner.watchdog())\n            .ok();\n        BrokerConn { inner, dog }\n    }\n\n    /// The stable per-physical-conn id — what lifecycle records key on\n    /// (`Arc::ptr_eq` identity does not survive a log line).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn id(&self) -> u64 {\n        self.inner.id\n    }\n\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\n    /// endpoint/session where known — e.g. `role=brain`,\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn describe(&self, fact: &str) {\n        let mut l = recover(&self.inner.label);\n        if l.ends_with('…') {\n            return;\n        }\n        if l.len() + fact.len() + 1 > LABEL_CAP {\n            l.push('…');\n            return;\n        }\n        if !l.is_empty() {\n            l.push(' ');\n        }\n        l.push_str(fact);\n    }\n\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\n…\n}\n\n[Showing lines 129-483 of 815. Use :484 to continue]"}],"details":{"truncation":{"content":"}\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    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\r\n    id: u64,\r\n    /// Accreted attribution facts (subscriber role, endpoint/session where\r\n    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\r\n    label: Mutex<String>,\r\n    /// Latch: the once-per-conn `write-start` lifecycle record fired.\r\n    first_write_logged: AtomicBool,\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    /// The attribution snapshot every lifecycle record carries: stable conn id,\r\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn attribution(&self) -> String {\r\n        let label = recover(&self.label);\r\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\r\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\r\n    }\r\n\r\n    /// Render the once-per-conn retirement record — the F-039 token split\r\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\r\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\r\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\r\n    /// already-dead counterpart, the offline-peer dial churn) emits the\r\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\r\n    /// SAME attribution fields — quieter class, never thinner (the retired\r\n    /// class is what discriminates fresh-carrier churn from interleave\r\n    /// artifact in the field). Behavior is identical either way: poison,\r\n    /// abort, retire, never reuse.\r\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\r\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\r\n        if timed_out {\r\n            format!(\r\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\r\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\r\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.attribution(),\r\n                self.bound.as_millis()\r\n            )\r\n        } else {\r\n            let cause = match cause {\r\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\r\n                None => \"write failed\".to_string(),\r\n            };\r\n            format!(\r\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\r\n                 broker conn poisoned + retired, never reused (same retire behavior \\\r\n                 as POISONED — only the failure class differs) \\\r\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\r\n                self.attribution()\r\n            )\r\n        }\r\n    }\r\n\r\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\r\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\r\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\r\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\r\n    /// timeout/cancel leg is the retirement record itself.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn lifecycle(&self, event: &str, extra: &str) {\r\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\r\n        eprintln!(\r\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\r\n            self.attribution()\r\n        );\r\n    }\r\n\r\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\r\n    /// the halves share one OS object) and latch the poison. Idempotent and\r\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\r\n    /// no-op, and re-cancel closes the race where a write was issued a beat\r\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\r\n    /// `cause` (the organic caller's write error) rides the retirement record\r\n    /// when the retirement is NOT deadline-driven.\r\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once. The token is class-split\r\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\r\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\r\n            eprintln!(\r\n                \"{}\",\r\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\r\n            );\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\r\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\r\n        unsafe {\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n        #[cfg(unix)]\r\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\r\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\r\n        // every future op — on Unix this one call IS the whole teardown.\r\n        unsafe {\r\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\r\n        }\r\n        // Wake gate-waiters so they observe the poison instead of sleeping\r\n        // out their full deadline, and the watchdog so it re-evaluates.\r\n        self.gate_cv.notify_all();\r\n        self.dog_cv.notify_all();\r\n    }\r\n\r\n    /// Sever the physical connection (exactly once), AFTER the canceled op\r\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\r\n    /// client off (its next access fails), makes a re-issued server-side\r\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\r\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\r\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\r\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\r\n    // On Unix the cfg(windows) teardown strips out and the idempotence\r\n    // guard's early `return` becomes the function tail — needless_return\r\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\r\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\r\n    #[cfg_attr(unix, allow(clippy::needless_return))]\r\n    fn sever(&self) {\r\n        if self.severed.swap(true, Ordering::AcqRel) {\r\n            return;\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\r\n        unsafe {\r\n            let _ = win::DisconnectNamedPipe(self.raw);\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n    }\r\n\r\n    /// The watchdog body: park until a write is in flight, then sleep to its\r\n    /// absolute deadline. If the op is still in flight at the deadline,\r\n    /// poison + cancel, re-canceling periodically until the op reports\r\n    /// completion (the writer clears `inflight` the instant `write_frame`\r\n    /// returns — that return IS the buffer-release handshake), then sever.\r\n    fn watchdog(&self) {\r\n        let mut d = recover(&self.dog);\r\n        loop {\r\n            // Park until armed (or shutdown).\r\n            let (op, deadline) = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                if let Some(armed) = d.inflight {\r\n                    break armed;\r\n                }\r\n                d = self\r\n                    .dog_cv\r\n                    .wait(d)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n            };\r\n            // Sleep toward the deadline while THIS op stays in flight.\r\n            let fired = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                match d.inflight {\r\n                    Some((cur, _)) if cur == op => {}\r\n                    // Completed (or a newer op armed) before the deadline.\r\n                    _ => break false,\r\n                }\r\n                let now = Instant::now();\r\n                if now >= deadline {\r\n                    break true;\r\n                }\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, deadline - now)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n            };\r\n            if !fired {\r\n                continue;\r\n            }\r\n            // Deadline: abort out of band, then wait for the canceled op to\r\n            // report completion before severing (re-cancel each slice — a\r\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\r\n            // one shot is not enough). Latch timed_out FIRST so the aborted\r\n            // writer observes its failure as a DEADLINE retirement (wedge),\r\n            // not an organic conn death.\r\n            drop(d);\r\n            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r","truncated":true,"truncatedBy":"lines","totalLines":687,"totalBytes":15444,"outputLines":355,"outputBytes":15444,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"struct Gate {\n…\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    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\r\n    id: u64,\r\n    /// Accreted attribution facts (subscriber role, endpoint/session where\r\n    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\r\n    label: Mutex<String>,\r\n    /// Latch: the once-per-conn `write-start` lifecycle record fired.\r\n    first_write_logged: AtomicBool,\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    /// The attribution snapshot every lifecycle record carries: stable conn id,\r\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn attribution(&self) -> String {\r\n        let label = recover(&self.label);\r\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\r\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\r\n    }\r\n\r\n    /// Render the once-per-conn retirement record — the F-039 token split\r\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\r\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\r\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\r\n    /// already-dead counterpart, the offline-peer dial churn) emits the\r\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\r\n    /// SAME attribution fields — quieter class, never thinner (the retired\r\n    /// class is what discriminates fresh-carrier churn from interleave\r\n    /// artifact in the field). Behavior is identical either way: poison,\r\n    /// abort, retire, never reuse.\r\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\r\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\r\n        if timed_out {\r\n            format!(\r\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\r\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\r\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.attribution(),\r\n                self.bound.as_millis()\r\n            )\r\n        } else {\r\n            let cause = match cause {\r\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\r\n                None => \"write failed\".to_string(),\r\n            };\r\n            format!(\r\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\r\n                 broker conn poisoned + retired, never reused (same retire behavior \\\r\n                 as POISONED — only the failure class differs) \\\r\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\r\n                self.attribution()\r\n            )\r\n        }\r\n    }\r\n\r\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\r\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\r\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\r\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\r\n    /// timeout/cancel leg is the retirement record itself.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn lifecycle(&self, event: &str, extra: &str) {\r\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\r\n        eprintln!(\r\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\r\n            self.attribution()\r\n        );\r\n    }\r\n\r\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\r\n    /// the halves share one OS object) and latch the poison. Idempotent and\r\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\r\n    /// no-op, and re-cancel closes the race where a write was issued a beat\r\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\r\n    /// `cause` (the organic caller's write error) rides the retirement record\r\n    /// when the retirement is NOT deadline-driven.\r\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once. The token is class-split\r\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\r\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\r\n            eprintln!(\r\n                \"{}\",\r\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\r\n            );\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\r\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\r\n        unsafe {\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n        #[cfg(unix)]\r\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\r\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\r\n        // every future op — on Unix this one call IS the whole teardown.\r\n        unsafe {\r\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\r\n        }\r\n        // Wake gate-waiters so they observe the poison instead of sleeping\r\n        // out their full deadline, and the watchdog so it re-evaluates.\r\n        self.gate_cv.notify_all();\r\n        self.dog_cv.notify_all();\r\n    }\r\n\r\n    /// Sever the physical connection (exactly once), AFTER the canceled op\r\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\r\n    /// client off (its next access fails), makes a re-issued server-side\r\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\r\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\r\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\r\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\r\n    // On Unix the cfg(windows) teardown strips out and the idempotence\r\n    // guard's early `return` becomes the function tail — needless_return\r\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\r\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\r\n    #[cfg_attr(unix, allow(clippy::needless_return))]\r\n    fn sever(&self) {\r\n        if self.severed.swap(true, Ordering::AcqRel) {\r\n            return;\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\r\n        unsafe {\r\n            let _ = win::DisconnectNamedPipe(self.raw);\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n    }\r\n\r\n    /// The watchdog body: park until a write is in flight, then sleep to its\r\n    /// absolute deadline. If the op is still in flight at the deadline,\r\n    /// poison + cancel, re-canceling periodically until the op reports\r\n    /// completion (the writer clears `inflight` the instant `write_frame`\r\n    /// returns — that return IS the buffer-release handshake), then sever.\r\n    fn watchdog(&self) {\r\n        let mut d = recover(&self.dog);\r\n        loop {\r\n            // Park until armed (or shutdown).\r\n            let (op, deadline) = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                if let Some(armed) = d.inflight {\r\n                    break armed;\r\n                }\r\n                d = self\r\n                    .dog_cv\r\n                    .wait(d)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n            };\r\n            // Sleep toward the deadline while THIS op stays in flight.\r\n            let fired = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                match d.inflight {\r\n                    Some((cur, _)) if cur == op => {}\r\n                    // Completed (or a newer op armed) before the deadline.\r\n                    _ => break false,\r\n                }\r\n                let now = Instant::now();\r\n                if now >= deadline {\r\n                    break true;\r\n                }\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, deadline - now)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n            };\r\n            if !fired {\r\n                continue;\r\n            }\r\n            // Deadline: abort out of band, then wait for the canceled op to\r\n            // report completion before severing (re-cancel each slice — a\r\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\r\n            // one shot is not enough). Latch timed_out FIRST so the aborted\r\n            // writer observes its failure as a DEADLINE retirement (wedge),\r\n            // not an organic conn death.\r\n            drop(d);\r\n            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r\n…\n}","startLine":127,"lineNumbers":[127,null,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,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,null,602]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":815,"totalBytes":15444,"outputLines":355,"outputBytes":15444,"shownRange":{"start":129,"end":483},"nextOffset":484}}},"isError":false,"timestamp":1784271567093}}
{"type":"message","id":"895fff8d","parentId":"2b0cc334","timestamp":"2026-07-17T06:59:27.112Z","message":{"role":"toolResult","toolCallId":"call_WTWmUh0B8LnRXncf7w5GUju4|fc_089095e18118ee7d016a59d2ceeea48190abd9aab0fdfb82bf","toolName":"read","content":[{"type":"text","text":"\n    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n    // — a persistently-failing stream retries exactly its bounded budget and\n    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n    // abandoned the stream on failure #1 forever.\n    #[test]\n    fn transient_failure_requeues_bounded_and_terminal_never_reenters() {\n        let now = Instant::now();\n\n        // Fresh row → claimable with zero prior failures.\n        assert_eq!(should_claim(None, now), Some(0));\n        // In flight / terminal → never.\n        assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);\n        assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);\n\n        // Failure #1 releases with backoff — NOT claimable before next_at,\n        // claimable at/after it, carrying the attempt count.\n        let failed = DispatchOutcome::Failed(\"io\".into());\n        let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);\n        let ClaimState::Retry { attempts: 1, next_at } = s1 else {\n            panic!(\"failure #1 must requeue, got {s1:?}\");\n        };\n        assert_eq!(next_at, now + retry_backoff(1));\n        assert_eq!(should_claim(Some(&s1), now), None, \"backoff holds the claim\");\n        assert_eq!(should_claim(Some(&s1), next_at), Some(1), \"due → reclaimable\");\n\n        // Backoff doubles per attempt.\n        assert_eq!(retry_backoff(2), retry_backoff(1) * 2);\n\n        // The budget is BOUNDED: the MAX'th failure goes terminal, silent\n        // forever after (no hot loop under a persistently-failing stream).\n        let s2 = outcome_transition(&failed, 1, now, Duration::ZERO);\n        assert!(matches!(s2, ClaimState::Retry { attempts: 2, .. }));\n        let s3 = outcome_transition(&failed, 2, now, Duration::ZERO);\n        assert_eq!(s3, ClaimState::Terminal, \"attempt {MAX_DISPATCH_ATTEMPTS} exhausts the budget\");\n\n        // Terminal CLASSIFICATION outcomes are terminal immediately — a\n        // served exchange and an unclassifiable stream never retry.\n        assert_eq!(outcome_transition(&DispatchOutcome::Served(\"ok\".into()), 0, now, Duration::ZERO), ClaimState::Terminal);\n        assert_eq!(outcome_transition(&DispatchOutcome::Unknown, 0, now, Duration::ZERO), ClaimState::Terminal);\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:\n    // exactly the three strings the nethost subscriber discipline emits are\n    // SeatBlocked (they pace on the global breaker, budget-free); everything\n    // else — transport timeouts, EOF, opener-not-pinned, store errors — keeps\n    // the bounded Transient budget. The strings are load-bearing wire facts:\n    // begin_attach's \"subscriber busy\", subscribe_stream's \"replay halted\",\n    // send_stream's \"lease canceled\".\n    #[test]\n    fn classify_failure_seat_blocked_is_exactly_the_three_discipline_strings() {\n        for seat_blocked in [\n            \"stream 7058 subscriber busy: prior subscriber still draining\",\n            \"presence subscriber busy: prior subscriber still draining\",\n            \"stream 7058 replay halted: subscriber write failed\",\n            \"stream 7058 lease canceled (subscriber poisoned) — serve leg terminated\",\n        ] {\n            assert_eq!(\n                classify_failure(seat_blocked),\n                FailClass::SeatBlocked,\n                \"for {seat_blocked:?}\"\n            );\n        }\n        for transient in [\n            \"brain IPC read deadline elapsed\",\n            \"stream 7058: opener not pinned yet\",\n            \"unexpected EOF reading broker frame\",\n            \"no such session 12\",\n            \"\",\n        ] {\n            assert_eq!(\n                classify_failure(transient),\n                FailClass::Transient,\n                \"for {transient:?}\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] fallback narrowing (Amendment\n    // fix 1): ONLY the old broker's literal `unknown command kind` answer may\n    // fall back to the ring peek. Transport timeout / EOF / poison — the\n    // pre-Amendment broad Err(_) classes that manufactured throwaway replay\n    // subscribers against wedged conns — must NOT pass this gate.\n    #[test]\n    fn only_the_old_broker_unknown_verb_answer_falls_back_to_the_peek() {\n        assert!(is_unsupported_verb(&io::Error::other(\n            \"unknown command kind \\\"net-stream-opener\\\"\"\n        )));\n        for e in [\n            io::Error::new(io::ErrorKind::TimedOut, \"write deadline elapsed\"),\n            io::Error::new(io::ErrorKind::UnexpectedEof, \"broker EOF\"),\n            io::Error::other(\"conn poisoned\"),\n            io::Error::other(\"stream 7: opener not pinned yet\"),\n        ] {\n            assert!(\n                !is_unsupported_verb(&e),\n                \"{e}: must be Failed/requeue, never a second replay subscriber\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the global circuit breaker's\n    // trip/gate/reset contract (Amendment fix 5): trip opens a doubling window\n    // (base 2s, cap 30s), open() gates claiming inside the window and releases\n    // at its end, reset (a Served outcome) closes it AND restores the base\n    // window — real progress forgives the backoff history.\n    #[test]\n    fn breaker_trips_with_doubling_capped_window_and_resets_on_progress() {\n        let now = Instant::now();\n        let mut b = CircuitBreaker::default();\n        assert!(!b.open(now), \"a fresh breaker is closed\");\n\n        // Trip #1: base window; open inside, closed at its end.\n        assert_eq!(b.trip(now), BREAKER_BASE);\n        assert!(b.open(now));\n        assert!(b.open(now + BREAKER_BASE - Duration::from_millis(1)));\n        assert!(!b.open(now + BREAKER_BASE), \"window elapses -> claiming resumes\");\n\n        // Consecutive trips double... (2s, 4s, 8s, 16s, 32s->30s cap)\n        assert_eq!(b.trip(now), BREAKER_BASE * 2);\n        assert_eq!(b.trip(now), BREAKER_BASE * 4);\n        assert_eq!(b.trip(now), BREAKER_BASE * 8);\n        // ...and cap at BREAKER_CAP, never wider.\n        assert_eq!(b.trip(now), BREAKER_CAP);\n        assert_eq!(b.trip(now), BREAKER_CAP);\n\n        // Reset (Served): closed now, and the NEXT trip is back to base.\n        b.reset();\n        assert!(!b.open(now));\n        assert_eq!(b.trip(now), BREAKER_BASE, \"reset restores the base window\");\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] a SeatBlocked failure requeues\n    // WITHOUT burning the bounded transient budget, paced by the breaker\n    // window (T5's not-abandonment discriminator): the wedged seat clears at\n    // most one 15s bounded write later, and three fast transient retries\n    // would have burned the claim Terminal before that — the v0.33\n    // abandonment this must never regress to. A Transient at the same\n    // attempt count still burns budget (D3 unchanged).\n    #[test]\n    fn seat_blocked_requeues_budget_free_paced_by_the_breaker_window() {\n        let now = Instant::now();\n        let window = Duration::from_secs(4);\n        let blocked =\n            DispatchOutcome::Failed(\"stream 9 subscriber busy: prior subscriber still draining\".into());\n\n        // Even AT the terminal edge (attempts = MAX-1), SeatBlocked keeps the\n        // attempt count and paces on the breaker window instead of Terminal.\n        let s = outcome_transition(&blocked, MAX_DISPATCH_ATTEMPTS - 1, now, window);\n        assert_eq!(\n            s,\n            ClaimState::Retry {\n                attempts: MAX_DISPATCH_ATTEMPTS - 1,\n                next_at: now + window\n            },\n            \"seat-blocked never increments attempts, never goes terminal\"\n        );\n\n        // The SAME message classified Transient would have gone Terminal here\n        // — proving the classification, not the budget arithmetic, is what\n        // keeps the stream alive.\n        let transient = DispatchOutcome::Failed(\"broker IPC hiccup\".into());\n        assert_eq!(\n            outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),\n            ClaimState::Terminal,\n            \"a transient at the budget edge still exhausts (D3 unchanged)\"\n        );\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] keyed-record completeness\n    // (Amendment fix 9): every dispatch_event carries gen / stream / family /\n    // attempt / conn / event / detail — the field-discriminator key set the\n    // poison-window census greps on. Unclassified rows say so honestly.\n    #[test]\n    fn dispatch_event_carries_the_complete_key_set() {\n        let ev = dispatch_event(\n            1721111111,\n            7058,\n            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),\n            // The right family with a blank/missing field -> None, not \"\".\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"\"}), None),\n            (StreamFamily::Attach, json!({\"session_id\": 3}), None),\n        ];\n        for (family, line, want) in cases {\n            assert_eq!(\n                line_endpoint(family, &line).as_deref(),\n                want,\n                \"family {family:?} line {line}\"\n            );\n        }\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n    // discriminator (gate round 1): a finished ATTACH is terminal for\n    // redispatch (the operator detached — replay = the 7.41 controller\n    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n    // requesters FIN their send WITH the request (sync.rs request_sync /\n    // propagate.rs fetch_update) and await the reply, so a bare finished\n    // filter would break every pull that arrives while the brain is down.\n\n[Showing lines 1269-1533 of 1558. Use :1534 to continue]"}],"details":{"truncation":{"content":"\n    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n    // — a persistently-failing stream retries exactly its bounded budget and\n    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n    // abandoned the stream on failure #1 forever.\n    #[test]\n    fn transient_failure_requeues_bounded_and_terminal_never_reenters() {\n        let now = Instant::now();\n\n        // Fresh row → claimable with zero prior failures.\n        assert_eq!(should_claim(None, now), Some(0));\n        // In flight / terminal → never.\n        assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);\n        assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);\n\n        // Failure #1 releases with backoff — NOT claimable before next_at,\n        // claimable at/after it, carrying the attempt count.\n        let failed = DispatchOutcome::Failed(\"io\".into());\n        let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);\n        let ClaimState::Retry { attempts: 1, next_at } = s1 else {\n            panic!(\"failure #1 must requeue, got {s1:?}\");\n        };\n        assert_eq!(next_at, now + retry_backoff(1));\n        assert_eq!(should_claim(Some(&s1), now), None, \"backoff holds the claim\");\n        assert_eq!(should_claim(Some(&s1), next_at), Some(1), \"due → reclaimable\");\n\n        // Backoff doubles per attempt.\n        assert_eq!(retry_backoff(2), retry_backoff(1) * 2);\n\n        // The budget is BOUNDED: the MAX'th failure goes terminal, silent\n        // forever after (no hot loop under a persistently-failing stream).\n        let s2 = outcome_transition(&failed, 1, now, Duration::ZERO);\n        assert!(matches!(s2, ClaimState::Retry { attempts: 2, .. }));\n        let s3 = outcome_transition(&failed, 2, now, Duration::ZERO);\n        assert_eq!(s3, ClaimState::Terminal, \"attempt {MAX_DISPATCH_ATTEMPTS} exhausts the budget\");\n\n        // Terminal CLASSIFICATION outcomes are terminal immediately — a\n        // served exchange and an unclassifiable stream never retry.\n        assert_eq!(outcome_transition(&DispatchOutcome::Served(\"ok\".into()), 0, now, Duration::ZERO), ClaimState::Terminal);\n        assert_eq!(outcome_transition(&DispatchOutcome::Unknown, 0, now, Duration::ZERO), ClaimState::Terminal);\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:\n    // exactly the three strings the nethost subscriber discipline emits are\n    // SeatBlocked (they pace on the global breaker, budget-free); everything\n    // else — transport timeouts, EOF, opener-not-pinned, store errors — keeps\n    // the bounded Transient budget. The strings are load-bearing wire facts:\n    // begin_attach's \"subscriber busy\", subscribe_stream's \"replay halted\",\n    // send_stream's \"lease canceled\".\n    #[test]\n    fn classify_failure_seat_blocked_is_exactly_the_three_discipline_strings() {\n        for seat_blocked in [\n            \"stream 7058 subscriber busy: prior subscriber still draining\",\n            \"presence subscriber busy: prior subscriber still draining\",\n            \"stream 7058 replay halted: subscriber write failed\",\n            \"stream 7058 lease canceled (subscriber poisoned) — serve leg terminated\",\n        ] {\n            assert_eq!(\n                classify_failure(seat_blocked),\n                FailClass::SeatBlocked,\n                \"for {seat_blocked:?}\"\n            );\n        }\n        for transient in [\n            \"brain IPC read deadline elapsed\",\n            \"stream 7058: opener not pinned yet\",\n            \"unexpected EOF reading broker frame\",\n            \"no such session 12\",\n            \"\",\n        ] {\n            assert_eq!(\n                classify_failure(transient),\n                FailClass::Transient,\n                \"for {transient:?}\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] fallback narrowing (Amendment\n    // fix 1): ONLY the old broker's literal `unknown command kind` answer may\n    // fall back to the ring peek. Transport timeout / EOF / poison — the\n    // pre-Amendment broad Err(_) classes that manufactured throwaway replay\n    // subscribers against wedged conns — must NOT pass this gate.\n    #[test]\n    fn only_the_old_broker_unknown_verb_answer_falls_back_to_the_peek() {\n        assert!(is_unsupported_verb(&io::Error::other(\n            \"unknown command kind \\\"net-stream-opener\\\"\"\n        )));\n        for e in [\n            io::Error::new(io::ErrorKind::TimedOut, \"write deadline elapsed\"),\n            io::Error::new(io::ErrorKind::UnexpectedEof, \"broker EOF\"),\n            io::Error::other(\"conn poisoned\"),\n            io::Error::other(\"stream 7: opener not pinned yet\"),\n        ] {\n            assert!(\n                !is_unsupported_verb(&e),\n                \"{e}: must be Failed/requeue, never a second replay subscriber\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the global circuit breaker's\n    // trip/gate/reset contract (Amendment fix 5): trip opens a doubling window\n    // (base 2s, cap 30s), open() gates claiming inside the window and releases\n    // at its end, reset (a Served outcome) closes it AND restores the base\n    // window — real progress forgives the backoff history.\n    #[test]\n    fn breaker_trips_with_doubling_capped_window_and_resets_on_progress() {\n        let now = Instant::now();\n        let mut b = CircuitBreaker::default();\n        assert!(!b.open(now), \"a fresh breaker is closed\");\n\n        // Trip #1: base window; open inside, closed at its end.\n        assert_eq!(b.trip(now), BREAKER_BASE);\n        assert!(b.open(now));\n        assert!(b.open(now + BREAKER_BASE - Duration::from_millis(1)));\n        assert!(!b.open(now + BREAKER_BASE), \"window elapses -> claiming resumes\");\n\n        // Consecutive trips double... (2s, 4s, 8s, 16s, 32s->30s cap)\n        assert_eq!(b.trip(now), BREAKER_BASE * 2);\n        assert_eq!(b.trip(now), BREAKER_BASE * 4);\n        assert_eq!(b.trip(now), BREAKER_BASE * 8);\n        // ...and cap at BREAKER_CAP, never wider.\n        assert_eq!(b.trip(now), BREAKER_CAP);\n        assert_eq!(b.trip(now), BREAKER_CAP);\n\n        // Reset (Served): closed now, and the NEXT trip is back to base.\n        b.reset();\n        assert!(!b.open(now));\n        assert_eq!(b.trip(now), BREAKER_BASE, \"reset restores the base window\");\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] a SeatBlocked failure requeues\n    // WITHOUT burning the bounded transient budget, paced by the breaker\n    // window (T5's not-abandonment discriminator): the wedged seat clears at\n    // most one 15s bounded write later, and three fast transient retries\n    // would have burned the claim Terminal before that — the v0.33\n    // abandonment this must never regress to. A Transient at the same\n    // attempt count still burns budget (D3 unchanged).\n    #[test]\n    fn seat_blocked_requeues_budget_free_paced_by_the_breaker_window() {\n        let now = Instant::now();\n        let window = Duration::from_secs(4);\n        let blocked =\n            DispatchOutcome::Failed(\"stream 9 subscriber busy: prior subscriber still draining\".into());\n\n        // Even AT the terminal edge (attempts = MAX-1), SeatBlocked keeps the\n        // attempt count and paces on the breaker window instead of Terminal.\n        let s = outcome_transition(&blocked, MAX_DISPATCH_ATTEMPTS - 1, now, window);\n        assert_eq!(\n            s,\n            ClaimState::Retry {\n                attempts: MAX_DISPATCH_ATTEMPTS - 1,\n                next_at: now + window\n            },\n            \"seat-blocked never increments attempts, never goes terminal\"\n        );\n\n        // The SAME message classified Transient would have gone Terminal here\n        // — proving the classification, not the budget arithmetic, is what\n        // keeps the stream alive.\n        let transient = DispatchOutcome::Failed(\"broker IPC hiccup\".into());\n        assert_eq!(\n            outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),\n            ClaimState::Terminal,\n            \"a transient at the budget edge still exhausts (D3 unchanged)\"\n        );\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] keyed-record completeness\n    // (Amendment fix 9): every dispatch_event carries gen / stream / family /\n    // attempt / conn / event / detail — the field-discriminator key set the\n    // poison-window census greps on. Unclassified rows say so honestly.\n    #[test]\n    fn dispatch_event_carries_the_complete_key_set() {\n        let ev = dispatch_event(\n            1721111111,\n            7058,\n            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),\n            // The right family with a blank/missing field -> None, not \"\".\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"\"}), None),\n            (StreamFamily::Attach, json!({\"session_id\": 3}), None),\n        ];\n        for (family, line, want) in cases {\n            assert_eq!(\n                line_endpoint(family, &line).as_deref(),\n                want,\n                \"family {family:?} line {line}\"\n            );\n        }\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n    // discriminator (gate round 1): a finished ATTACH is terminal for\n    // redispatch (the operator detached — replay = the 7.41 controller\n    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n    // requesters FIN their send WITH the request (sync.rs request_sync /\n    // propagate.rs fetch_update) and await the reply, so a bare finished\n    // filter would break every pull that arrives while the brain is down.","truncated":true,"truncatedBy":"lines","totalLines":290,"totalBytes":12623,"outputLines":265,"outputBytes":12623,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"\n    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n    // — a persistently-failing stream retries exactly its bounded budget and\n    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n    // abandoned the stream on failure #1 forever.\n    #[test]\n    fn transient_failure_requeues_bounded_and_terminal_never_reenters() {\n        let now = Instant::now();\n\n        // Fresh row → claimable with zero prior failures.\n        assert_eq!(should_claim(None, now), Some(0));\n        // In flight / terminal → never.\n        assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);\n        assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);\n\n        // Failure #1 releases with backoff — NOT claimable before next_at,\n        // claimable at/after it, carrying the attempt count.\n        let failed = DispatchOutcome::Failed(\"io\".into());\n        let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);\n        let ClaimState::Retry { attempts: 1, next_at } = s1 else {\n            panic!(\"failure #1 must requeue, got {s1:?}\");\n        };\n        assert_eq!(next_at, now + retry_backoff(1));\n        assert_eq!(should_claim(Some(&s1), now), None, \"backoff holds the claim\");\n        assert_eq!(should_claim(Some(&s1), next_at), Some(1), \"due → reclaimable\");\n\n        // Backoff doubles per attempt.\n        assert_eq!(retry_backoff(2), retry_backoff(1) * 2);\n\n        // The budget is BOUNDED: the MAX'th failure goes terminal, silent\n        // forever after (no hot loop under a persistently-failing stream).\n        let s2 = outcome_transition(&failed, 1, now, Duration::ZERO);\n        assert!(matches!(s2, ClaimState::Retry { attempts: 2, .. }));\n        let s3 = outcome_transition(&failed, 2, now, Duration::ZERO);\n        assert_eq!(s3, ClaimState::Terminal, \"attempt {MAX_DISPATCH_ATTEMPTS} exhausts the budget\");\n\n        // Terminal CLASSIFICATION outcomes are terminal immediately — a\n        // served exchange and an unclassifiable stream never retry.\n        assert_eq!(outcome_transition(&DispatchOutcome::Served(\"ok\".into()), 0, now, Duration::ZERO), ClaimState::Terminal);\n        assert_eq!(outcome_transition(&DispatchOutcome::Unknown, 0, now, Duration::ZERO), ClaimState::Terminal);\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:\n    // exactly the three strings the nethost subscriber discipline emits are\n    // SeatBlocked (they pace on the global breaker, budget-free); everything\n    // else — transport timeouts, EOF, opener-not-pinned, store errors — keeps\n    // the bounded Transient budget. The strings are load-bearing wire facts:\n    // begin_attach's \"subscriber busy\", subscribe_stream's \"replay halted\",\n    // send_stream's \"lease canceled\".\n    #[test]\n    fn classify_failure_seat_blocked_is_exactly_the_three_discipline_strings() {\n        for seat_blocked in [\n            \"stream 7058 subscriber busy: prior subscriber still draining\",\n            \"presence subscriber busy: prior subscriber still draining\",\n            \"stream 7058 replay halted: subscriber write failed\",\n            \"stream 7058 lease canceled (subscriber poisoned) — serve leg terminated\",\n        ] {\n            assert_eq!(\n                classify_failure(seat_blocked),\n                FailClass::SeatBlocked,\n                \"for {seat_blocked:?}\"\n            );\n        }\n        for transient in [\n            \"brain IPC read deadline elapsed\",\n            \"stream 7058: opener not pinned yet\",\n            \"unexpected EOF reading broker frame\",\n            \"no such session 12\",\n            \"\",\n        ] {\n            assert_eq!(\n                classify_failure(transient),\n                FailClass::Transient,\n                \"for {transient:?}\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] fallback narrowing (Amendment\n    // fix 1): ONLY the old broker's literal `unknown command kind` answer may\n    // fall back to the ring peek. Transport timeout / EOF / poison — the\n    // pre-Amendment broad Err(_) classes that manufactured throwaway replay\n    // subscribers against wedged conns — must NOT pass this gate.\n    #[test]\n    fn only_the_old_broker_unknown_verb_answer_falls_back_to_the_peek() {\n        assert!(is_unsupported_verb(&io::Error::other(\n            \"unknown command kind \\\"net-stream-opener\\\"\"\n        )));\n        for e in [\n            io::Error::new(io::ErrorKind::TimedOut, \"write deadline elapsed\"),\n            io::Error::new(io::ErrorKind::UnexpectedEof, \"broker EOF\"),\n            io::Error::other(\"conn poisoned\"),\n            io::Error::other(\"stream 7: opener not pinned yet\"),\n        ] {\n            assert!(\n                !is_unsupported_verb(&e),\n                \"{e}: must be Failed/requeue, never a second replay subscriber\"\n            );\n        }\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the global circuit breaker's\n    // trip/gate/reset contract (Amendment fix 5): trip opens a doubling window\n    // (base 2s, cap 30s), open() gates claiming inside the window and releases\n    // at its end, reset (a Served outcome) closes it AND restores the base\n    // window — real progress forgives the backoff history.\n    #[test]\n    fn breaker_trips_with_doubling_capped_window_and_resets_on_progress() {\n        let now = Instant::now();\n        let mut b = CircuitBreaker::default();\n        assert!(!b.open(now), \"a fresh breaker is closed\");\n\n        // Trip #1: base window; open inside, closed at its end.\n        assert_eq!(b.trip(now), BREAKER_BASE);\n        assert!(b.open(now));\n        assert!(b.open(now + BREAKER_BASE - Duration::from_millis(1)));\n        assert!(!b.open(now + BREAKER_BASE), \"window elapses -> claiming resumes\");\n\n        // Consecutive trips double... (2s, 4s, 8s, 16s, 32s->30s cap)\n        assert_eq!(b.trip(now), BREAKER_BASE * 2);\n        assert_eq!(b.trip(now), BREAKER_BASE * 4);\n        assert_eq!(b.trip(now), BREAKER_BASE * 8);\n        // ...and cap at BREAKER_CAP, never wider.\n        assert_eq!(b.trip(now), BREAKER_CAP);\n        assert_eq!(b.trip(now), BREAKER_CAP);\n\n        // Reset (Served): closed now, and the NEXT trip is back to base.\n        b.reset();\n        assert!(!b.open(now));\n        assert_eq!(b.trip(now), BREAKER_BASE, \"reset restores the base window\");\n    }\n\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] a SeatBlocked failure requeues\n    // WITHOUT burning the bounded transient budget, paced by the breaker\n    // window (T5's not-abandonment discriminator): the wedged seat clears at\n    // most one 15s bounded write later, and three fast transient retries\n    // would have burned the claim Terminal before that — the v0.33\n    // abandonment this must never regress to. A Transient at the same\n    // attempt count still burns budget (D3 unchanged).\n    #[test]\n    fn seat_blocked_requeues_budget_free_paced_by_the_breaker_window() {\n        let now = Instant::now();\n        let window = Duration::from_secs(4);\n        let blocked =\n            DispatchOutcome::Failed(\"stream 9 subscriber busy: prior subscriber still draining\".into());\n\n        // Even AT the terminal edge (attempts = MAX-1), SeatBlocked keeps the\n        // attempt count and paces on the breaker window instead of Terminal.\n        let s = outcome_transition(&blocked, MAX_DISPATCH_ATTEMPTS - 1, now, window);\n        assert_eq!(\n            s,\n            ClaimState::Retry {\n                attempts: MAX_DISPATCH_ATTEMPTS - 1,\n                next_at: now + window\n            },\n            \"seat-blocked never increments attempts, never goes terminal\"\n        );\n\n        // The SAME message classified Transient would have gone Terminal here\n        // — proving the classification, not the budget arithmetic, is what\n        // keeps the stream alive.\n        let transient = DispatchOutcome::Failed(\"broker IPC hiccup\".into());\n        assert_eq!(\n            outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),\n            ClaimState::Terminal,\n            \"a transient at the budget edge still exhausts (D3 unchanged)\"\n        );\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] keyed-record completeness\n    // (Amendment fix 9): every dispatch_event carries gen / stream / family /\n    // attempt / conn / event / detail — the field-discriminator key set the\n    // poison-window census greps on. Unclassified rows say so honestly.\n    #[test]\n    fn dispatch_event_carries_the_complete_key_set() {\n        let ev = dispatch_event(\n            1721111111,\n            7058,\n            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),\n            // The right family with a blank/missing field -> None, not \"\".\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"\"}), None),\n            (StreamFamily::Attach, json!({\"session_id\": 3}), None),\n        ];\n        for (family, line, want) in cases {\n            assert_eq!(\n                line_endpoint(family, &line).as_deref(),\n                want,\n                \"family {family:?} line {line}\"\n            );\n        }\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n    // discriminator (gate round 1): a finished ATTACH is terminal for\n    // redispatch (the operator detached — replay = the 7.41 controller\n    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n    // requesters FIN their send WITH the request (sync.rs request_sync /\n    // propagate.rs fetch_update) and await the reply, so a bare finished\n    // filter would break every pull that arrives while the brain is down.","startLine":1269,"lineNumbers":[1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,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,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":12623,"outputLines":265,"outputBytes":12623,"shownRange":{"start":1269,"end":1533},"nextOffset":1534}}},"isError":false,"timestamp":1784271567111}}
{"type":"message","id":"f02b26b4","parentId":"895fff8d","timestamp":"2026-07-17T06:59:27.149Z","message":{"role":"toolResult","toolCallId":"call_3WVGBWm7pcP9WbqOe7wR76Zj|fc_089095e18118ee7d016a59d2ceee94819089fb1a636b7385e5","toolName":"read","content":[{"type":"text","text":"    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n…\n        match req.op_id {\n…\n            Some(op) => {\n…\n                Ok(())\n            }\n        }\n    }\n\n    /// List every stream the broker holds (D4b) — the query a re-attaching\n    /// brain uses to find streams that arrived while it was down.\n    fn dispatch_net_streams(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let frame = Envelope::new(\n            KIND_NET_STREAMS_REPLY,\n            serde_json::to_value(NetStreamsReply {\n                streams: host.stream_infos(),\n            })\n            .expect(\"NetStreamsReply serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A):\n    /// the dispatcher reports a serve loop that ran to completion, and the row\n    /// leaves the table so no future dispatcher generation can rediscover and\n    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let removed = host.retire_stream(req.stream_id);\n        let frame = Envelope::new(\n            KIND_NET_STREAM_RETIRED,\n            serde_json::to_value(NetStreamRetired {\n                stream_id: req.stream_id,\n                removed,\n            })\n            .expect(\"NetStreamRetired serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact the dispatcher reads instead of peeking ring seq 0.\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenerReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let opener = host\n            .stream_opener(req.stream_id)\n            .ok_or_else(|| format!(\"no such stream {}\", req.stream_id))?;\n        let frame = Envelope::new(\n            KIND_NET_STREAM_OPENER_REPLY,\n            serde_json::to_value(NetStreamOpenerReply {\n…\n            })\n            .expect(\"NetStreamOpenerReply serializes\"),\n        );\n…\n    }\n\n[Showing lines 4359-4413 of 6298. Use :4414 to continue]"}],"details":{"truncation":{"content":"                Ok(())\n            }\n        }\n    }\n\n    /// List every stream the broker holds (D4b) — the query a re-attaching\n    /// brain uses to find streams that arrived while it was down.\n    fn dispatch_net_streams(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let frame = Envelope::new(\n            KIND_NET_STREAMS_REPLY,\n            serde_json::to_value(NetStreamsReply {\n                streams: host.stream_infos(),\n            })\n            .expect(\"NetStreamsReply serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A):\n    /// the dispatcher reports a serve loop that ran to completion, and the row\n    /// leaves the table so no future dispatcher generation can rediscover and\n    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let removed = host.retire_stream(req.stream_id);\n        let frame = Envelope::new(\n            KIND_NET_STREAM_RETIRED,\n            serde_json::to_value(NetStreamRetired {\n                stream_id: req.stream_id,\n                removed,\n            })\n            .expect(\"NetStreamRetired serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact the dispatcher reads instead of peeking ring seq 0.\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenerReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let opener = host\n            .stream_opener(req.stream_id)\n            .ok_or_else(|| format!(\"no such stream {}\", req.stream_id))?;\n        let frame = Envelope::new(\n            KIND_NET_STREAM_OPENER_REPLY,\n            serde_json::to_value(NetStreamOpenerReply {","truncated":true,"truncatedBy":"lines","totalLines":1940,"totalBytes":2447,"outputLines":55,"outputBytes":2447,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n…\n        match req.op_id {\n…\n            Some(op) => {\n…\n                Ok(())\n            }\n        }\n    }\n\n    /// List every stream the broker holds (D4b) — the query a re-attaching\n    /// brain uses to find streams that arrived while it was down.\n    fn dispatch_net_streams(&self, send: &SharedSend) -> Result<(), String> {\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let frame = Envelope::new(\n            KIND_NET_STREAMS_REPLY,\n            serde_json::to_value(NetStreamsReply {\n                streams: host.stream_infos(),\n            })\n            .expect(\"NetStreamsReply serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A):\n    /// the dispatcher reports a serve loop that ran to completion, and the row\n    /// leaves the table so no future dispatcher generation can rediscover and\n    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let removed = host.retire_stream(req.stream_id);\n        let frame = Envelope::new(\n            KIND_NET_STREAM_RETIRED,\n            serde_json::to_value(NetStreamRetired {\n                stream_id: req.stream_id,\n                removed,\n            })\n            .expect(\"NetStreamRetired serializes\"),\n        );\n        send_frame(send, &frame);\n        Ok(())\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact the dispatcher reads instead of peeking ring seq 0.\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: NetStreamOpenerReq = serde_json::from_value(env.payload)\n            .map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;\n        let host = self.net.get().ok_or(\"net disabled\")?;\n        let opener = host\n            .stream_opener(req.stream_id)\n            .ok_or_else(|| format!(\"no such stream {}\", req.stream_id))?;\n        let frame = Envelope::new(\n            KIND_NET_STREAM_OPENER_REPLY,\n            serde_json::to_value(NetStreamOpenerReply {\n…\n            })\n            .expect(\"NetStreamOpenerReply serializes\"),\n        );\n…\n    }","startLine":4326,"lineNumbers":[4326,null,4332,null,4336,null,4359,4360,4361,4362,4363,4364,4365,4366,4367,4368,4369,4370,4371,4372,4373,4374,4375,4376,4377,4378,4379,4380,4381,4382,4383,4384,4385,4386,4387,4388,4389,4390,4391,4392,4393,4394,4395,4396,4397,4398,4399,4400,4401,4402,4403,4404,4405,4406,4407,4408,4409,4410,4411,4412,4413,null,4416,4417,4418,null,4421]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":2447,"outputLines":55,"outputBytes":2447,"shownRange":{"start":4359,"end":4413},"nextOffset":4414}}},"isError":false,"timestamp":1784271567149}}
{"type":"message","id":"083f2bcd","parentId":"f02b26b4","timestamp":"2026-07-17T06:59:40.685Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing BrokerConn watchdog thread creation**"},{"type":"toolCall","id":"call_H1Qj3Q1n36bVbUoLUgu6ertO|fc_089095e18118ee7d016a59d2dc8d1481909a514a366f36448d","name":"grep","arguments":{"i":"Locating handler cleanup tail","pattern":"for id in my_stream_subs|my_stream_subs|my_presence_sub|detach_stream_sub|detach_if\\(&send|return Ok\\(\\(\\)\\)","path":"crates/spt-daemon/src/broker.rs","selector":"3025-3350","case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_h3mjye6YMAhNExzUbHo6iXhg|fc_089095e18118ee7d016a59d2dc8d288190b7c8b13309084340","name":"read","arguments":{"i":"Reading handler cleanup section","path":"crates/spt-daemon/src/broker.rs","selector":"3300-3410"}},{"type":"toolCall","id":"call_JeWvF3h3X7xIwSMW4XGA6x6r|fc_089095e18118ee7d016a59d2dc8d3081908f8ffd1aa1361abe","name":"grep","arguments":{"i":"Checking worker thread gauges","pattern":"conn_handler_count|active_workers|stream-sub-attach|transport-close|worker-outcome|breaker-trip","path":"crates/spt-daemon/tests; crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_bBikPsqa33abUdH9HiPYcayX|fc_089095e18118ee7d016a59d2dc8d348190b28264e2b301c73d","name":"grep","arguments":{"i":"Checking refresh regression coverage","pattern":"refresh|generation|conn_handler_count|thread|stream-sub-attach|retire","path":"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs; crates/spt-daemon/tests/daemon_refresh.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_GsJQFiq0ibbFfAwtO1wiJBnU|fc_089095e18118ee7d016a59d2dc8d388190ad8391c9b4be1820","name":"glob","arguments":{"i":"Finding runtime RCA artifacts","path":".claude/reports/**/*redispatch*","hidden":true,"gitignore":false,"limit":200}},{"type":"toolCall","id":"call_sH2XLX5Fn0W1s4Og1W8cLaLa|fc_089095e18118ee7d016a59d2dc8d408190bfee9ce7e8b100db","name":"glob","arguments":{"i":"Finding performance scripts","path":"**/*{thread,cpu,io,redispatch}*.{ps1,rs,md,txt}","hidden":true,"gitignore":true,"limit":300}},{"type":"toolCall","id":"call_v1uEn4sCfwaezTufzbSMVdeA|fc_089095e18118ee7d016a59d2dc8d448190bf6fc1376cb9272f","name":"grep","arguments":{"i":"Finding dispatch generation metrics","pattern":"DISPATCH_EV|stream-sub-attach|conn_handler_count|MAX_DISPATCH_WORKERS","path":"scripts; tools; crates/spt-daemon/tests","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":23732,"output":606,"cacheRead":140800,"cacheWrite":0,"totalTokens":165138,"reasoningTokens":125,"cost":{"input":0.11866000000000002,"output":0.01818,"cacheRead":0.07039999999999999,"cacheWrite":0,"total":0.20724}},"stopReason":"toolUse","timestamp":1784271567200,"responseId":"resp_089095e18118ee7d016a59d2cf94e0819081fff7a711b85758","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2d183a88190866703edc11281cb","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdLcAdD0QWggWOAKMDxNYVOiIVU2RuVIt-vC3nLpMMxJXuoYSBqCXTTGz25120-zK4Ai0BzjJtk1COqxO3BaJ3cerYQmHqzsk5JBm31JIswrDxtS1I5HZ1TUUlo_HgmMN2Efi4zGo9_jg0oyFAeUdBu2hAXLJvGlHkfbpSk69cPmcojCBm4XlfOBA7p74IoFvsUSTWsCST3cM98Qpv0--rhv8jjKQHtWID1sHPQk86LT_n_uMZL9MPwxIhLcdu7yZd-wILxWAHRJieyNLU_H5gzLumZtdBWqRAOUZBXroFkdFkGju4wpZOXSdqvNM2m8JceyWev3UEVfiRh47tccnmKQWydRN8l745hZetlGO7B3EVr-Fhfz3veEWZDUIKhNOmhxitlqjPsRwHx_4LL9lI70Xg3UG8o_W1n8YPXBJa55cZoJE-4O7LbgqodYTttbNKUORyauWZqdPGmAObexNwCRyKgfofRWLbxk8eToGBwWmsP1-J-D_jo0ss3ixxeW3Lm11slK-_rlnze1pVeuHgeJtRzk88njhEpsVg4BEE0yDrnugdYML8oddwtld4UDhybaRCa1Iog5HZZg0uS4x-9WOoW3hxbyQugxRzCQtqrPk5dDfXcTcrLR8rWSTUbTjfhUeZGBYJ97s4ThX8bBgkP4lub76fsSDhmLXAypfZ_EXYNHQsiOKjXKQHCxPZw8OzfhpMeisb_-eF_sBf6kS_3fRHkl4lv0en7HyTlfITjDodq4Jj78ZYddSmJe26RQujFxKzTdzsRSDjlB1GmvGcIVlWhtx5Ksb2lnBMVJToPOy71gZHLHpD0H-FvEW-WUuU-USTXS61KQcUMdUMKFcHkccuS1eYU4bpdwj8RGED-FaJpaULAFggM80WJ3-bNZDl4cA3aWYUG3WIVsisYhxTCD_xpTRzFo3kAXxZzAJvLXbYroIklJvbJuh68qg2HJury6t2TP5Fa8QI6dcavIlnT7FcttSR2oZsuoCaVGk7JAq5V-49C_gAuvpebWzV-C3N2dqpJ8ModJ7vNqKuCmFwyJ0UyicEw4GQuAggwBAv1Hz2LS3lODYpd-VGMI7dzJ4oWrRCXj3vtLZfrrlrF1tde7rOadq5RIsYGWyjx8Mhsi75vA6kgYFcrX66za0HyGuvZJzricEH8tpchzc5Jjw21V101-EiOJeeEGN1FuFyLnW4zAlT6WyQ28FLqrbkxQJ31ENH5vs_gEXBhJyDEajtS2UUXH3gsM-3NjtYfUcpYvafoPcI68ulGe5bxAfKzpOD1YwL0y4qpBf8Srgl1QtCllQ_T6Ipp0fBTIwbQ_kz8HfrhopkO3ffAdeIpHbTjJ3R1TFM6q38DIzCxGgszhnJsy65yKU6fliUJU7MmVrXBTWoGwUYSPz5rE_huRKm_HOXFf7nhFixKsKqXX0c0v812037qFWDaau1daHAkmmVOQzQYyB1Ddv6ri6T2nk6-6JYB-1KXd0hxQnaM14sQwbhOZ-Ot8fN24N_QF07sb594FV3_e40o968wibkrmezn17H0eUZalh6ft2qBRJovKwh-4a5HAD61IsVqKjmRiWHiwkHkkZDhpp8QcnL6y6tOnZXlqJE2CiAXdNcfWi9P0MJoSTIgJSh-n4wQkEQn6XTd_fjmUcq_4_-eX-FtW-F3Ddawr0I1xb0l9Wwe9CVYlUEzH4WIA7Wo2BxmbMgcNmOSI14_EVNCpqy7LymdI0ZxrySkUzJwEHD2Erd-4oj-oUIzHwLpeZgiQPqPGNukc-I16SHM=","summary":[{"type":"summary_text","text":"**Tracing BrokerConn watchdog thread creation**"}]},{"id":"fc_089095e18118ee7d016a59d2dc8d1481909a514a366f36448d","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating handler cleanup tail\",\"pattern\":\"for id in my_stream_subs|my_stream_subs|my_presence_sub|detach_stream_sub|detach_if\\\\(&send|return Ok\\\\(\\\\(\\\\)\\\\)\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"3025-3350\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_H1Qj3Q1n36bVbUoLUgu6ertO","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2dc8d288190b7c8b13309084340","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading handler cleanup section\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"3300-3410\"}","call_id":"call_h3mjye6YMAhNExzUbHo6iXhg","name":"read"},{"id":"fc_089095e18118ee7d016a59d2dc8d3081908f8ffd1aa1361abe","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking worker thread gauges\",\"pattern\":\"conn_handler_count|active_workers|stream-sub-attach|transport-close|worker-outcome|breaker-trip\",\"path\":\"crates/spt-daemon/tests; crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_JeWvF3h3X7xIwSMW4XGA6x6r","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2dc8d348190b28264e2b301c73d","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking refresh regression coverage\",\"pattern\":\"refresh|generation|conn_handler_count|thread|stream-sub-attach|retire\",\"path\":\"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs; crates/spt-daemon/tests/daemon_refresh.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_bBikPsqa33abUdH9HiPYcayX","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2dc8d388190ad8391c9b4be1820","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding runtime RCA artifacts\",\"path\":\".claude/reports/**/*redispatch*\",\"hidden\":true,\"gitignore\":false,\"limit\":200}","call_id":"call_GsJQFiq0ibbFfAwtO1wiJBnU","name":"glob"},{"id":"fc_089095e18118ee7d016a59d2dc8d408190bfee9ce7e8b100db","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding performance scripts\",\"path\":\"**/*{thread,cpu,io,redispatch}*.{ps1,rs,md,txt}\",\"hidden\":true,\"gitignore\":true,\"limit\":300}","call_id":"call_sH2XLX5Fn0W1s4Og1W8cLaLa","name":"glob"},{"id":"fc_089095e18118ee7d016a59d2dc8d448190bf6fc1376cb9272f","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding dispatch generation metrics\",\"pattern\":\"DISPATCH_EV|stream-sub-attach|conn_handler_count|MAX_DISPATCH_WORKERS\",\"path\":\"scripts; tools; crates/spt-daemon/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_v1uEn4sCfwaezTufzbSMVdeA","name":"grep"}]},"duration":13472.621299996972,"ttft":2332.5473000109196,"contextSnapshot":{"promptTokens":164532,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_H1Qj3Q1n36bVbUoLUgu6ertO|fc_089095e18118ee7d016a59d2dc8d1481909a514a366f36448d","toolName":"grep","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Locating handler cleanup tail"},"id":"9840e74f","parentId":"083f2bcd","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_h3mjye6YMAhNExzUbHo6iXhg|fc_089095e18118ee7d016a59d2dc8d288190b7c8b13309084340","toolName":"read","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading handler cleanup section"},"id":"639bebe4","parentId":"9840e74f","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_JeWvF3h3X7xIwSMW4XGA6x6r|fc_089095e18118ee7d016a59d2dc8d3081908f8ffd1aa1361abe","toolName":"grep","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"crates/spt-daemon/tests; crates/spt-daemon/src"},"intent":"Checking worker thread gauges"},"id":"20e5ff6e","parentId":"639bebe4","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bBikPsqa33abUdH9HiPYcayX|fc_089095e18118ee7d016a59d2dc8d348190b28264e2b301c73d","toolName":"grep","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs; crates/spt-daemon/tests/daemon_refresh.rs"},"intent":"Checking refresh regression coverage"},"id":"38e096b4","parentId":"20e5ff6e","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GsJQFiq0ibbFfAwtO1wiJBnU|fc_089095e18118ee7d016a59d2dc8d388190ad8391c9b4be1820","toolName":"glob","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":".claude/reports/**/*redispatch*"},"intent":"Finding runtime RCA artifacts"},"id":"85835300","parentId":"38e096b4","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_sH2XLX5Fn0W1s4Og1W8cLaLa|fc_089095e18118ee7d016a59d2dc8d408190bfee9ce7e8b100db","toolName":"glob","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"**/*{thread,cpu,io,redispatch}*.{ps1,rs,md,txt}"},"intent":"Finding performance scripts"},"id":"72c7fa7d","parentId":"85835300","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_v1uEn4sCfwaezTufzbSMVdeA|fc_089095e18118ee7d016a59d2dc8d448190bf6fc1376cb9272f","toolName":"grep","startedAt":"2026-07-17T06:59:40.686Z","args":{"path":"scripts; tools; crates/spt-daemon/tests"},"intent":"Finding dispatch generation metrics"},"id":"959a7d7a","parentId":"72c7fa7d","timestamp":"2026-07-17T06:59:40.686Z"}
{"type":"message","id":"16a6c94d","parentId":"959a7d7a","timestamp":"2026-07-17T06:59:40.689Z","message":{"role":"toolResult","toolCallId":"call_H1Qj3Q1n36bVbUoLUgu6ertO|fc_089095e18118ee7d016a59d2dc8d1481909a514a366f36448d","toolName":"grep","content":[{"type":"text","text":" 3051|        let mut my_subs: Vec<u64> = Vec::new();\n*3052|        let mut my_stream_subs: Vec<u64> = Vec::new();\n*3053|        let mut my_presence_sub = false;\n 3054|\n 3055|        loop {\n 3056|            let env = match read_frame(&mut recv) {\n...\n 3245|                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n*3246|                    Ok(id) => my_stream_subs.push(id),\n 3247|                    Err(msg) => send_error(&send, &msg),\n 3248|                },\n 3249|                KIND_NET_PRESENCE_SUBSCRIBE => {\n 3250|                    match self.dispatch_net_presence_subscribe(env, &send) {\n*3251|                        Ok(()) => my_presence_sub = true,\n 3252|                        Err(msg) => send_error(&send, &msg),\n 3253|                    }\n 3254|                }\n...\n 3275|        for log in &logs {\n*3276|            recover_log(log).detach_if(&send);\n 3277|        }\n 3278|        // Same for net streams: the streams + their rings persist (D4b).\n 3279|        if let Some(host) = self.net.get() {\n*3280|            for id in &my_stream_subs {\n*3281|                host.detach_stream_sub(*id, &send);\n 3282|            }\n 3283|            // And presence: the liveness log + its ring persist (D4c).\n*3284|            if my_presence_sub {\n 3285|                host.detach_presence_sub(&send);\n 3286|            }\n 3287|        }"}],"details":{"scopePath":"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\\spt-core","matchCount":8,"fileCount":1,"files":["crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":8}],"truncated":false,"displayContent":" 3051│        let mut my_subs: Vec<u64> = Vec::new();\n*3052│        let mut my_stream_subs: Vec<u64> = Vec::new();\n*3053│        let mut my_presence_sub = false;\n 3054│\n 3055│        loop {\n 3056│            let env = match read_frame(&mut recv) {\n     │...\n 3245│                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n*3246│                    Ok(id) => my_stream_subs.push(id),\n 3247│                    Err(msg) => send_error(&send, &msg),\n 3248│                },\n 3249│                KIND_NET_PRESENCE_SUBSCRIBE => {\n 3250│                    match self.dispatch_net_presence_subscribe(env, &send) {\n*3251│                        Ok(()) => my_presence_sub = true,\n 3252│                        Err(msg) => send_error(&send, &msg),\n 3253│                    }\n 3254│                }\n     │...\n 3275│        for log in &logs {\n*3276│            recover_log(log).detach_if(&send);\n 3277│        }\n 3278│        // Same for net streams: the streams + their rings persist (D4b).\n 3279│        if let Some(host) = self.net.get() {\n*3280│            for id in &my_stream_subs {\n*3281│                host.detach_stream_sub(*id, &send);\n 3282│            }\n 3283│            // And presence: the liveness log + its ring persist (D4c).\n*3284│            if my_presence_sub {\n 3285│                host.detach_presence_sub(&send);\n 3286│            }\n 3287│        }"},"isError":false,"timestamp":1784271580688}}
{"type":"message","id":"ffbe6855","parentId":"16a6c94d","timestamp":"2026-07-17T06:59:40.689Z","message":{"role":"toolResult","toolCallId":"call_GsJQFiq0ibbFfAwtO1wiJBnU|fc_089095e18118ee7d016a59d2dc8d388190ad8391c9b4be1820","toolName":"glob","content":[{"type":"text","text":"# .claude/reports/\n## 2026-07-16-redispatch-stall/\n## 2026-07-16-redispatch-truth/"}],"details":{"scopePath":".claude/reports","fileCount":2,"files":[".claude/reports/2026-07-16-redispatch-stall/",".claude/reports/2026-07-16-redispatch-truth/"],"truncated":false,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"},"isError":false,"timestamp":1784271580689}}
{"type":"message","id":"66a518db","parentId":"ffbe6855","timestamp":"2026-07-17T06:59:40.697Z","message":{"role":"toolResult","toolCallId":"call_v1uEn4sCfwaezTufzbSMVdeA|fc_089095e18118ee7d016a59d2dc8d448190bf6fc1376cb9272f","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## brain_decouple.rs\n 401|//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\n*402|//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\n 403|//     client ever dropping its end;                                    ← RED pre-fix\n 404|//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n 405|// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n...\n 677|\n*678|    // ── Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads\n 679|    //    ZERO (a zero sample tolerates a transient query conn briefly bumping it),\n 680|    //    bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed\n 681|    //    conn's handler never exits until the client drops) = the RED. ──\n...\n 685|    while Instant::now() < step7_deadline {\n*686|        if broker.conn_handler_count() == 0 {\n 687|            handler_released = true;\n 688|            break;\n 689|        }\n...\n 810|        handler_released,\n*811|        \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\\n 812|         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n 813|         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 814|         detached writer stays wedged in write_frame and the handler never exits.\"\n## conn_blackhole_lifecycle.rs\n 23|//!      physically retired, not merely logically stall-evicted);\n*24|//!   3. ITS WRITER EXITS — `broker.conn_handler_count()` falls to the\n 25|//!      unrelated-viewer floor (<= 1; the persistent invariant-1 viewer holds one\n 26|//!      handler) WITHOUT the black-holed client ever dropping its end (the parked\n 27|//!      writer was aborted + the reader EOF-cleaned, so the handler thread ran to\n...\n 35|//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n*36|//!      is held by the retired writer), `conn_handler_count()` returns to the\n 37|//!      unrelated-viewer floor without the client dropping its end (no task\n 38|//!      lingers), AND both the\n 39|//!      unrelated session's viewer and the fresh viewer flow output (the shared\n...\n 49|//! gate for that hazard). It reuses the same SEED/trigger/burst choreography,\n*50|//! the hand-back-the-unread-Stream pattern, the `conn_handler_count` and\n 51|//! `stall_evict_count` seams, the old-client-write-fails probe, and the\n 52|//! teardown-before-assert discipline. It ADDS the two pieces the r4 gate does\n 53|//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n...\n 470|    // ── INVARIANT 3 (its writer exits): WITHOUT dropping R's Stream, poll\n*471|    //    conn_handler_count() until it drops to the UNRELATED-VIEWER FLOOR (<= 1),\n 472|    //    bounded at t_release + 2 s. `conn_handlers` is a GLOBAL atomic of live\n 473|    //    handle_conn threads, incremented once per physical conn. Unlike the r4\n 474|    //    gate (whose only persistent conn was the black-holed R, so its floor was\n...\n 494|    while Instant::now() < step7_deadline {\n*495|        if broker.conn_handler_count() <= 1 {\n 496|            handler_released = true;\n 497|            u_at_release = u_frames.load(Ordering::Relaxed);\n 498|            break;\n...\n 656|    // ── INVARIANT 3: ITS WRITER EXITS — the black-holed conn's handle_conn thread\n*657|    //    finishes (conn_handler_count → the <=1 unrelated-viewer floor) within 2 s\n 658|    //    of logical release, WITHOUT the client dropping its end. ──\n 659|    assert!(\n 660|        handler_released,\n 661|        \"INVARIANT 3: the black-holed connection's writer must EXIT — \\\n*662|         conn_handler_count must fall to the unrelated-viewer floor (<= 1) within 2 s \\\n 663|         of logical release WITHOUT the client dropping its end (while R's handler is \\\n 664|         alive the global count is >= 2, so <= 1 proves R's handler finished). A stuck \\\n 665|         count means the detached writer stays wedged in write_frame and the handler \\\n\nSkipped missing paths: scripts, tools"}],"details":{"scopePath":"crates/spt-daemon/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":11,"fileCount":2,"files":["crates/spt-daemon/tests/brain_decouple.rs","crates/spt-daemon/tests/conn_blackhole_lifecycle.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":4},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","count":7}],"truncated":false,"displayContent":"# crates/spt-daemon/tests/\n## brain_decouple.rs\n 401│//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\n*402│//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\n 403│//     client ever dropping its end;                                    ← RED pre-fix\n 404│//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n 405│// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n    │...\n 677│\n*678│    // ── Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads\n 679│    //    ZERO (a zero sample tolerates a transient query conn briefly bumping it),\n 680│    //    bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed\n 681│    //    conn's handler never exits until the client drops) = the RED. ──\n    │...\n 685│    while Instant::now() < step7_deadline {\n*686│        if broker.conn_handler_count() == 0 {\n 687│            handler_released = true;\n 688│            break;\n 689│        }\n    │...\n 810│        handler_released,\n*811│        \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\\n 812│         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n 813│         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 814│         detached writer stays wedged in write_frame and the handler never exits.\"\n## conn_blackhole_lifecycle.rs\n  23│//!      physically retired, not merely logically stall-evicted);\n *24│//!   3. ITS WRITER EXITS — `broker.conn_handler_count()` falls to the\n  25│//!      unrelated-viewer floor (<= 1; the persistent invariant-1 viewer holds one\n  26│//!      handler) WITHOUT the black-holed client ever dropping its end (the parked\n  27│//!      writer was aborted + the reader EOF-cleaned, so the handler thread ran to\n    │...\n  35│//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n *36│//!      is held by the retired writer), `conn_handler_count()` returns to the\n  37│//!      unrelated-viewer floor without the client dropping its end (no task\n  38│//!      lingers), AND both the\n  39│//!      unrelated session's viewer and the fresh viewer flow output (the shared\n    │...\n  49│//! gate for that hazard). It reuses the same SEED/trigger/burst choreography,\n *50│//! the hand-back-the-unread-Stream pattern, the `conn_handler_count` and\n  51│//! `stall_evict_count` seams, the old-client-write-fails probe, and the\n  52│//! teardown-before-assert discipline. It ADDS the two pieces the r4 gate does\n  53│//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n    │...\n 470│    // ── INVARIANT 3 (its writer exits): WITHOUT dropping R's Stream, poll\n*471│    //    conn_handler_count() until it drops to the UNRELATED-VIEWER FLOOR (<= 1),\n 472│    //    bounded at t_release + 2 s. `conn_handlers` is a GLOBAL atomic of live\n 473│    //    handle_conn threads, incremented once per physical conn. Unlike the r4\n 474│    //    gate (whose only persistent conn was the black-holed R, so its floor was\n    │...\n 494│    while Instant::now() < step7_deadline {\n*495│        if broker.conn_handler_count() <= 1 {\n 496│            handler_released = true;\n 497│            u_at_release = u_frames.load(Ordering::Relaxed);\n 498│            break;\n    │...\n 656│    // ── INVARIANT 3: ITS WRITER EXITS — the black-holed conn's handle_conn thread\n*657│    //    finishes (conn_handler_count → the <=1 unrelated-viewer floor) within 2 s\n 658│    //    of logical release, WITHOUT the client dropping its end. ──\n 659│    assert!(\n 660│        handler_released,\n 661│        \"INVARIANT 3: the black-holed connection's writer must EXIT — \\\n*662│         conn_handler_count must fall to the unrelated-viewer floor (<= 1) within 2 s \\\n 663│         of logical release WITHOUT the client dropping its end (while R's handler is \\\n 664│         alive the global count is >= 2, so <= 1 proves R's handler finished). A stuck \\\n 665│         count means the detached writer stays wedged in write_frame and the handler \\","missingPaths":["scripts","tools"]},"isError":false,"timestamp":1784271580697}}
{"type":"message","id":"6c99c0c0","parentId":"66a518db","timestamp":"2026-07-17T06:59:40.700Z","message":{"role":"toolResult","toolCallId":"call_sH2XLX5Fn0W1s4Og1W8cLaLa|fc_089095e18118ee7d016a59d2dc8d408190bfee9ce7e8b100db","toolName":"glob","content":[{"type":"text","text":"# crates/\n## spt-daemon/\n### tests/\nredispatch_stall.rs\nredispatch.rs\npsyche_residency_expectation_e2e.rs\n### src/\ntranslation.rs\n## spt/\n### tests/\nlive_adapt_translation_swap_e2e.rs\nrun_no_dup_session_e2e.rs\nnested_resolution_e2e.rs\n### src/\nelevation.rs\n## spt-store/src/\nsessions.rs\nrotation.rs\n## spt-term/src/\nprojection.rs\n## spt-proto/src/\nversion.rs\n# docs/adr/\n0039-peer-route-resolution-truth.md\n0036-private-fork-distribution.md\n0022-spt-hosted-idle-delivery-translation-binary.md\n0032-boundary-rotation-credential.md\n0027-unbound-endpoint-state-and-attach-on-session.md\n0026-multi-subnet-home-at-endpoint-run-creation.md\n0021-harness-hosted-adapter-agnostic-seed-and-bind-time-resolution.md\n0019-digest-adapter-declared-extractor-session-spanning.md\n0018-broker-brain-process-isolation-restoration.md\n0007-subnet-notification-primitive.md\n# docs-site/src/harness-contract/\nintegration-checklist.md"}],"details":{"scopePath":".","fileCount":23,"files":["crates/spt-daemon/tests/redispatch_stall.rs","docs/adr/0039-peer-route-resolution-truth.md","crates/spt-daemon/tests/redispatch.rs","docs-site/src/harness-contract/integration-checklist.md","docs/adr/0036-private-fork-distribution.md","crates/spt/tests/live_adapt_translation_swap_e2e.rs","crates/spt-store/src/sessions.rs","crates/spt-term/src/projection.rs","crates/spt-store/src/rotation.rs","crates/spt-proto/src/version.rs","crates/spt-daemon/tests/psyche_residency_expectation_e2e.rs","crates/spt-daemon/src/translation.rs","crates/spt/tests/run_no_dup_session_e2e.rs","crates/spt/tests/nested_resolution_e2e.rs","crates/spt/src/elevation.rs","docs/adr/0022-spt-hosted-idle-delivery-translation-binary.md","docs/adr/0032-boundary-rotation-credential.md","docs/adr/0027-unbound-endpoint-state-and-attach-on-session.md","docs/adr/0026-multi-subnet-home-at-endpoint-run-creation.md","docs/adr/0021-harness-hosted-adapter-agnostic-seed-and-bind-time-resolution.md","docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md","docs/adr/0018-broker-brain-process-isolation-restoration.md","docs/adr/0007-subnet-notification-primitive.md"],"truncated":false,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"},"isError":false,"timestamp":1784271580700}}
{"type":"message","id":"0d26a397","parentId":"6c99c0c0","timestamp":"2026-07-17T06:59:40.703Z","message":{"role":"toolResult","toolCallId":"call_bBikPsqa33abUdH9HiPYcayX|fc_089095e18118ee7d016a59d2dc8d348190b28264e2b301c73d","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## redispatch.rs\n 3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n*4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n 5|//! exactly like a production brain death). NO manual re-serve anywhere: the\n 6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n 7|//! hand-stepped a known stream and bypassed exactly the rediscovery/\n...\n 10|//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n*11|//!   endpoint/origin; restart the dispatcher generation only → the\n 12|//!   historical stream neither takes nor clears the current controller\n 13|//!   (post-restart input still renders; no EOF; no replay-storm log line;\n*14|//!   the retired row left the broker's enumeration).\n 15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n 16|//!   restart the dispatcher → durable classification (the pinned opener)\n 17|//!   resumes the SAME operator stream.\n...\n 27|use std::sync::{Arc, OnceLock};\n*28|use std::thread;\n 29|use std::time::{Duration, Instant};\n 30|\n 31|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 73|    let serve = Arc::clone(&broker);\n*74|    thread::spawn(move || {\n 75|        let _ = serve.serve();\n 76|    });\n 77|    broker\n...\n 84|        }\n*85|        thread::sleep(Duration::from_millis(10));\n 86|    }\n 87|    panic!(\"brain could not connect\");\n 88|}\n...\n 108|\n*109|/// One REAL dispatcher generation: the `dispatch_fixture` child running\n 110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n*112|struct Generation {\n 113|    child: Child,\n 114|    log: std::path::PathBuf,\n 115|}\n 116|\n*117|impl Generation {\n*118|    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119|        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120|        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121|        cmd.arg(broker_name)\n...\n 128|        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*129|        Generation { child, log }\n 130|    }\n 131|\n 132|    fn stderr_log(&self) -> String {\n...\n 154|/// Render the operator's viewport (dedup `Output` by seq — replays after a\n*155|/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156|/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157|/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n 158|/// Returns `true` if an EOF for the stream arrived while rendering (the\n...\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214|// active Attach on the same endpoint/origin; only the dispatcher generation\n 215|// restarts. The historical stream must neither take nor clear the current\n 216|// controller: post-restart input still renders (the frozen-PTY symptom is\n 217|// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218|// clear), the fresh generation logs NO replay of the historical stream id,\n*219|// and the retired row is GONE from the broker's enumeration\n*220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n 222|fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223|    init_home();\n...\n 234|\n*235|    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n 236|\n 237|    let mut operator = connect_retry(&name_b);\n 238|    let conn = operator\n...\n 251|\n*252|    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n 253|    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254|    let end = Instant::now() + Duration::from_secs(20);\n 255|    while peer_stream_ids(&mut probe).contains(&s1_a) {\n*256|        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n*257|        thread::sleep(Duration::from_millis(50));\n 258|    }\n 259|\n 260|    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n## redispatch_stall.rs\n 18|//!     stream's serve lease — the paired forwarding leg terminates at its\n*19|//!     next send; a new subscriber generation renews and serves again.\n 20|//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n 21|//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n 22|//!     recovers after the window (the not-abandonment discriminator).\n 23|//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n*24|//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n*25|//!     see) replayed by a fresh generation neither takes nor clears the live\n*26|//!     replacement controller (ownership/generation validation).\n 27|//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n 28|//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n 29|//!     the ring-peek fallback fires on the old broker's explicit\n...\n 32|//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n*33|//! binary alongside — the retire/steal regressions are substrate).\n 34|//!\n 35|//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n 36|//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n*37|//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n*38|//! `--test-threads=1` — tests set process-wide env knobs\n 39|//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n 40|\n 41|use std::process::{Child, Command, Stdio};\n...\n 43|use std::sync::{Arc, OnceLock};\n*44|use std::thread;\n 45|use std::time::{Duration, Instant};\n 46|\n 47|use spt_daemon::attach::{request_attach, send_attach_input};\n...\n 87|    let serve = Arc::clone(&broker);\n*88|    thread::spawn(move || {\n 89|        let _ = serve.serve();\n 90|    });\n 91|    broker\n...\n 98|        }\n*99|        thread::sleep(Duration::from_millis(10));\n 100|    }\n 101|    panic!(\"brain could not connect\");\n 102|}\n...\n 122|\n*123|/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124|/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125|/// brain death. Stderr lands in `log`.\n*126|struct Generation {\n 127|    child: Child,\n 128|    log: std::path::PathBuf,\n 129|}\n 130|\n*131|impl Generation {\n*132|    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 133|        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 134|        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 135|        cmd.arg(broker_name)\n...\n 142|        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*143|        Generation { child, log }\n 144|    }\n 145|\n 146|    fn stderr_log(&self) -> String {\n...\n 247|        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n*248|        thread::sleep(Duration::from_millis(25));\n 249|    };\n 250|    (operator, opened.stream_id, sid_a)\n 251|}\n...\n 294|                    );\n*295|                    thread::sleep(Duration::from_millis(200));\n 296|                    continue 'attach;\n 297|                }\n 298|                Ok(_) => {}\n...\n 354|        .expect(\"wedged subscribe send\");\n*355|    thread::sleep(Duration::from_millis(1200)); // > deadline: the seat has poisoned\n 356|\n 357|    // T3: the producer stays FLAT while the poisoned seat halts. Every ack'd\n 358|    // send round-trips B→A (QUIC transfer + ring append) — pre-fix each\n...\n 396|// REFUSED (\"lease canceled\") — the leg terminates instead of feeding an\n*397|// orphan forever. A new subscriber generation then renews the lease and a\n 398|// fresh send serves again (recovery, the C2-lease seam this wave builds).\n 399|#[test]\n 400|fn poisoned_subscriber_cancels_the_paired_forwarding_leg_then_recovery_serves() {\n...\n 415|    let (fw_tx, fw_rx) = std::sync::mpsc::channel::<(u32, String)>();\n*416|    let forwarder = thread::spawn(move || {\n 417|        let mut fw = connect_retry(&fw_name);\n 418|        let mut ok = 0u32;\n 419|        for i in 0..600u64 {\n\nSkipped missing paths: crates/spt-daemon/tests/daemon_refresh.rs"}],"details":{"scopePath":"crates/spt-daemon/tests/redispatch.rs, crates/spt-daemon/tests/redispatch_stall.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":40,"fileCount":2,"files":["crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/redispatch.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/tests/\n## redispatch.rs\n   3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n  *4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n   5│//! exactly like a production brain death). NO manual re-serve anywhere: the\n   6│//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n   7│//! hand-stepped a known stream and bypassed exactly the rediscovery/\n    │...\n  10│//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n *11│//!   endpoint/origin; restart the dispatcher generation only → the\n  12│//!   historical stream neither takes nor clears the current controller\n  13│//!   (post-restart input still renders; no EOF; no replay-storm log line;\n *14│//!   the retired row left the broker's enumeration).\n  15│//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n  16│//!   restart the dispatcher → durable classification (the pinned opener)\n  17│//!   resumes the SAME operator stream.\n    │...\n  27│use std::sync::{Arc, OnceLock};\n *28│use std::thread;\n  29│use std::time::{Duration, Instant};\n  30│\n  31│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  73│    let serve = Arc::clone(&broker);\n *74│    thread::spawn(move || {\n  75│        let _ = serve.serve();\n  76│    });\n  77│    broker\n    │...\n  84│        }\n *85│        thread::sleep(Duration::from_millis(10));\n  86│    }\n  87│    panic!(\"brain could not connect\");\n  88│}\n    │...\n 108│\n*109│/// One REAL dispatcher generation: the `dispatch_fixture` child running\n 110│/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111│/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n*112│struct Generation {\n 113│    child: Child,\n 114│    log: std::path::PathBuf,\n 115│}\n 116│\n*117│impl Generation {\n*118│    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119│        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120│        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121│        cmd.arg(broker_name)\n    │...\n 128│        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*129│        Generation { child, log }\n 130│    }\n 131│\n 132│    fn stderr_log(&self) -> String {\n    │...\n 154│/// Render the operator's viewport (dedup `Output` by seq — replays after a\n*155│/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156│/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157│/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n 158│/// Returns `true` if an EOF for the stream arrived while rendering (the\n    │...\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214│// active Attach on the same endpoint/origin; only the dispatcher generation\n 215│// restarts. The historical stream must neither take nor clear the current\n 216│// controller: post-restart input still renders (the frozen-PTY symptom is\n 217│// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218│// clear), the fresh generation logs NO replay of the historical stream id,\n*219│// and the retired row is GONE from the broker's enumeration\n*220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n 222│fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223│    init_home();\n    │...\n 234│\n*235│    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n 236│\n 237│    let mut operator = connect_retry(&name_b);\n 238│    let conn = operator\n    │...\n 251│\n*252│    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n 253│    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254│    let end = Instant::now() + Duration::from_secs(20);\n 255│    while peer_stream_ids(&mut probe).contains(&s1_a) {\n*256│        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n*257│        thread::sleep(Duration::from_millis(50));\n 258│    }\n 259│\n 260│    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n## redispatch_stall.rs\n  18│//!     stream's serve lease — the paired forwarding leg terminates at its\n *19│//!     next send; a new subscriber generation renews and serves again.\n  20│//!   - T5 `seat_blocked_failure_trips_...`: a seat-blocked worker failure\n  21│//!     trips the GLOBAL breaker, requeues budget-free, and the SAME stream\n  22│//!     recovers after the window (the not-abandonment discriminator).\n  23│//!   - T6 `stale_unfinished_attach_...`: an UNFINISHED-stale attach row\n *24│//!     (raw-close viewport, no FIN — the shape finished-row retirement cannot\n *25│//!     see) replayed by a fresh generation neither takes nor clears the live\n *26│//!     replacement controller (ownership/generation validation).\n  27│//!   - T7 `old_broker_fallback_...`: mixed-image N-1 with REAL\n  28│//!     traffic-carrying streams (doyle rider: handshake-only insufficient) —\n  29│//!     the ring-peek fallback fires on the old broker's explicit\n    │...\n  32│//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n *33│//! binary alongside — the retire/steal regressions are substrate).\n  34│//!\n  35│//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n  36│//! brokers; several spawn real PTY children and/or killable dispatch_fixture\n *37│//! generations. RUNNER CONTRACT: nextest (process-per-test) or\n *38│//! `--test-threads=1` — tests set process-wide env knobs\n  39│//! (SPT_BRAIN_WRITE_DEADLINE_MS, SPT_TEST_STREAM_OPENER_UNSUPPORTED).\n  40│\n  41│use std::process::{Child, Command, Stdio};\n    │...\n  43│use std::sync::{Arc, OnceLock};\n *44│use std::thread;\n  45│use std::time::{Duration, Instant};\n  46│\n  47│use spt_daemon::attach::{request_attach, send_attach_input};\n    │...\n  87│    let serve = Arc::clone(&broker);\n *88│    thread::spawn(move || {\n  89│        let _ = serve.serve();\n  90│    });\n  91│    broker\n    │...\n  98│        }\n *99│        thread::sleep(Duration::from_millis(10));\n 100│    }\n 101│    panic!(\"brain could not connect\");\n 102│}\n    │...\n 122│\n*123│/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124│/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125│/// brain death. Stderr lands in `log`.\n*126│struct Generation {\n 127│    child: Child,\n 128│    log: std::path::PathBuf,\n 129│}\n 130│\n*131│impl Generation {\n*132│    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 133│        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 134│        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 135│        cmd.arg(broker_name)\n    │...\n 142│        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*143│        Generation { child, log }\n 144│    }\n 145│\n 146│    fn stderr_log(&self) -> String {\n    │...\n 247│        assert!(Instant::now() < end, \"the peer stream row never registered on A\");\n*248│        thread::sleep(Duration::from_millis(25));\n 249│    };\n 250│    (operator, opened.stream_id, sid_a)\n 251│}\n    │...\n 294│                    );\n*295│                    thread::sleep(Duration::from_millis(200));\n 296│                    continue 'attach;\n 297│                }\n 298│                Ok(_) => {}\n    │...\n 354│        .expect(\"wedged subscribe send\");\n*355│    thread::sleep(Duration::from_millis(1200)); // > deadline: the seat has poisoned\n 356│\n 357│    // T3: the producer stays FLAT while the poisoned seat halts. Every ack'd\n 358│    // send round-trips B→A (QUIC transfer + ring append) — pre-fix each\n    │...\n 396│// REFUSED (\"lease canceled\") — the leg terminates instead of feeding an\n*397│// orphan forever. A new subscriber generation then renews the lease and a\n 398│// fresh send serves again (recovery, the C2-lease seam this wave builds).\n 399│#[test]\n 400│fn poisoned_subscriber_cancels_the_paired_forwarding_leg_then_recovery_serves() {\n    │...\n 415│    let (fw_tx, fw_rx) = std::sync::mpsc::channel::<(u32, String)>();\n*416│    let forwarder = thread::spawn(move || {\n 417│        let mut fw = connect_retry(&fw_name);\n 418│        let mut ok = 0u32;\n 419│        for i in 0..600u64 {","missingPaths":["crates/spt-daemon/tests/daemon_refresh.rs"]},"isError":false,"timestamp":1784271580703}}
{"type":"message","id":"d80cdf9b","parentId":"0d26a397","timestamp":"2026-07-17T06:59:40.706Z","message":{"role":"toolResult","toolCallId":"call_JeWvF3h3X7xIwSMW4XGA6x6r|fc_089095e18118ee7d016a59d2dc8d3081908f8ffd1aa1361abe","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## tests/\n### brain_decouple.rs\n 401|//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\n*402|//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\n 403|//     client ever dropping its end;                                    ← RED pre-fix\n 404|//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n 405|// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n...\n 677|\n*678|    // ── Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads\n 679|    //    ZERO (a zero sample tolerates a transient query conn briefly bumping it),\n 680|    //    bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed\n 681|    //    conn's handler never exits until the client drops) = the RED. ──\n...\n 685|    while Instant::now() < step7_deadline {\n*686|        if broker.conn_handler_count() == 0 {\n 687|            handler_released = true;\n 688|            break;\n 689|        }\n...\n 810|        handler_released,\n*811|        \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\\n 812|         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n 813|         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 814|         detached writer stays wedged in write_frame and the handler never exits.\"\n### conn_blackhole_lifecycle.rs\n 23|//!      physically retired, not merely logically stall-evicted);\n*24|//!   3. ITS WRITER EXITS — `broker.conn_handler_count()` falls to the\n 25|//!      unrelated-viewer floor (<= 1; the persistent invariant-1 viewer holds one\n 26|//!      handler) WITHOUT the black-holed client ever dropping its end (the parked\n 27|//!      writer was aborted + the reader EOF-cleaned, so the handler thread ran to\n...\n 35|//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n*36|//!      is held by the retired writer), `conn_handler_count()` returns to the\n 37|//!      unrelated-viewer floor without the client dropping its end (no task\n 38|//!      lingers), AND both the\n 39|//!      unrelated session's viewer and the fresh viewer flow output (the shared\n...\n 49|//! gate for that hazard). It reuses the same SEED/trigger/burst choreography,\n*50|//! the hand-back-the-unread-Stream pattern, the `conn_handler_count` and\n 51|//! `stall_evict_count` seams, the old-client-write-fails probe, and the\n 52|//! teardown-before-assert discipline. It ADDS the two pieces the r4 gate does\n 53|//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n...\n 470|    // ── INVARIANT 3 (its writer exits): WITHOUT dropping R's Stream, poll\n*471|    //    conn_handler_count() until it drops to the UNRELATED-VIEWER FLOOR (<= 1),\n 472|    //    bounded at t_release + 2 s. `conn_handlers` is a GLOBAL atomic of live\n 473|    //    handle_conn threads, incremented once per physical conn. Unlike the r4\n 474|    //    gate (whose only persistent conn was the black-holed R, so its floor was\n...\n 494|    while Instant::now() < step7_deadline {\n*495|        if broker.conn_handler_count() <= 1 {\n 496|            handler_released = true;\n 497|            u_at_release = u_frames.load(Ordering::Relaxed);\n 498|            break;\n...\n 656|    // ── INVARIANT 3: ITS WRITER EXITS — the black-holed conn's handle_conn thread\n*657|    //    finishes (conn_handler_count → the <=1 unrelated-viewer floor) within 2 s\n 658|    //    of logical release, WITHOUT the client dropping its end. ──\n 659|    assert!(\n 660|        handler_released,\n 661|        \"INVARIANT 3: the black-holed connection's writer must EXIT — \\\n*662|         conn_handler_count must fall to the unrelated-viewer floor (<= 1) within 2 s \\\n 663|         of logical release WITHOUT the client dropping its end (while R's handler is \\\n 664|         alive the global count is >= 2, so <= 1 proves R's handler finished). A stuck \\\n 665|         count means the detached writer stays wedged in write_frame and the handler \\\n### redispatch_stall.rs\n 624|    assert!(\n*625|        log.contains(\"event=breaker-trip\") && log.contains(\"window_ms=2000\"),\n*626|        \"the trip must land the keyed breaker-trip record with the base window: {log}\"\n 627|    );\n 628|    // Budget NOT burned: exactly one Failed outcome, then the recovery serve\n 629|    // (Served outcomes log no DISPATCH line).\n\n## src/\n### broker.rs\n 2921|    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2922|    pub fn conn_handler_count(&self) -> usize {\n 2923|        self.conn_handlers.load(Ordering::Acquire)\n 2924|    }\n 2925|\n...\n 3026|        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n*3027|        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028|        struct HandlerTally<'a>(&'a AtomicUsize);\n 3029|        impl Drop for HandlerTally<'_> {\n 3030|            fn drop(&mut self) {\n### conn.rs\n 264|    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\n*265|    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\n 266|    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\n 267|    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\n 268|    /// timeout/cancel leg is the retirement record itself.\n...\n 640|    fn drop(&mut self) {\n*641|        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n 642|        // is about to close — the terminal record of this conn id's life, with\n 643|        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\n 644|        self.inner.lifecycle(\n*645|            \"transport-close\",\n 646|            &format!(\n 647|                \"poisoned={} timed_out={}\",\n 648|                self.inner.poisoned.load(Ordering::Acquire),\n### dispatch.rs\n 452|    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n*453|    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n 454|\n 455|    while !stop.load(Ordering::Relaxed) {\n 456|        if breaker.lock().unwrap().open(Instant::now()) {\n...\n 472|            }\n*473|            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n 474|                break; // pool full: the rest of the table waits for the next poll\n 475|            }\n 476|            let attempts = {\n...\n 495|            let breaker_done = Arc::clone(&breaker);\n*496|            let pool = Arc::clone(&active_workers);\n 497|            pool.fetch_add(1, Ordering::Relaxed);\n 498|            thread::spawn(move || {\n 499|                let started = Instant::now();\n...\n 523|                } else {\n*524|                    // Keyed worker-outcome record (Amendment fix 9) — the\n 525|                    // legacy DISPATCH:<id> token is kept as a prefix so\n 526|                    // existing field greps keep matching.\n 527|                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n...\n 536|                            conn_id,\n*537|                            \"worker-outcome\",\n 538|                            &format!(\n 539|                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n 540|                                report.endpoint.as_deref().unwrap_or(\"-\")\n...\n 561|                                conn_id,\n*562|                                \"breaker-trip\",\n 563|                                &format!(\"window_ms={}\", window.as_millis()),\n 564|                            )\n 565|                        );\n...\n 1451|            41,\n*1452|            \"worker-outcome\",\n 1453|            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n 1454|        );\n 1455|        for key in [\n...\n 1460|            \"conn=41\",\n*1461|            \"event=worker-outcome\",\n 1462|            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n 1463|        ] {\n 1464|            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n...\n 1468|        // Pre-classification events name the family honestly.\n*1469|        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n 1470|        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n 1471|    }\n 1472|\n### nethost.rs\n 541|        sub.lifecycle_event(\n*542|            \"stream-sub-attach\",\n 543|            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n 544|        );\n 545|        let mut replay: Vec<Envelope> = self"}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":28,"fileCount":7,"files":["crates/spt-daemon/tests/brain_decouple.rs","crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","crates/spt-daemon/tests/redispatch_stall.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/conn.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":4},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","count":7},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":2},{"path":"crates/spt-daemon/src/broker.rs","count":2},{"path":"crates/spt-daemon/src/conn.rs","count":3},{"path":"crates/spt-daemon/src/dispatch.rs","count":9},{"path":"crates/spt-daemon/src/nethost.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/\n\n## tests/\n### brain_decouple.rs\n 401│//   - the black-holed conn's `handle_conn` thread FINISHES within the write bound\n*402│//     of logical release (broker.conn_handler_count() falls to 0) WITHOUT the\n 403│//     client ever dropping its end;                                    ← RED pre-fix\n 404│//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n 405│// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n    │...\n 677│\n*678│    // ── Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads\n 679│    //    ZERO (a zero sample tolerates a transient query conn briefly bumping it),\n 680│    //    bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed\n 681│    //    conn's handler never exits until the client drops) = the RED. ──\n    │...\n 685│    while Instant::now() < step7_deadline {\n*686│        if broker.conn_handler_count() == 0 {\n 687│            handler_released = true;\n 688│            break;\n 689│        }\n    │...\n 810│        handler_released,\n*811│        \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\\n 812│         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n 813│         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 814│         detached writer stays wedged in write_frame and the handler never exits.\"\n### conn_blackhole_lifecycle.rs\n  23│//!      physically retired, not merely logically stall-evicted);\n *24│//!   3. ITS WRITER EXITS — `broker.conn_handler_count()` falls to the\n  25│//!      unrelated-viewer floor (<= 1; the persistent invariant-1 viewer holds one\n  26│//!      handler) WITHOUT the black-holed client ever dropping its end (the parked\n  27│//!      writer was aborted + the reader EOF-cleaned, so the handler thread ran to\n    │...\n  35│//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n *36│//!      is held by the retired writer), `conn_handler_count()` returns to the\n  37│//!      unrelated-viewer floor without the client dropping its end (no task\n  38│//!      lingers), AND both the\n  39│//!      unrelated session's viewer and the fresh viewer flow output (the shared\n    │...\n  49│//! gate for that hazard). It reuses the same SEED/trigger/burst choreography,\n *50│//! the hand-back-the-unread-Stream pattern, the `conn_handler_count` and\n  51│//! `stall_evict_count` seams, the old-client-write-fails probe, and the\n  52│//! teardown-before-assert discipline. It ADDS the two pieces the r4 gate does\n  53│//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n    │...\n 470│    // ── INVARIANT 3 (its writer exits): WITHOUT dropping R's Stream, poll\n*471│    //    conn_handler_count() until it drops to the UNRELATED-VIEWER FLOOR (<= 1),\n 472│    //    bounded at t_release + 2 s. `conn_handlers` is a GLOBAL atomic of live\n 473│    //    handle_conn threads, incremented once per physical conn. Unlike the r4\n 474│    //    gate (whose only persistent conn was the black-holed R, so its floor was\n    │...\n 494│    while Instant::now() < step7_deadline {\n*495│        if broker.conn_handler_count() <= 1 {\n 496│            handler_released = true;\n 497│            u_at_release = u_frames.load(Ordering::Relaxed);\n 498│            break;\n    │...\n 656│    // ── INVARIANT 3: ITS WRITER EXITS — the black-holed conn's handle_conn thread\n*657│    //    finishes (conn_handler_count → the <=1 unrelated-viewer floor) within 2 s\n 658│    //    of logical release, WITHOUT the client dropping its end. ──\n 659│    assert!(\n 660│        handler_released,\n 661│        \"INVARIANT 3: the black-holed connection's writer must EXIT — \\\n*662│         conn_handler_count must fall to the unrelated-viewer floor (<= 1) within 2 s \\\n 663│         of logical release WITHOUT the client dropping its end (while R's handler is \\\n 664│         alive the global count is >= 2, so <= 1 proves R's handler finished). A stuck \\\n 665│         count means the detached writer stays wedged in write_frame and the handler \\\n### redispatch_stall.rs\n 624│    assert!(\n*625│        log.contains(\"event=breaker-trip\") && log.contains(\"window_ms=2000\"),\n*626│        \"the trip must land the keyed breaker-trip record with the base window: {log}\"\n 627│    );\n 628│    // Budget NOT burned: exactly one Failed outcome, then the recovery serve\n 629│    // (Served outcomes log no DISPATCH line).\n\n## src/\n### broker.rs\n 2921│    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2922│    pub fn conn_handler_count(&self) -> usize {\n 2923│        self.conn_handlers.load(Ordering::Acquire)\n 2924│    }\n 2925│\n     │...\n 3026│        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n*3027│        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028│        struct HandlerTally<'a>(&'a AtomicUsize);\n 3029│        impl Drop for HandlerTally<'_> {\n 3030│            fn drop(&mut self) {\n### conn.rs\n 264│    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\n*265│    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\n 266│    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\n 267│    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\n 268│    /// timeout/cancel leg is the retirement record itself.\n    │...\n 640│    fn drop(&mut self) {\n*641│        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n 642│        // is about to close — the terminal record of this conn id's life, with\n 643│        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\n 644│        self.inner.lifecycle(\n*645│            \"transport-close\",\n 646│            &format!(\n 647│                \"poisoned={} timed_out={}\",\n 648│                self.inner.poisoned.load(Ordering::Acquire),\n### dispatch.rs\n  452│    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n *453│    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n  454│\n  455│    while !stop.load(Ordering::Relaxed) {\n  456│        if breaker.lock().unwrap().open(Instant::now()) {\n     │...\n  472│            }\n *473│            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n  474│                break; // pool full: the rest of the table waits for the next poll\n  475│            }\n  476│            let attempts = {\n     │...\n  495│            let breaker_done = Arc::clone(&breaker);\n *496│            let pool = Arc::clone(&active_workers);\n  497│            pool.fetch_add(1, Ordering::Relaxed);\n  498│            thread::spawn(move || {\n  499│                let started = Instant::now();\n     │...\n  523│                } else {\n *524│                    // Keyed worker-outcome record (Amendment fix 9) — the\n  525│                    // legacy DISPATCH:<id> token is kept as a prefix so\n  526│                    // existing field greps keep matching.\n  527│                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n     │...\n  536│                            conn_id,\n *537│                            \"worker-outcome\",\n  538│                            &format!(\n  539│                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n  540│                                report.endpoint.as_deref().unwrap_or(\"-\")\n     │...\n  561│                                conn_id,\n *562│                                \"breaker-trip\",\n  563│                                &format!(\"window_ms={}\", window.as_millis()),\n  564│                            )\n  565│                        );\n     │...\n 1451│            41,\n*1452│            \"worker-outcome\",\n 1453│            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n 1454│        );\n 1455│        for key in [\n     │...\n 1460│            \"conn=41\",\n*1461│            \"event=worker-outcome\",\n 1462│            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n 1463│        ] {\n 1464│            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n     │...\n 1468│        // Pre-classification events name the family honestly.\n*1469│        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n 1470│        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n 1471│    }\n 1472│\n### nethost.rs\n 541│        sub.lifecycle_event(\n*542│            \"stream-sub-attach\",\n 543│            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n 544│        );\n 545│        let mut replay: Vec<Envelope> = self"},"isError":false,"timestamp":1784271580706}}
{"type":"message","id":"8dcdbc8a","parentId":"d80cdf9b","timestamp":"2026-07-17T06:59:40.753Z","message":{"role":"toolResult","toolCallId":"call_h3mjye6YMAhNExzUbHo6iXhg|fc_089095e18118ee7d016a59d2dc8d288190b7c8b13309084340","toolName":"read","content":[{"type":"text","text":"        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session\n        // OR a spawn in flight — closing the check-then-spawn TOCTOU that let one wake\n        // spawn two identical launch trees (perri's flynn dup: duplicate perch writers\n        // then stomped info.json, routing injects to a contended record). The claim +\n        // the live-session check are read together under one atomic critical section so\n        // a concurrent spawn either sees our claim or its registered session — never a\n        // gap; NO I/O under the lock (process_id reads a cached pid). An empty endpoint\n        // is an ad-hoc/legacy spawn (no id to dedup on) → never gated. `_wake_claim`\n        // holds the claim to fn end, releasing (RAII) after the session is table-visible\n        // or on any early error.\n        // [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\n        let _wake_claim: Option<WakeClaimGuard> = if req.endpoint.is_empty() {\n            None\n        } else {\n            let deadline = Instant::now() + WAKE_DEDUP_WAIT;\n            loop {\n                enum Gate<'a> {\n                    Claimed(WakeClaimGuard<'a>),\n                    AlreadyLive(u64, Option<u32>),\n                    Racing,\n                }\n                let gate = {\n                    let sessions = recover(&self.sessions);\n                    let mut inflight = recover(&self.wake_inflight);\n                    let live = sessions\n                        .iter()\n                        .find(|(_, h)| h.endpoint == req.endpoint)\n                        .map(|(sid, h)| (*sid, h.session.process_id()));\n                    match wake_gate_decision(\n                        live.is_some(),\n                        inflight.contains(&req.endpoint),\n                        Instant::now() >= deadline,\n                    ) {\n                        WakeGate::AlreadyLive => {\n                            let (sid, spid) = live.expect(\"live is Some on AlreadyLive\");\n                            Gate::AlreadyLive(sid, spid)\n                        }\n                        WakeGate::Racing => Gate::Racing,\n                        WakeGate::Claim => {\n                            inflight.insert(req.endpoint.clone());\n                            Gate::Claimed(WakeClaimGuard {\n                                set: &self.wake_inflight,\n                                endpoint: req.endpoint.clone(),\n                            })\n                        }\n                    }\n                };\n                match gate {\n                    Gate::Claimed(guard) => break Some(guard),\n                    Gate::AlreadyLive(sid, spid) => {\n                        eprintln!(\n                            \"SPAWN_DEDUP:{}: endpoint already has a live session {sid} — \\\n                             single-flight wake no-op ack [REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\",\n                            req.endpoint\n                        );\n                        let frame = Envelope::new(\n                            KIND_SPAWNED,\n                            serde_json::to_value(Spawned { session_id: sid, pid: spid })\n                                .expect(\"Spawned serializes\"),\n                        );\n                        send_frame(send, &frame);\n                        return Ok(sid);\n                    }\n                    Gate::Racing => thread::sleep(WAKE_DEDUP_POLL),\n                }\n            }\n        };\n        // REQ-HOST-RUN-2: spawn in the requested project cwd when carried;\n        // `None` inherits the broker's cwd (legacy/test + W1 bringup shortcut).\n        // Inject the manifest [env] vars (REQ-HAZARD-ENV-SUBST) on top of the\n        // inherited env — empty for a legacy/N-1 spawn (inherit only).\n        let session = PtySession::spawn_program_in_env(\n            &req.program,\n            &req.args,\n            SurfaceSize::new(req.rows, req.cols),\n            req.cwd.as_deref(),\n            req.env.iter().map(|(k, v)| (k.as_str(), v.as_str())),\n        )\n        .map_err(|e| format!(\"spawn failed: {e}\"))?;\n        let session = Arc::new(session);\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\n        let pid = session.process_id();\n\n        // The session's lone PTY input-writer (P0): the SOLE caller of the\n        // blocking `write_input`. Every dispatch-side write enqueues here, so a\n        // paste burst parks only this thread — never the broker dispatch thread\n        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\n        // too, so the lone writer enforces the W2 inject-floor order.\n        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());\n\n        // The output log, with the spawning connection pre-attached as the\n        // controller (identity `None` = LOCAL, not a remote driver) so no chunk\n        // is missed before an explicit subscribe — and so a remote attach to a\n        // spawned-but-undriven session takes control silently (no loud displace\n        // of the local-spawner placeholder; REQ-RCVIEW-1).\n        let log = Arc::new(Mutex::new(OutputLog::new(\n            id,\n            self.log_cap_chunks,\n            req.endpoint.clone(),\n            (req.rows, req.cols),\n        )));\n        log.lock()\n            .unwrap()\n            .become_controller(Arc::clone(send), None, 0, 0);\n\n        // Output drain → append under the log lock (fast: ring + viewer fan-out),\n        // then deliver to the controller OFF the lock with a bounded deadline\n        // (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no\n        // longer inline under the log lock, so a backed-up controller can never\n        // wedge the session; a fully-stalled controller is detached after the\n        // deadline (bounded wedge, never permanent).\n\n[Showing lines 3299-3413 of 6298. Use :3414 to continue]"}],"details":{"truncation":{"content":"        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session\n        // OR a spawn in flight — closing the check-then-spawn TOCTOU that let one wake\n        // spawn two identical launch trees (perri's flynn dup: duplicate perch writers\n        // then stomped info.json, routing injects to a contended record). The claim +\n        // the live-session check are read together under one atomic critical section so\n        // a concurrent spawn either sees our claim or its registered session — never a\n        // gap; NO I/O under the lock (process_id reads a cached pid). An empty endpoint\n        // is an ad-hoc/legacy spawn (no id to dedup on) → never gated. `_wake_claim`\n        // holds the claim to fn end, releasing (RAII) after the session is table-visible\n        // or on any early error.\n        // [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\n        let _wake_claim: Option<WakeClaimGuard> = if req.endpoint.is_empty() {\n            None\n        } else {\n            let deadline = Instant::now() + WAKE_DEDUP_WAIT;\n            loop {\n                enum Gate<'a> {\n                    Claimed(WakeClaimGuard<'a>),\n                    AlreadyLive(u64, Option<u32>),\n                    Racing,\n                }\n                let gate = {\n                    let sessions = recover(&self.sessions);\n                    let mut inflight = recover(&self.wake_inflight);\n                    let live = sessions\n                        .iter()\n                        .find(|(_, h)| h.endpoint == req.endpoint)\n                        .map(|(sid, h)| (*sid, h.session.process_id()));\n                    match wake_gate_decision(\n                        live.is_some(),\n                        inflight.contains(&req.endpoint),\n                        Instant::now() >= deadline,\n                    ) {\n                        WakeGate::AlreadyLive => {\n                            let (sid, spid) = live.expect(\"live is Some on AlreadyLive\");\n                            Gate::AlreadyLive(sid, spid)\n                        }\n                        WakeGate::Racing => Gate::Racing,\n                        WakeGate::Claim => {\n                            inflight.insert(req.endpoint.clone());\n                            Gate::Claimed(WakeClaimGuard {\n                                set: &self.wake_inflight,\n                                endpoint: req.endpoint.clone(),\n                            })\n                        }\n                    }\n                };\n                match gate {\n                    Gate::Claimed(guard) => break Some(guard),\n                    Gate::AlreadyLive(sid, spid) => {\n                        eprintln!(\n                            \"SPAWN_DEDUP:{}: endpoint already has a live session {sid} — \\\n                             single-flight wake no-op ack [REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\",\n                            req.endpoint\n                        );\n                        let frame = Envelope::new(\n                            KIND_SPAWNED,\n                            serde_json::to_value(Spawned { session_id: sid, pid: spid })\n                                .expect(\"Spawned serializes\"),\n                        );\n                        send_frame(send, &frame);\n                        return Ok(sid);\n                    }\n                    Gate::Racing => thread::sleep(WAKE_DEDUP_POLL),\n                }\n            }\n        };\n        // REQ-HOST-RUN-2: spawn in the requested project cwd when carried;\n        // `None` inherits the broker's cwd (legacy/test + W1 bringup shortcut).\n        // Inject the manifest [env] vars (REQ-HAZARD-ENV-SUBST) on top of the\n        // inherited env — empty for a legacy/N-1 spawn (inherit only).\n        let session = PtySession::spawn_program_in_env(\n            &req.program,\n            &req.args,\n            SurfaceSize::new(req.rows, req.cols),\n            req.cwd.as_deref(),\n            req.env.iter().map(|(k, v)| (k.as_str(), v.as_str())),\n        )\n        .map_err(|e| format!(\"spawn failed: {e}\"))?;\n        let session = Arc::new(session);\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\n        let pid = session.process_id();\n\n        // The session's lone PTY input-writer (P0): the SOLE caller of the\n        // blocking `write_input`. Every dispatch-side write enqueues here, so a\n        // paste burst parks only this thread — never the broker dispatch thread\n        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\n        // too, so the lone writer enforces the W2 inject-floor order.\n        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());\n\n        // The output log, with the spawning connection pre-attached as the\n        // controller (identity `None` = LOCAL, not a remote driver) so no chunk\n        // is missed before an explicit subscribe — and so a remote attach to a\n        // spawned-but-undriven session takes control silently (no loud displace\n        // of the local-spawner placeholder; REQ-RCVIEW-1).\n        let log = Arc::new(Mutex::new(OutputLog::new(\n            id,\n            self.log_cap_chunks,\n            req.endpoint.clone(),\n            (req.rows, req.cols),\n        )));\n        log.lock()\n            .unwrap()\n            .become_controller(Arc::clone(send), None, 0, 0);\n\n        // Output drain → append under the log lock (fast: ring + viewer fan-out),\n        // then deliver to the controller OFF the lock with a bounded deadline\n        // (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no\n        // longer inline under the log lock, so a backed-up controller can never\n        // wedge the session; a fully-stalled controller is detached after the\n        // deadline (bounded wedge, never permanent).","truncated":true,"truncatedBy":"lines","totalLines":3000,"totalBytes":6136,"outputLines":115,"outputBytes":6136,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session\n        // OR a spawn in flight — closing the check-then-spawn TOCTOU that let one wake\n        // spawn two identical launch trees (perri's flynn dup: duplicate perch writers\n        // then stomped info.json, routing injects to a contended record). The claim +\n        // the live-session check are read together under one atomic critical section so\n        // a concurrent spawn either sees our claim or its registered session — never a\n        // gap; NO I/O under the lock (process_id reads a cached pid). An empty endpoint\n        // is an ad-hoc/legacy spawn (no id to dedup on) → never gated. `_wake_claim`\n        // holds the claim to fn end, releasing (RAII) after the session is table-visible\n        // or on any early error.\n        // [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\n        let _wake_claim: Option<WakeClaimGuard> = if req.endpoint.is_empty() {\n            None\n        } else {\n            let deadline = Instant::now() + WAKE_DEDUP_WAIT;\n            loop {\n                enum Gate<'a> {\n                    Claimed(WakeClaimGuard<'a>),\n                    AlreadyLive(u64, Option<u32>),\n                    Racing,\n                }\n                let gate = {\n                    let sessions = recover(&self.sessions);\n                    let mut inflight = recover(&self.wake_inflight);\n                    let live = sessions\n                        .iter()\n                        .find(|(_, h)| h.endpoint == req.endpoint)\n                        .map(|(sid, h)| (*sid, h.session.process_id()));\n                    match wake_gate_decision(\n                        live.is_some(),\n                        inflight.contains(&req.endpoint),\n                        Instant::now() >= deadline,\n                    ) {\n                        WakeGate::AlreadyLive => {\n                            let (sid, spid) = live.expect(\"live is Some on AlreadyLive\");\n                            Gate::AlreadyLive(sid, spid)\n                        }\n                        WakeGate::Racing => Gate::Racing,\n                        WakeGate::Claim => {\n                            inflight.insert(req.endpoint.clone());\n                            Gate::Claimed(WakeClaimGuard {\n                                set: &self.wake_inflight,\n                                endpoint: req.endpoint.clone(),\n                            })\n                        }\n                    }\n                };\n                match gate {\n                    Gate::Claimed(guard) => break Some(guard),\n                    Gate::AlreadyLive(sid, spid) => {\n                        eprintln!(\n                            \"SPAWN_DEDUP:{}: endpoint already has a live session {sid} — \\\n                             single-flight wake no-op ack [REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]\",\n                            req.endpoint\n                        );\n                        let frame = Envelope::new(\n                            KIND_SPAWNED,\n                            serde_json::to_value(Spawned { session_id: sid, pid: spid })\n                                .expect(\"Spawned serializes\"),\n                        );\n                        send_frame(send, &frame);\n                        return Ok(sid);\n                    }\n                    Gate::Racing => thread::sleep(WAKE_DEDUP_POLL),\n                }\n            }\n        };\n        // REQ-HOST-RUN-2: spawn in the requested project cwd when carried;\n        // `None` inherits the broker's cwd (legacy/test + W1 bringup shortcut).\n        // Inject the manifest [env] vars (REQ-HAZARD-ENV-SUBST) on top of the\n        // inherited env — empty for a legacy/N-1 spawn (inherit only).\n        let session = PtySession::spawn_program_in_env(\n            &req.program,\n            &req.args,\n            SurfaceSize::new(req.rows, req.cols),\n            req.cwd.as_deref(),\n            req.env.iter().map(|(k, v)| (k.as_str(), v.as_str())),\n        )\n        .map_err(|e| format!(\"spawn failed: {e}\"))?;\n        let session = Arc::new(session);\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\n        let pid = session.process_id();\n\n        // The session's lone PTY input-writer (P0): the SOLE caller of the\n        // blocking `write_input`. Every dispatch-side write enqueues here, so a\n        // paste burst parks only this thread — never the broker dispatch thread\n        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\n        // too, so the lone writer enforces the W2 inject-floor order.\n        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());\n\n        // The output log, with the spawning connection pre-attached as the\n        // controller (identity `None` = LOCAL, not a remote driver) so no chunk\n        // is missed before an explicit subscribe — and so a remote attach to a\n        // spawned-but-undriven session takes control silently (no loud displace\n        // of the local-spawner placeholder; REQ-RCVIEW-1).\n        let log = Arc::new(Mutex::new(OutputLog::new(\n            id,\n            self.log_cap_chunks,\n            req.endpoint.clone(),\n            (req.rows, req.cols),\n        )));\n        log.lock()\n            .unwrap()\n            .become_controller(Arc::clone(send), None, 0, 0);\n\n        // Output drain → append under the log lock (fast: ring + viewer fan-out),\n        // then deliver to the controller OFF the lock with a bounded deadline\n        // (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no\n        // longer inline under the log lock, so a backed-up controller can never\n        // wedge the session; a fully-stalled controller is detached after the\n        // deadline (bounded wedge, never permanent).","startLine":3299,"lineNumbers":[3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,3327,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":6136,"outputLines":115,"outputBytes":6136,"shownRange":{"start":3299,"end":3413},"nextOffset":3414}}},"isError":false,"timestamp":1784271580753}}
{"type":"message","id":"61a71061","parentId":"8dcdbc8a","timestamp":"2026-07-17T06:59:50.761Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Examining RCA artifact summaries**"},{"type":"toolCall","id":"call_rOx9TUWKOSjB9cHZNKBpJVUV|fc_089095e18118ee7d016a59d2e679fc81908740a74f05b88e4d","name":"read","arguments":{"i":"Listing stall RCA artifacts","path":".claude/reports/2026-07-16-redispatch-stall","selector":null}},{"type":"toolCall","id":"call_rk6SnPe6tWVdOwOVzOrT1J6D|fc_089095e18118ee7d016a59d2e67a1081909a3355c3b12daf0b","name":"read","arguments":{"i":"Listing truth RCA artifacts","path":".claude/reports/2026-07-16-redispatch-truth","selector":null}},{"type":"toolCall","id":"call_Vc8Z26X2KKgq97oATh43cPcp|fc_089095e18118ee7d016a59d2e67a148190a772141845e7d21e","name":"grep","arguments":{"i":"Searching RCA retained workers","pattern":"thread|worker|replay|attach|conn|churn|CPU|I/O|4361|retained|leak","path":".claude/reports/2026-07-16-redispatch-stall; .claude/reports/2026-07-16-redispatch-truth","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_YeDPuaBUgbwFiolA1imVUnax|fc_089095e18118ee7d016a59d2e67a1c8190aeb7976a24ba0694","name":"read","arguments":{"i":"Reading exact handler cleanup","path":"crates/spt-daemon/src/broker.rs","selector":"3255-3295"}},{"type":"toolCall","id":"call_j1TnRCe9HEzMIxl9P2W3Kxei|fc_089095e18118ee7d016a59d2e67a20819085d8d10c5b5b33c6","name":"read","arguments":{"i":"Reading watchdog drop exact","path":"crates/spt-daemon/src/conn.rs","selector":"630-665"}},{"type":"toolCall","id":"call_TwcefNDPK2f088r1rKw4zUAx|fc_089095e18118ee7d016a59d2e67a248190951b4a46293a7e16","name":"read","arguments":{"i":"Reading subscriber unit seams","path":"crates/spt-daemon/src/nethost.rs","selector":"2380-2505,2506-2805"}},{"type":"toolCall","id":"call_MTqxhld5qiT6bkQjwG4rdMYq|fc_089095e18118ee7d016a59d2e67a2881909433e76d4e65ce65","name":"read","arguments":{"i":"Reading dispatch final worker outcomes","path":"crates/spt-daemon/src/dispatch.rs","selector":"900-1220"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8564,"output":384,"cacheRead":164352,"cacheWrite":0,"totalTokens":173300,"reasoningTokens":27,"cost":{"input":0.042820000000000004,"output":0.01152,"cacheRead":0.082176,"cacheWrite":0,"total":0.136516}},"stopReason":"toolUse","timestamp":1784271580794,"responseId":"resp_089095e18118ee7d016a59d2dd15d88190adb9a65885a1e475","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2df88348190a787b60a1ff46a3c","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdLm75VJSnlKlGIh4kKPXOjXXnu1Cobbv1cHfXTEKc7Ds_B6CVTvgHk3EcnkHgeC8Vbn97ACU9dhG6jdZBRUvTtUaKW8Oblx-zf7skUMEglrmM9qJRMZttk-qC5_EvmJPck59Q3Y_p0OYYfbHPGIfTZHeNA3d_hVJlp_YnZDqYL6LvXlzdnuJpZdzCzy9jK_47QuWpi229m0uY_6iA2A1eeWZboTk_tjywzDSwwuvK-lQTfbXuCGH2aZLqeQ_IoDu2RV6k5lIICESnVtdbtdvH3n2rKlEW_6QrHYrVUMuQfvg9Qsf_3MFYGIU5p46VX0CRqMjvSmS7jT49qgzqjaZh7mOqGk1DRKTBqep3l-Cha_F-z4BLDgZBqopvXmMCz2KcV3BDZjvIx02QxYOqXt3j1vLr9qewjRS2SS7hLXhDplz-dR3APvMjzTuvjBswbVSFwh83QQsFiEjGYjSZ26tlUcQ_GvUw1OyS4GzIWNt0_VnCjF1PmVt5WqPmwO8P3jnl4x2PiADFclqyMGX28wPXkOZwK8Bdz05havTmYJubRkMcyInCV1sM5i7httM5HBvxIOEHpRFaF142eRr4y5Q-9fPHBokCiSVukZ3gUDLdnOa9oZ_P7A749zRN0Q1kiIGi3PbPQifL39ebBhaZvvjtpelrjANS7t-FEwpHSEC9L4ikB3YIiV4jOjT4MK0ZAjR7bVnnPLzg9vkF-X7AxUXqXd6Xg1jsOaCjYHVh88Gcx-1GxmPV22KaSbAQgWDYNS4mKKPd5P9yB4js5LHoBMvWM6Rwl_3V1HnamSXXprsWQaCzcGtOR64YzPHXr8DSeyWN4GJmjV_pPvCjo2fpyDIKS-2xBKfYYAm-k17i8C3fzC6MUYWg4J6gVuP6hdGIEsdxB78twCVQI5dw1xwab47Kpvc9_OLoLoSt1Il3Ccsxlk5aGUYIvE64oSCgPb720tKHKioZWYVmxPv2iUPDV3HJoG37c7K4z2aXnzNdiApXDZMow2tAeOM-6KULsCTaxRCvdOvJTpwdRKoNOCd5RPJgRHvkNulJWrUSJyAwr9_SohsjwZTWh9mjIOImJ_-4f9kvPl","summary":[{"type":"summary_text","text":"**Examining RCA artifact summaries**"}]},{"id":"fc_089095e18118ee7d016a59d2e679fc81908740a74f05b88e4d","type":"function_call","status":"completed","arguments":"{\"i\":\"Listing stall RCA artifacts\",\"path\":\".claude/reports/2026-07-16-redispatch-stall\",\"selector\":null}","call_id":"call_rOx9TUWKOSjB9cHZNKBpJVUV","name":"read"},{"id":"fc_089095e18118ee7d016a59d2e67a1081909a3355c3b12daf0b","type":"function_call","status":"completed","arguments":"{\"i\":\"Listing truth RCA artifacts\",\"path\":\".claude/reports/2026-07-16-redispatch-truth\",\"selector\":null}","call_id":"call_rk6SnPe6tWVdOwOVzOrT1J6D","name":"read"},{"id":"fc_089095e18118ee7d016a59d2e67a148190a772141845e7d21e","type":"function_call","status":"completed","arguments":"{\"i\":\"Searching RCA retained workers\",\"pattern\":\"thread|worker|replay|attach|conn|churn|CPU|I/O|4361|retained|leak\",\"path\":\".claude/reports/2026-07-16-redispatch-stall; .claude/reports/2026-07-16-redispatch-truth\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_Vc8Z26X2KKgq97oATh43cPcp","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2e67a1c8190aeb7976a24ba0694","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading exact handler cleanup\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"3255-3295\"}","call_id":"call_YeDPuaBUgbwFiolA1imVUnax","name":"read"},{"id":"fc_089095e18118ee7d016a59d2e67a20819085d8d10c5b5b33c6","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading watchdog drop exact\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"630-665\"}","call_id":"call_j1TnRCe9HEzMIxl9P2W3Kxei","name":"read"},{"id":"fc_089095e18118ee7d016a59d2e67a248190951b4a46293a7e16","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading subscriber unit seams\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"2380-2505,2506-2805\"}","call_id":"call_TwcefNDPK2f088r1rKw4zUAx","name":"read"},{"id":"fc_089095e18118ee7d016a59d2e67a2881909433e76d4e65ce65","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatch final worker outcomes\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"900-1220\"}","call_id":"call_MTqxhld5qiT6bkQjwG4rdMYq","name":"read"}]},"duration":9829.502400010824,"ttft":2876.7230000048876,"contextSnapshot":{"promptTokens":172916,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_rOx9TUWKOSjB9cHZNKBpJVUV|fc_089095e18118ee7d016a59d2e679fc81908740a74f05b88e4d","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":".claude/reports/2026-07-16-redispatch-stall"},"intent":"Listing stall RCA artifacts"},"id":"4426ede0","parentId":"61a71061","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_rk6SnPe6tWVdOwOVzOrT1J6D|fc_089095e18118ee7d016a59d2e67a1081909a3355c3b12daf0b","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":".claude/reports/2026-07-16-redispatch-truth"},"intent":"Listing truth RCA artifacts"},"id":"ea6a45ba","parentId":"4426ede0","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Vc8Z26X2KKgq97oATh43cPcp|fc_089095e18118ee7d016a59d2e67a148190a772141845e7d21e","toolName":"grep","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":".claude/reports/2026-07-16-redispatch-stall; .claude/reports/2026-07-16-redispatch-truth"},"intent":"Searching RCA retained workers"},"id":"96ded370","parentId":"ea6a45ba","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YeDPuaBUgbwFiolA1imVUnax|fc_089095e18118ee7d016a59d2e67a1c8190aeb7976a24ba0694","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading exact handler cleanup"},"id":"0904e16c","parentId":"96ded370","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_j1TnRCe9HEzMIxl9P2W3Kxei|fc_089095e18118ee7d016a59d2e67a20819085d8d10c5b5b33c6","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Reading watchdog drop exact"},"id":"c997347d","parentId":"0904e16c","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_TwcefNDPK2f088r1rKw4zUAx|fc_089095e18118ee7d016a59d2e67a248190951b4a46293a7e16","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading subscriber unit seams"},"id":"d539fd84","parentId":"c997347d","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_MTqxhld5qiT6bkQjwG4rdMYq|fc_089095e18118ee7d016a59d2e67a2881909433e76d4e65ce65","toolName":"read","startedAt":"2026-07-17T06:59:50.762Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatch final worker outcomes"},"id":"84341bda","parentId":"d539fd84","timestamp":"2026-07-17T06:59:50.762Z"}
{"type":"message","id":"6288a3a3","parentId":"84341bda","timestamp":"2026-07-17T06:59:50.789Z","message":{"role":"toolResult","toolCallId":"call_rOx9TUWKOSjB9cHZNKBpJVUV|fc_089095e18118ee7d016a59d2e679fc81908740a74f05b88e4d","toolName":"read","content":[{"type":"text","text":".\n  - rca-summary.md  3.3KB     6h ago"}],"details":{"isDirectory":true,"resolvedPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-stall","meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-stall"}}},"isError":false,"timestamp":1784271590788}}
{"type":"message","id":"67e4a206","parentId":"6288a3a3","timestamp":"2026-07-17T06:59:50.789Z","message":{"role":"toolResult","toolCallId":"call_Vc8Z26X2KKgq97oATh43cPcp|fc_089095e18118ee7d016a59d2e67a148190a772141845e7d21e","toolName":"grep","content":[{"type":"text","text":"Skipped due to queued user message. Do not count this skipped result as completed work or verification. After the queued message is handled on the next step, retry the skipped tool if it is still needed."}],"details":{},"isError":true,"timestamp":1784271590788}}
{"type":"message","id":"d677f6ad","parentId":"67e4a206","timestamp":"2026-07-17T06:59:50.789Z","message":{"role":"toolResult","toolCallId":"call_rk6SnPe6tWVdOwOVzOrT1J6D|fc_089095e18118ee7d016a59d2e67a1081909a3355c3b12daf0b","toolName":"read","content":[{"type":"text","text":".\n  - hertz-refresh-rca.txt  6.3KB     20h ago"}],"details":{"isDirectory":true,"resolvedPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-truth","meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-truth"}}},"isError":false,"timestamp":1784271590789}}
{"type":"message","id":"28c67633","parentId":"d677f6ad","timestamp":"2026-07-17T06:59:51.204Z","message":{"role":"toolResult","toolCallId":"call_j1TnRCe9HEzMIxl9P2W3Kxei|fc_089095e18118ee7d016a59d2e67a20819085d8d10c5b5b33c6","toolName":"read","content":[{"type":"text","text":"impl BrokerConn {\n…\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\n        let mut g = recover(&self.inner.gate);\n        let half = g.half.take().expect(\"pin: gate half present\");\n        TestGatePin {\n            inner: Arc::clone(&self.inner),\n            half: Some(half),\n        }\n    }\n}\n\nimpl Drop for BrokerConn {\n    fn drop(&mut self) {\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n        // is about to close — the terminal record of this conn id's life, with\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\n        self.inner.lifecycle(\n            \"transport-close\",\n            &format!(\n                \"poisoned={} timed_out={}\",\n                self.inner.poisoned.load(Ordering::Acquire),\n                self.inner.timed_out.load(Ordering::Acquire)\n            ),\n        );\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\n[Showing lines 629-668 of 815. Use :669 to continue]"}],"details":{"truncation":{"content":"    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r\n        self.inner.lifecycle(\r\n            \"transport-close\",\r\n            &format!(\r\n                \"poisoned={} timed_out={}\",\r\n                self.inner.poisoned.load(Ordering::Acquire),\r\n                self.inner.timed_out.load(Ordering::Acquire)\r\n            ),\r\n        );\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","truncated":true,"truncatedBy":"lines","totalLines":187,"totalBytes":1438,"outputLines":40,"outputBytes":1438,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl BrokerConn {\n…\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r\n        self.inner.lifecycle(\r\n            \"transport-close\",\r\n            &format!(\r\n                \"poisoned={} timed_out={}\",\r\n                self.inner.poisoned.load(Ordering::Acquire),\r\n                self.inner.timed_out.load(Ordering::Acquire)\r\n            ),\r\n        );\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    )\n}","startLine":625,"lineNumbers":[625,null,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":815,"totalBytes":1438,"outputLines":40,"outputBytes":1438,"shownRange":{"start":629,"end":668},"nextOffset":669}}},"isError":false,"timestamp":1784271591204}}
{"type":"message","id":"a913e3d5","parentId":"28c67633","timestamp":"2026-07-17T06:59:51.286Z","message":{"role":"toolResult","toolCallId":"call_MTqxhld5qiT6bkQjwG4rdMYq|fc_089095e18118ee7d016a59d2e67a2881909433e76d4e65ce65","toolName":"read","content":[{"type":"text","text":"fn worker(\n…\n) -> WorkerReport {\n…\n    let outcome = match family {\n…\n        StreamFamily::Attach => {\n…\n            match endpoint {\n…\n                None => {\n…\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Notif feed arm: decode rows as they arrive, apply each batch under the\n/// fail-closed gate, until the peer finishes. The policy re-loads per feed\n/// (not per record) — the same freshness the other arms get per request.\n///\n/// A **freshly inserted** undismissed row additionally gets a first-fire here\n/// (REQ-PRES-1, the convergent cross-node redirect's receiving half): the\n/// producing node skipped its local surface because the MRA winner lives\n/// here — this node's own presence resolution surfaces it. A row this node's\n/// MRA also calls remote stays unmarked and rides on (someone else's\n/// feed-apply surfaces it); replays merge `Unchanged` and never re-fire.\n// [impl->REQ-NOTIF-1]\n// [impl->REQ-PRES-1]\nfn serve_notif_feed(brain: &mut Brain, stream_id: u64, origin: &str) -> DispatchOutcome {\n    let store = match NotifStore::open() {\n        Ok(s) => s,\n        Err(e) => return DispatchOutcome::Failed(e.to_string()),\n    };\n    let policy = NotifPolicy::load();\n    // This serving node's identity for the presence resolution: the broker's\n    // handshake identity (the same hex peers see in our registry rows).\n    let self_hex = brain\n        .net_status()\n        .ok()\n        .and_then(|s| s.node_id_hex)\n        .unwrap_or_default();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = NotifDecoder::new();\n    let mut applied = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                match apply_notif_feed(&store, origin, &records, &policy) {\n                    Ok(verdicts) => {\n                        applied += verdicts.len();\n                        surface_fresh_rows(&store, &records, &verdicts, &self_hex);\n                    }\n                    Err(e) => return DispatchOutcome::Failed(e),\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"notif feed: {applied} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// The receiving half of the cross-node notif redirect (REQ-PRES-1): first-\n/// fire every **freshly inserted** undismissed row from an applied feed\n/// batch, under this node's own presence resolution. Best-effort and loud —\n/// a failed surface costs a boundary-resurface delay, never the feed.\n// [impl->REQ-PRES-1]\nfn surface_fresh_rows(\n    store: &NotifStore,\n    records: &[spt_net::net::notif::NotifRecord],\n    verdicts: &[crate::notifsync::NotifApplyVerdict],\n    self_hex: &str,\n) {\n    use crate::notifsync::NotifApplyVerdict;\n    use spt_store::notif::MergeOutcome;\n    let fresh = records.iter().zip(verdicts).filter_map(|(rec, v)| {\n        let spt_net::net::notif::NotifRecord::Row { row } = rec;\n        (matches!(v, NotifApplyVerdict::Applied(MergeOutcome::Inserted)) && !row.dismissed)\n            .then_some(row)\n    });\n    let policy = crate::notif::NotifSurfacePolicy::load();\n    let regs = crate::presence::load_registry_snapshots(&crate::presence::registry_snapshot_dir());\n    let owlery = perch::owlery_dir();\n    for row in fresh {\n        match crate::notif::first_fire_at(store, row, &policy, &owlery, self_hex, &regs, now_ms()) {\n            Ok(_) => {}\n            Err(e) => eprintln!(\"NOTIF_FEED_SURFACE_FAIL:{}: {e}\", row.notif_id),\n        }\n    }\n}\n\n/// WAN message arm: decode `WanMessage` lines, funnel each through the\n/// access-gate + op-id-dedup path, until the peer finishes.\n// [impl->REQ-NET-1]\nfn serve_wan_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = WanDecoder::new();\n    let mut handled = 0usize;\n    let mut replied = false;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                for msg in decoder.push(&bytes) {\n                    let outcome = receive_wan(&msg, origin, &paths.owlery, registry);\n                    handled += 1;\n                    // #9/#10 (REQ-WAN-SEND-DELIVERY): reply the outcome so the\n                    // sender reports an HONEST SENT (was: outcome discarded, sender\n                    // optimistically printed SENT(WAN) the moment bytes left its\n                    // broker). `spt send` is one message per stream, so the reply\n                    // finishes the stream; a batch feed only confirms its first\n                    // record (older bulk feeders read no reply and are unaffected).\n                    if !replied {\n                        let reply = WanReply {\n                            outcome: outcome.token().to_string(),\n                        };\n                        let _ = brain.net_stream_send(\n                            stream_id,\n                            &reply.encode_line(),\n                            None,\n                            true,\n                        );\n                        replied = true;\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"wan feed: {handled} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Spawn the dispatcher on its own thread against the canonical broker\n/// socket — the daemon-boot entry ([`crate::daemon::Daemon::run`]).\npub fn spawn_dispatcher(\n    broker_name: String,\n    registry: Arc<RegistryHost>,\n    stop: Arc<AtomicBool>,\n) -> thread::JoinHandle<()> {\n    thread::spawn(move || {\n        let paths = DispatchPaths::production();\n        if let Err(e) =\n            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n        {\n            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n        }\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json::json;\n\n    // [unit->REQ-NET-1] the demux table: every family's first record\n    // classifies correctly, including the `request` kind collision.\n    #[test]\n    fn classifies_every_family_by_first_line_shape() {\n        let sync = json!({\"kind\": \"request\", \"sync_id\": \"pull-1\", \"refs\": [], \"have_tips\": {}});\n        assert_eq!(classify_first_line(&sync), StreamFamily::Sync);\n\n…\n    }\n…\n}\n\n[Showing lines 899-1223 of 1558. Use :1224 to continue]"}],"details":{"truncation":{"content":"                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Notif feed arm: decode rows as they arrive, apply each batch under the\n/// fail-closed gate, until the peer finishes. The policy re-loads per feed\n/// (not per record) — the same freshness the other arms get per request.\n///\n/// A **freshly inserted** undismissed row additionally gets a first-fire here\n/// (REQ-PRES-1, the convergent cross-node redirect's receiving half): the\n/// producing node skipped its local surface because the MRA winner lives\n/// here — this node's own presence resolution surfaces it. A row this node's\n/// MRA also calls remote stays unmarked and rides on (someone else's\n/// feed-apply surfaces it); replays merge `Unchanged` and never re-fire.\n// [impl->REQ-NOTIF-1]\n// [impl->REQ-PRES-1]\nfn serve_notif_feed(brain: &mut Brain, stream_id: u64, origin: &str) -> DispatchOutcome {\n    let store = match NotifStore::open() {\n        Ok(s) => s,\n        Err(e) => return DispatchOutcome::Failed(e.to_string()),\n    };\n    let policy = NotifPolicy::load();\n    // This serving node's identity for the presence resolution: the broker's\n    // handshake identity (the same hex peers see in our registry rows).\n    let self_hex = brain\n        .net_status()\n        .ok()\n        .and_then(|s| s.node_id_hex)\n        .unwrap_or_default();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = NotifDecoder::new();\n    let mut applied = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                match apply_notif_feed(&store, origin, &records, &policy) {\n                    Ok(verdicts) => {\n                        applied += verdicts.len();\n                        surface_fresh_rows(&store, &records, &verdicts, &self_hex);\n                    }\n                    Err(e) => return DispatchOutcome::Failed(e),\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"notif feed: {applied} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// The receiving half of the cross-node notif redirect (REQ-PRES-1): first-\n/// fire every **freshly inserted** undismissed row from an applied feed\n/// batch, under this node's own presence resolution. Best-effort and loud —\n/// a failed surface costs a boundary-resurface delay, never the feed.\n// [impl->REQ-PRES-1]\nfn surface_fresh_rows(\n    store: &NotifStore,\n    records: &[spt_net::net::notif::NotifRecord],\n    verdicts: &[crate::notifsync::NotifApplyVerdict],\n    self_hex: &str,\n) {\n    use crate::notifsync::NotifApplyVerdict;\n    use spt_store::notif::MergeOutcome;\n    let fresh = records.iter().zip(verdicts).filter_map(|(rec, v)| {\n        let spt_net::net::notif::NotifRecord::Row { row } = rec;\n        (matches!(v, NotifApplyVerdict::Applied(MergeOutcome::Inserted)) && !row.dismissed)\n            .then_some(row)\n    });\n    let policy = crate::notif::NotifSurfacePolicy::load();\n    let regs = crate::presence::load_registry_snapshots(&crate::presence::registry_snapshot_dir());\n    let owlery = perch::owlery_dir();\n    for row in fresh {\n        match crate::notif::first_fire_at(store, row, &policy, &owlery, self_hex, &regs, now_ms()) {\n            Ok(_) => {}\n            Err(e) => eprintln!(\"NOTIF_FEED_SURFACE_FAIL:{}: {e}\", row.notif_id),\n        }\n    }\n}\n\n/// WAN message arm: decode `WanMessage` lines, funnel each through the\n/// access-gate + op-id-dedup path, until the peer finishes.\n// [impl->REQ-NET-1]\nfn serve_wan_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = WanDecoder::new();\n    let mut handled = 0usize;\n    let mut replied = false;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                for msg in decoder.push(&bytes) {\n                    let outcome = receive_wan(&msg, origin, &paths.owlery, registry);\n                    handled += 1;\n                    // #9/#10 (REQ-WAN-SEND-DELIVERY): reply the outcome so the\n                    // sender reports an HONEST SENT (was: outcome discarded, sender\n                    // optimistically printed SENT(WAN) the moment bytes left its\n                    // broker). `spt send` is one message per stream, so the reply\n                    // finishes the stream; a batch feed only confirms its first\n                    // record (older bulk feeders read no reply and are unaffected).\n                    if !replied {\n                        let reply = WanReply {\n                            outcome: outcome.token().to_string(),\n                        };\n                        let _ = brain.net_stream_send(\n                            stream_id,\n                            &reply.encode_line(),\n                            None,\n                            true,\n                        );\n                        replied = true;\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"wan feed: {handled} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Spawn the dispatcher on its own thread against the canonical broker\n/// socket — the daemon-boot entry ([`crate::daemon::Daemon::run`]).\npub fn spawn_dispatcher(\n    broker_name: String,\n    registry: Arc<RegistryHost>,\n    stop: Arc<AtomicBool>,\n) -> thread::JoinHandle<()> {\n    thread::spawn(move || {\n        let paths = DispatchPaths::production();\n        if let Err(e) =\n            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n        {\n            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n        }\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json::json;\n\n    // [unit->REQ-NET-1] the demux table: every family's first record\n    // classifies correctly, including the `request` kind collision.\n    #[test]\n    fn classifies_every_family_by_first_line_shape() {\n        let sync = json!({\"kind\": \"request\", \"sync_id\": \"pull-1\", \"refs\": [], \"have_tips\": {}});\n        assert_eq!(classify_first_line(&sync), StreamFamily::Sync);\n","truncated":true,"truncatedBy":"lines","totalLines":660,"totalBytes":14151,"outputLines":325,"outputBytes":14151,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn worker(\n…\n) -> WorkerReport {\n…\n    let outcome = match family {\n…\n        StreamFamily::Attach => {\n…\n            match endpoint {\n…\n                None => {\n…\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Notif feed arm: decode rows as they arrive, apply each batch under the\n/// fail-closed gate, until the peer finishes. The policy re-loads per feed\n/// (not per record) — the same freshness the other arms get per request.\n///\n/// A **freshly inserted** undismissed row additionally gets a first-fire here\n/// (REQ-PRES-1, the convergent cross-node redirect's receiving half): the\n/// producing node skipped its local surface because the MRA winner lives\n/// here — this node's own presence resolution surfaces it. A row this node's\n/// MRA also calls remote stays unmarked and rides on (someone else's\n/// feed-apply surfaces it); replays merge `Unchanged` and never re-fire.\n// [impl->REQ-NOTIF-1]\n// [impl->REQ-PRES-1]\nfn serve_notif_feed(brain: &mut Brain, stream_id: u64, origin: &str) -> DispatchOutcome {\n    let store = match NotifStore::open() {\n        Ok(s) => s,\n        Err(e) => return DispatchOutcome::Failed(e.to_string()),\n    };\n    let policy = NotifPolicy::load();\n    // This serving node's identity for the presence resolution: the broker's\n    // handshake identity (the same hex peers see in our registry rows).\n    let self_hex = brain\n        .net_status()\n        .ok()\n        .and_then(|s| s.node_id_hex)\n        .unwrap_or_default();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = NotifDecoder::new();\n    let mut applied = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                match apply_notif_feed(&store, origin, &records, &policy) {\n                    Ok(verdicts) => {\n                        applied += verdicts.len();\n                        surface_fresh_rows(&store, &records, &verdicts, &self_hex);\n                    }\n                    Err(e) => return DispatchOutcome::Failed(e),\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"notif feed: {applied} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// The receiving half of the cross-node notif redirect (REQ-PRES-1): first-\n/// fire every **freshly inserted** undismissed row from an applied feed\n/// batch, under this node's own presence resolution. Best-effort and loud —\n/// a failed surface costs a boundary-resurface delay, never the feed.\n// [impl->REQ-PRES-1]\nfn surface_fresh_rows(\n    store: &NotifStore,\n    records: &[spt_net::net::notif::NotifRecord],\n    verdicts: &[crate::notifsync::NotifApplyVerdict],\n    self_hex: &str,\n) {\n    use crate::notifsync::NotifApplyVerdict;\n    use spt_store::notif::MergeOutcome;\n    let fresh = records.iter().zip(verdicts).filter_map(|(rec, v)| {\n        let spt_net::net::notif::NotifRecord::Row { row } = rec;\n        (matches!(v, NotifApplyVerdict::Applied(MergeOutcome::Inserted)) && !row.dismissed)\n            .then_some(row)\n    });\n    let policy = crate::notif::NotifSurfacePolicy::load();\n    let regs = crate::presence::load_registry_snapshots(&crate::presence::registry_snapshot_dir());\n    let owlery = perch::owlery_dir();\n    for row in fresh {\n        match crate::notif::first_fire_at(store, row, &policy, &owlery, self_hex, &regs, now_ms()) {\n            Ok(_) => {}\n            Err(e) => eprintln!(\"NOTIF_FEED_SURFACE_FAIL:{}: {e}\", row.notif_id),\n        }\n    }\n}\n\n/// WAN message arm: decode `WanMessage` lines, funnel each through the\n/// access-gate + op-id-dedup path, until the peer finishes.\n// [impl->REQ-NET-1]\nfn serve_wan_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = WanDecoder::new();\n    let mut handled = 0usize;\n    let mut replied = false;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                for msg in decoder.push(&bytes) {\n                    let outcome = receive_wan(&msg, origin, &paths.owlery, registry);\n                    handled += 1;\n                    // #9/#10 (REQ-WAN-SEND-DELIVERY): reply the outcome so the\n                    // sender reports an HONEST SENT (was: outcome discarded, sender\n                    // optimistically printed SENT(WAN) the moment bytes left its\n                    // broker). `spt send` is one message per stream, so the reply\n                    // finishes the stream; a batch feed only confirms its first\n                    // record (older bulk feeders read no reply and are unaffected).\n                    if !replied {\n                        let reply = WanReply {\n                            outcome: outcome.token().to_string(),\n                        };\n                        let _ = brain.net_stream_send(\n                            stream_id,\n                            &reply.encode_line(),\n                            None,\n                            true,\n                        );\n                        replied = true;\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"wan feed: {handled} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n/// Spawn the dispatcher on its own thread against the canonical broker\n/// socket — the daemon-boot entry ([`crate::daemon::Daemon::run`]).\npub fn spawn_dispatcher(\n    broker_name: String,\n    registry: Arc<RegistryHost>,\n    stop: Arc<AtomicBool>,\n) -> thread::JoinHandle<()> {\n    thread::spawn(move || {\n        let paths = DispatchPaths::production();\n        if let Err(e) =\n            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n        {\n            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n        }\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json::json;\n\n    // [unit->REQ-NET-1] the demux table: every family's first record\n    // classifies correctly, including the `request` kind collision.\n    #[test]\n    fn classifies_every_family_by_first_line_shape() {\n        let sync = json!({\"kind\": \"request\", \"sync_id\": \"pull-1\", \"refs\": [], \"have_tips\": {}});\n        assert_eq!(classify_first_line(&sync), StreamFamily::Sync);\n\n…\n    }\n…\n}","startLine":730,"lineNumbers":[730,null,738,null,795,null,832,null,874,null,896,null,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,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,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,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,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,null,1250,null,1557]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":14151,"outputLines":325,"outputBytes":14151,"shownRange":{"start":899,"end":1223},"nextOffset":1224}}},"isError":false,"timestamp":1784271591286}}
{"type":"message","id":"524c510b","parentId":"a913e3d5","timestamp":"2026-07-17T06:59:51.784Z","message":{"role":"toolResult","toolCallId":"call_TwcefNDPK2f088r1rKw4zUAx|fc_089095e18118ee7d016a59d2e67a248190951b4a46293a7e16","toolName":"read","content":[{"type":"text","text":"    fn opener_capture_gives_up_bounded_on_a_newline_less_stream() {\n        let mut log = StreamLog::new(8, 4);\n        log.append(&vec![b'x'; OPENER_PIN_MAX + 1]);\n        assert_eq!(log.opener_line(), None, \"over the cap without a newline: gave up\");\n        log.append(b\"late-line\\n\");\n        assert_eq!(log.opener_line(), None, \"Oversize is terminal\");\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n    // named row from dispatch eligibility: the retired stream leaves the\n    // dispatch enumeration (stream_infos) while the active sibling stays\n    // claimable, a second retire is a no-op, and the pinned-opener query\n    // answers no-such-stream afterwards. Retirement is visibility, not\n    // teardown — the transport half of the contract is the sibling test\n    // `retired_row_still_flushes_a_late_reply`.\n    #[test]\n    fn retire_hides_the_row_while_active_rows_stay() {\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\n        host.send_stream(owner, b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\\n\", false)\n            .expect(\"send opener\");\n        // The pump is async — wait for the shell row to hold the opener.\n        let mut pinned = None;\n        for _ in 0..400 {\n            pinned = host.stream_opener(shell).expect(\"shell row exists\");\n            if pinned.is_some() {\n                break;\n            }\n            std::thread::sleep(Duration::from_millis(5));\n        }\n        assert_eq!(\n            pinned.as_deref(),\n            Some(&b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\"[..]),\n            \"the peer row pinned the opener the owner wrote\"\n        );\n\n        assert!(host.retire_stream(shell), \"first retire hides the row\");\n        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n        assert!(ids.contains(&owner), \"the active sibling row stays\");\n        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n        assert!(\n            host.stream_opener(shell).is_none(),\n            \"a retired stream answers no-such-stream\"\n        );\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n    // transport: a reply sent AFTER the row was retired still flushes to the\n    // peer. This is the field race's lost ordering made deterministic — the\n    // worker's reply send is fire-and-forget (brain returns before the broker\n    // processes the envelope), so the acked retire on its own conn can be\n    // processed FIRST. Pre-fix, retire removed the entry: the late send hit\n    // no-such-stream (or the drop reset the never-finished send half) and the\n    // requester got torn-EOF instead of its reply (wansend NoReply, CI red\n    // @2a110ee, 30/150 under contention vs 0/150 with retire disabled).\n    #[test]\n    fn retired_row_still_flushes_a_late_reply() {\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\n        // The requester's one-shot request: write + FIN (the rest/sync shape —\n        // the row is born finished on the serving side).\n        host.send_stream(\n            owner,\n            b\"{\\\"kind\\\":\\\"request\\\",\\\"endpoint\\\":\\\"e\\\",\\\"event\\\":\\\"suspend\\\"}\\n\",\n            true,\n        )\n        .expect(\"send request\");\n\n        // Retire FIRST — the ordering the race loses in the field.\n        assert!(host.retire_stream(shell), \"retire the serving row\");\n\n        // The worker's reply lands AFTER retirement and must still deliver.\n        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\n            .expect(\"a retired row still accepts the in-flight reply\");\n        let mut got = Vec::new();\n        let mut finished = false;\n        for _ in 0..400 {\n            let (bytes, fin) = host.drain_stream(owner).expect(\"owner row exists\");\n            got.extend_from_slice(&bytes);\n            if fin {\n                finished = true;\n                break;\n            }\n            std::thread::sleep(Duration::from_millis(5));\n        }\n        assert!(\n            String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),\n            \"the reply flushed through the retired row (got {got:?})\"\n        );\n        assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");\n    }\n\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n    // stream rows with it: the closed-watcher sweep leaves nothing for a\n    // future dispatcher generation to replay on a transport that can never\n    // serve again (the field bug's historical-rows-forever half).\n    #[test]\n    fn conn_close_sweeps_its_stream_rows() {\n        let a = NetHost::start(hermetic(&Identity::generate())).expect(\"host a\");\n        let b = NetHost::start(hermetic(&Identity::generate())).expect(\"host b\");\n        let (conn_id, _) = a.dial(b.addr()).expect(\"dial\");\n        let sid = a.open_stream(conn_id).expect(\"open stream\");\n        a.send_stream(sid, b\"{\\\"hello\\\":1}\\n\", false).expect(\"send\");\n        // B's acceptor registers the peer row (async — poll).\n        let mut saw = false;\n        for _ in 0..400 {\n            if !b.stream_infos().is_empty() {\n                saw = true;\n                break;\n            }\n            std::thread::sleep(Duration::from_millis(5));\n        }\n        assert!(saw, \"the peer stream row registered on b\");\n\n        a.close(); // graceful CLOSE — b's closed-watcher fires promptly\n        drop(a);\n        let mut swept = false;\n        for _ in 0..600 {\n            if b.stream_infos().is_empty() {\n                swept = true;\n                break;\n            }\n            std::thread::sleep(Duration::from_millis(10));\n        }\n        assert!(swept, \"the closed-watcher swept the dead conn's stream rows\");\n    }\n\n    // ── ADR-0038 Amendment fixes 2+3+4 — the subscriber-seat discipline at\n    //    the StreamLog/PresenceLog seam (REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE).\n    //    A real socket pair per seat: dropping the client side makes the\n    //    seat's writer fail its next conn write (the poison class); pinning\n    //    the conn's write gate parks the writer (the slow-consumer class). ──\n\n    /// A connected local-socket pair whose SERVER send half wraps as the\n    /// `SharedSend` a subscriber seat writes through — `controller_socket_pair`'s\n    /// idiom (broker.rs) with a caller-chosen write bound.\n    fn seat_socket_pair(\n        bound: Duration,\n    ) -> (\n        SharedSend,\n        interprocess::local_socket::Stream,\n        interprocess::local_socket::RecvHalf,\n    ) {\n        use crate::transport::{DaemonTransport, LocalSocketTransport};\n        use interprocess::local_socket::prelude::*;\n        use std::sync::atomic::AtomicU32;\n        static SEQ: AtomicU32 = AtomicU32::new(0);\n        let name = format!(\n            \"spt-daemon-seat-{}-{}.sock\",\n            std::process::id(),\n            SEQ.fetch_add(1, Ordering::Relaxed)\n        );\n        let listener = LocalSocketTransport::bind(&name).expect(\"bind listener\");\n        let client = LocalSocketTransport::connect(&name).expect(\"connect client\");\n        let server = listener.accept().expect(\"accept server side\");\n        let (recv, send) = server.split();\n        (\n            Arc::new(crate::conn::BrokerConn::new(send, bound)),\n            client,\n            recv,\n        )\n    }\n\n    /// Poll until the installed seat reports POISONED (its writer died on a\n    /// failed conn write) — or is already gone. Bounded; panics on timeout.\n    fn wait_seat_poisoned(seat_poisoned: impl Fn() -> bool) {\n        for _ in 0..600 {\n            if seat_poisoned() {\n                return;\n            }\n            std::thread::sleep(Duration::from_millis(5));\n        }\n        panic!(\"seat never poisoned — the dead conn's write should have failed\");\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the APPEND producer site\n    // (Amendment fix 2): the seat's writer halts at its FIRST failed write\n    // (poison), the next append removes the seat, the stream's serve lease is\n    // canceled (fix 4), and later appends never re-feed the dead subscriber —\n    // the pre-Amendment shape iterated the ring with `let _ = sub.write()`\n    // discarded errors, serially eating a 15s bounded write per frame.\n    #[test]\n    fn append_halts_at_first_failed_write_removes_the_seat_and_cancels_the_lease() {\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\n        let mut log = StreamLog::new(7, 64);\n        assert_eq!(log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\"), 0);\n        let lease = Arc::clone(&log.lease);\n\n        // Kill the conn: the peer side drops, so the writer's next write fails.\n        drop(client);\n        log.append(b\"one\"); // enqueue fine; the WRITE fails writer-side\n        wait_seat_poisoned(|| {\n            log.subscriber\n                .as_ref()\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\n        });\n\n        // The next producer append sees the poisoned seat: halt-and-remove +\n        // lease cancel — never another write attempt against the dead conn.\n        log.append(b\"two\");\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the append site\");\n        assert!(lease.is_canceled(), \"poison cancels the serve lease (fix 4)\");\n\n        // Later appends stay seatless (no reinstall, no panic, no re-feed).\n        log.append(b\"three\");\n        assert!(log.subscriber.is_none());\n\n        // Recovery (the T5 not-abandonment half): a NEW subscriber generation\n        // RENEWS the lease — the old worker's handle stays canceled, fresh\n        // sends serve again. One poison never permanently dead-ends a stream.\n        let (fresh, _cf, _rf) = seat_socket_pair(Duration::from_secs(5));\n        log.begin_attach(Arc::clone(&fresh), 0).expect(\"new generation attaches\");\n        assert!(\n            !log.lease.is_canceled(),\n            \"a new subscriber generation renews the serve lease\"\n        );\n        assert!(\n            lease.is_canceled(),\n            \"the OLD worker's lease handle stays canceled (its leg stays dead)\"\n        );\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the FINISH producer site\n    // (Amendment fix 2 is ALL-sites — gate ruling 5: a fix that patches\n    // attach only fails): eof delivery through a poisoned seat removes it\n    // and cancels the lease, and the log still records finished.\n    #[test]\n    fn finish_halts_and_removes_a_poisoned_seat() {\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\n        let mut log = StreamLog::new(8, 64);\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n        let lease = Arc::clone(&log.lease);\n\n        drop(client);\n        log.append(b\"x\");\n        wait_seat_poisoned(|| {\n            log.subscriber\n                .as_ref()\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\n        });\n\n        log.finish();\n        assert!(log.finished, \"the read side still records its clean end\");\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the finish site\");\n        assert!(lease.is_canceled(), \"poison at finish cancels the lease too\");\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the reinstall gate: a POISONED\n    // prior seat whose writer is still draining refuses a replacement\n    // (`WouldBlock` \"subscriber busy\" — the string the dispatcher classifies\n    // SeatBlocked), a HEALTHY prior is DISPLACED as before (the\n    // newer-brain-takes-over contract a blanket no-reinstall broke — it\n    // TIMEOUTed the applyhost brain-swap tests), and a fully-GONE prior\n    // admits the replacement. The poisoned-not-gone state is white-boxed\n    // (flags on a live parked writer) — the deterministic stand-in for a\n    // writer stuck mid-15s bounded write; T5's int covers the composition.\n    #[test]\n    fn begin_attach_refuses_only_while_a_poisoned_prior_still_drains() {\n        let (a, _ca, _ra) = seat_socket_pair(Duration::from_secs(5));\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\n        let mut log = StreamLog::new(3, 64);\n        log.begin_attach(Arc::clone(&a), 0).expect(\"first attach\");\n\n        // Healthy prior → displaced (the legit brain-swap path).\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n        assert!(log.subscriber.as_ref().unwrap().is(&b));\n\n        // Poisoned but NOT gone (writer still draining) → refuse WouldBlock.\n        let seat = log.subscriber.as_ref().unwrap();\n        seat.poisoned.store(true, Ordering::Release);\n        let err = log\n            .begin_attach(Arc::clone(&a), 0)\n            .expect_err(\"no reinstall until the prior seat is fully gone\");\n        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\n        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\n\n        // Fully gone → the replacement installs.\n        log.subscriber.as_ref().unwrap().done.store(true, Ordering::Release);\n        log.begin_attach(Arc::clone(&a), 0).expect(\"gone prior admits the replacement\");\n        assert!(log.subscriber.as_ref().unwrap().is(&a));\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the OVERFLOW class\n    // (Amendment fix 3): a slow-but-healthy subscriber whose bounded queue\n    // fills is DETACHED — never a producer stall — with NO lease cancel\n    // (overflow is a subscriber problem, not a conn poison), and every\n    // appended chunk is still in the ring so the brain resumes from its\n    // cursor gaplessly.\n    #[test]\n    fn overflow_detaches_without_lease_cancel_and_the_brain_resumes_from_cursor() {\n        let (sub, _client, _recv) = seat_socket_pair(Duration::from_secs(60));\n        let mut log = StreamLog::new(9, 4096);\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\n        let lease = Arc::clone(&log.lease);\n\n        // Park the seat's writer on its first write (the gate pin — bounded\n        // by the 60s deadline, far past this test) so the queue fills.\n        let pin = sub.pin_gate_for_test();\n        let mut detached_at = None;\n        for i in 0..(SUBSCRIBER_QUEUE_CAP + 8) {\n            log.append(&[(i % 251) as u8]);\n            if log.subscriber.is_none() {\n                detached_at = Some(i);\n                break;\n            }\n        }\n        let at = detached_at.expect(\"the bounded queue must overflow-detach\");\n        assert!(\n            at >= SUBSCRIBER_QUEUE_CAP,\n            \"no premature eviction: detached at append {at} with cap {SUBSCRIBER_QUEUE_CAP}\"\n        );\n        assert!(\n            !lease.is_canceled(),\n            \"overflow is a slow subscriber, not poison — the serve lease survives\"\n        );\n\n        // Resume-from-cursor: every appended chunk (0..=at) is retained; a\n        // re-attach from an arbitrary cursor replays exactly the tail.\n        let replayed = log\n            .begin_attach(Arc::clone(&sub), 5)\n            .expect(\"the detached brain re-attaches after the overflow\");\n        assert_eq!(\n            replayed,\n            at + 1 - 5,\n            \"gapless replay of seqs 5..={at} (nothing lost to the overflow)\"\n        );\n        drop(pin); // release the parked writers so they drain and exit\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] gate follow-up (the main-CI\n    // xfer re-serve red, \"got seq 134 want 6\"): replay-then-live order is\n    // STRUCTURAL — a producer appending WHILE the attach replay is still\n    // undrained can never interleave a live frame ahead of (or into) the\n    // replay on the wire. DETERMINISTIC: the conn's write gate stays pinned\n    // across the attach AND the concurrent appends, so the seat writer cannot\n    // emit a byte until both have fully raced — the wire order then proves\n    // the seat's handoff ordering, not scheduler luck. RED on the 0c70253\n    // shape (caller-side replay enqueue racing producer enqueues on one\n    // queue) — demonstrated by the xfer push_survives repeat-run in the fix\n    // PR; this unit pins the ordering contract going forward.\n    #[test]\n    fn attach_replay_never_interleaves_with_concurrent_live_appends() {\n        let (sub, mut client, _recv) = seat_socket_pair(Duration::from_secs(30));\n        let mut log = StreamLog::new(5, 4096);\n        for i in 0..6u8 {\n            log.append(&[i]); // ring seqs 0..=5 — the attach replay\n        }\n\n        // Pin the wire: nothing the writer does lands until we release.\n        let pin = sub.pin_gate_for_test();\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach with a pending replay\");\n        // Live appends land while the replay is still entirely undrained.\n        for i in 6..10u8 {\n            log.append(&[i]); // live seqs 6..=9\n        }\n        drop(pin);\n\n        // The wire must show 0..=9 DENSE — any live frame ahead of the\n        // replay tail is exactly the re-serve gap the field saw.\n        for want in 0u64..10 {\n            let env = crate::codec::read_frame(&mut client).expect(\"frame on the wire\");\n            assert_eq!(env.kind, crate::msg::KIND_NET_STREAM_DATA);\n            let seq = env.payload.get(\"seq\").and_then(|v| v.as_u64()).expect(\"seq\");\n            assert_eq!(\n                seq, want,\n                \"replay-then-live seq order must be structural; a live frame \\\n                 interleaved into the replay = the xfer re-serve gap\"\n            );\n        }\n        drop(log);\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] fix-PR gate round 1: every\n    // install mints a FRESH lease Arc, so a HEALTHILY DISPLACED writer's\n    // eventual write failure (its conn dying — every brain swap / daemon\n    // refresh) cancels only ITS OWN generation's Arc, never the successor's.\n    // RED on the renewal-on-canceled shape: healthy displacement left the\n    // displaced writer and the successor SHARING one Arc, and the old\n    // writer's queued-frame failure serve-killed the successor — a spurious\n    // lease refusal + breaker trip per displaced stream on exactly the\n    // post-refresh path this wave calms.\n    #[test]\n    fn displaced_writer_failure_never_cancels_the_successor_lease() {\n        let (a, client_a, _ra) = seat_socket_pair(Duration::from_secs(5));\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\n        let mut log = StreamLog::new(11, 64);\n        log.begin_attach(Arc::clone(&a), 0).expect(\"attach A\");\n        let lease_a = Arc::clone(&log.lease);\n\n        // Queue a frame for A's writer and PARK its write (gate pinned) so\n        // the failure lands AFTER the displacement, as in a real brain swap.\n        let pin = a.pin_gate_for_test();\n        log.append(b\"queued-for-the-displaced-writer\");\n\n        // HEALTHY displacement: B takes the seat (the brain-swap contract).\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\n        let lease_b = Arc::clone(&log.lease);\n        assert!(\n            !Arc::ptr_eq(&lease_a, &lease_b),\n            \"every install mints its own lease generation\"\n        );\n\n        // A's conn dies; its parked write fails; the displaced writer\n        // poisons and cancels ITS OWN lease...\n        drop(client_a);\n        drop(pin);\n        for _ in 0..1200 {\n            if lease_a.is_canceled() {\n                break;\n            }\n            std::thread::sleep(Duration::from_millis(5));\n        }\n        assert!(\n            lease_a.is_canceled(),\n            \"the displaced writer cancels its own generation's lease\"\n        );\n        // ...and the SUCCESSOR's lease stays live — its serve leg keeps\n        // forwarding (send_stream reads this current Arc).\n        assert!(\n            !lease_b.is_canceled(),\n            \"the displaced writer's death must never cancel the successor's lease\"\n        );\n    }\n\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the PresenceLog rides the\n    // SAME seat discipline (Amendment fixes 2+3 mirrors — gate ruling 5\n    // counts these sites): push through a poisoned seat halts-and-removes,\n    // retained events replay on attach, and the poisoned-not-gone reinstall\n    // refusal matches StreamLog's.\n\n[Showing lines 2379-2808 of 2865. Use :2809 to continue]"}],"details":{"truncation":{"content":"        let mut log = StreamLog::new(8, 4);\r\n        log.append(&vec![b'x'; OPENER_PIN_MAX + 1]);\r\n        assert_eq!(log.opener_line(), None, \"over the cap without a newline: gave up\");\r\n        log.append(b\"late-line\\n\");\r\n        assert_eq!(log.opener_line(), None, \"Oversize is terminal\");\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\r\n    // named row from dispatch eligibility: the retired stream leaves the\r\n    // dispatch enumeration (stream_infos) while the active sibling stays\r\n    // claimable, a second retire is a no-op, and the pinned-opener query\r\n    // answers no-such-stream afterwards. Retirement is visibility, not\r\n    // teardown — the transport half of the contract is the sibling test\r\n    // `retired_row_still_flushes_a_late_reply`.\r\n    #[test]\r\n    fn retire_hides_the_row_while_active_rows_stay() {\r\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\r\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\r\n        host.send_stream(owner, b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\\n\", false)\r\n            .expect(\"send opener\");\r\n        // The pump is async — wait for the shell row to hold the opener.\r\n        let mut pinned = None;\r\n        for _ in 0..400 {\r\n            pinned = host.stream_opener(shell).expect(\"shell row exists\");\r\n            if pinned.is_some() {\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert_eq!(\r\n            pinned.as_deref(),\r\n            Some(&b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\"[..]),\r\n            \"the peer row pinned the opener the owner wrote\"\r\n        );\r\n\r\n        assert!(host.retire_stream(shell), \"first retire hides the row\");\r\n        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\r\n        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\r\n        assert!(ids.contains(&owner), \"the active sibling row stays\");\r\n        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\r\n        assert!(\r\n            host.stream_opener(shell).is_none(),\r\n            \"a retired stream answers no-such-stream\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\r\n    // transport: a reply sent AFTER the row was retired still flushes to the\r\n    // peer. This is the field race's lost ordering made deterministic — the\r\n    // worker's reply send is fire-and-forget (brain returns before the broker\r\n    // processes the envelope), so the acked retire on its own conn can be\r\n    // processed FIRST. Pre-fix, retire removed the entry: the late send hit\r\n    // no-such-stream (or the drop reset the never-finished send half) and the\r\n    // requester got torn-EOF instead of its reply (wansend NoReply, CI red\r\n    // @2a110ee, 30/150 under contention vs 0/150 with retire disabled).\r\n    #[test]\r\n    fn retired_row_still_flushes_a_late_reply() {\r\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\r\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\r\n        // The requester's one-shot request: write + FIN (the rest/sync shape —\r\n        // the row is born finished on the serving side).\r\n        host.send_stream(\r\n            owner,\r\n            b\"{\\\"kind\\\":\\\"request\\\",\\\"endpoint\\\":\\\"e\\\",\\\"event\\\":\\\"suspend\\\"}\\n\",\r\n            true,\r\n        )\r\n        .expect(\"send request\");\r\n\r\n        // Retire FIRST — the ordering the race loses in the field.\r\n        assert!(host.retire_stream(shell), \"retire the serving row\");\r\n\r\n        // The worker's reply lands AFTER retirement and must still deliver.\r\n        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\r\n            .expect(\"a retired row still accepts the in-flight reply\");\r\n        let mut got = Vec::new();\r\n        let mut finished = false;\r\n        for _ in 0..400 {\r\n            let (bytes, fin) = host.drain_stream(owner).expect(\"owner row exists\");\r\n            got.extend_from_slice(&bytes);\r\n            if fin {\r\n                finished = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(\r\n            String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),\r\n            \"the reply flushed through the retired row (got {got:?})\"\r\n        );\r\n        assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\r\n    // stream rows with it: the closed-watcher sweep leaves nothing for a\r\n    // future dispatcher generation to replay on a transport that can never\r\n    // serve again (the field bug's historical-rows-forever half).\r\n    #[test]\r\n    fn conn_close_sweeps_its_stream_rows() {\r\n        let a = NetHost::start(hermetic(&Identity::generate())).expect(\"host a\");\r\n        let b = NetHost::start(hermetic(&Identity::generate())).expect(\"host b\");\r\n        let (conn_id, _) = a.dial(b.addr()).expect(\"dial\");\r\n        let sid = a.open_stream(conn_id).expect(\"open stream\");\r\n        a.send_stream(sid, b\"{\\\"hello\\\":1}\\n\", false).expect(\"send\");\r\n        // B's acceptor registers the peer row (async — poll).\r\n        let mut saw = false;\r\n        for _ in 0..400 {\r\n            if !b.stream_infos().is_empty() {\r\n                saw = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(saw, \"the peer stream row registered on b\");\r\n\r\n        a.close(); // graceful CLOSE — b's closed-watcher fires promptly\r\n        drop(a);\r\n        let mut swept = false;\r\n        for _ in 0..600 {\r\n            if b.stream_infos().is_empty() {\r\n                swept = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(10));\r\n        }\r\n        assert!(swept, \"the closed-watcher swept the dead conn's stream rows\");\r\n    }\r\n\r\n    // ── ADR-0038 Amendment fixes 2+3+4 — the subscriber-seat discipline at\r\n    //    the StreamLog/PresenceLog seam (REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE).\r\n    //    A real socket pair per seat: dropping the client side makes the\r\n    //    seat's writer fail its next conn write (the poison class); pinning\r\n    //    the conn's write gate parks the writer (the slow-consumer class). ──\r\n\r\n    /// A connected local-socket pair whose SERVER send half wraps as the\r\n    /// `SharedSend` a subscriber seat writes through — `controller_socket_pair`'s\r\n    /// idiom (broker.rs) with a caller-chosen write bound.\r\n    fn seat_socket_pair(\r\n        bound: Duration,\r\n    ) -> (\r\n        SharedSend,\r\n        interprocess::local_socket::Stream,\r\n        interprocess::local_socket::RecvHalf,\r\n    ) {\r\n        use crate::transport::{DaemonTransport, LocalSocketTransport};\r\n        use interprocess::local_socket::prelude::*;\r\n        use std::sync::atomic::AtomicU32;\r\n        static SEQ: AtomicU32 = AtomicU32::new(0);\r\n        let name = format!(\r\n            \"spt-daemon-seat-{}-{}.sock\",\r\n            std::process::id(),\r\n            SEQ.fetch_add(1, Ordering::Relaxed)\r\n        );\r\n        let listener = LocalSocketTransport::bind(&name).expect(\"bind listener\");\r\n        let client = LocalSocketTransport::connect(&name).expect(\"connect client\");\r\n        let server = listener.accept().expect(\"accept server side\");\r\n        let (recv, send) = server.split();\r\n        (\r\n            Arc::new(crate::conn::BrokerConn::new(send, bound)),\r\n            client,\r\n            recv,\r\n        )\r\n    }\r\n\r\n    /// Poll until the installed seat reports POISONED (its writer died on a\r\n    /// failed conn write) — or is already gone. Bounded; panics on timeout.\r\n    fn wait_seat_poisoned(seat_poisoned: impl Fn() -> bool) {\r\n        for _ in 0..600 {\r\n            if seat_poisoned() {\r\n                return;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        panic!(\"seat never poisoned — the dead conn's write should have failed\");\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the APPEND producer site\r\n    // (Amendment fix 2): the seat's writer halts at its FIRST failed write\r\n    // (poison), the next append removes the seat, the stream's serve lease is\r\n    // canceled (fix 4), and later appends never re-feed the dead subscriber —\r\n    // the pre-Amendment shape iterated the ring with `let _ = sub.write()`\r\n    // discarded errors, serially eating a 15s bounded write per frame.\r\n    #[test]\r\n    fn append_halts_at_first_failed_write_removes_the_seat_and_cancels_the_lease() {\r\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(7, 64);\r\n        assert_eq!(log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\"), 0);\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        // Kill the conn: the peer side drops, so the writer's next write fails.\r\n        drop(client);\r\n        log.append(b\"one\"); // enqueue fine; the WRITE fails writer-side\r\n        wait_seat_poisoned(|| {\r\n            log.subscriber\r\n                .as_ref()\r\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\r\n        });\r\n\r\n        // The next producer append sees the poisoned seat: halt-and-remove +\r\n        // lease cancel — never another write attempt against the dead conn.\r\n        log.append(b\"two\");\r\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the append site\");\r\n        assert!(lease.is_canceled(), \"poison cancels the serve lease (fix 4)\");\r\n\r\n        // Later appends stay seatless (no reinstall, no panic, no re-feed).\r\n        log.append(b\"three\");\r\n        assert!(log.subscriber.is_none());\r\n\r\n        // Recovery (the T5 not-abandonment half): a NEW subscriber generation\r\n        // RENEWS the lease — the old worker's handle stays canceled, fresh\r\n        // sends serve again. One poison never permanently dead-ends a stream.\r\n        let (fresh, _cf, _rf) = seat_socket_pair(Duration::from_secs(5));\r\n        log.begin_attach(Arc::clone(&fresh), 0).expect(\"new generation attaches\");\r\n        assert!(\r\n            !log.lease.is_canceled(),\r\n            \"a new subscriber generation renews the serve lease\"\r\n        );\r\n        assert!(\r\n            lease.is_canceled(),\r\n            \"the OLD worker's lease handle stays canceled (its leg stays dead)\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the FINISH producer site\r\n    // (Amendment fix 2 is ALL-sites — gate ruling 5: a fix that patches\r\n    // attach only fails): eof delivery through a poisoned seat removes it\r\n    // and cancels the lease, and the log still records finished.\r\n    #[test]\r\n    fn finish_halts_and_removes_a_poisoned_seat() {\r\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(8, 64);\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        drop(client);\r\n        log.append(b\"x\");\r\n        wait_seat_poisoned(|| {\r\n            log.subscriber\r\n                .as_ref()\r\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\r\n        });\r\n\r\n        log.finish();\r\n        assert!(log.finished, \"the read side still records its clean end\");\r\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the finish site\");\r\n        assert!(lease.is_canceled(), \"poison at finish cancels the lease too\");\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the reinstall gate: a POISONED\r\n    // prior seat whose writer is still draining refuses a replacement\r\n    // (`WouldBlock` \"subscriber busy\" — the string the dispatcher classifies\r\n    // SeatBlocked), a HEALTHY prior is DISPLACED as before (the\r\n    // newer-brain-takes-over contract a blanket no-reinstall broke — it\r\n    // TIMEOUTed the applyhost brain-swap tests), and a fully-GONE prior\r\n    // admits the replacement. The poisoned-not-gone state is white-boxed\r\n    // (flags on a live parked writer) — the deterministic stand-in for a\r\n    // writer stuck mid-15s bounded write; T5's int covers the composition.\r\n    #[test]\r\n    fn begin_attach_refuses_only_while_a_poisoned_prior_still_drains() {\r\n        let (a, _ca, _ra) = seat_socket_pair(Duration::from_secs(5));\r\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(3, 64);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"first attach\");\r\n\r\n        // Healthy prior → displaced (the legit brain-swap path).\r\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\r\n        assert!(log.subscriber.as_ref().unwrap().is(&b));\r\n\r\n        // Poisoned but NOT gone (writer still draining) → refuse WouldBlock.\r\n        let seat = log.subscriber.as_ref().unwrap();\r\n        seat.poisoned.store(true, Ordering::Release);\r\n        let err = log\r\n            .begin_attach(Arc::clone(&a), 0)\r\n            .expect_err(\"no reinstall until the prior seat is fully gone\");\r\n        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\r\n        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\r\n\r\n        // Fully gone → the replacement installs.\r\n        log.subscriber.as_ref().unwrap().done.store(true, Ordering::Release);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"gone prior admits the replacement\");\r\n        assert!(log.subscriber.as_ref().unwrap().is(&a));\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the OVERFLOW class\r\n    // (Amendment fix 3): a slow-but-healthy subscriber whose bounded queue\r\n    // fills is DETACHED — never a producer stall — with NO lease cancel\r\n    // (overflow is a subscriber problem, not a conn poison), and every\r\n    // appended chunk is still in the ring so the brain resumes from its\r\n    // cursor gaplessly.\r\n    #[test]\r\n    fn overflow_detaches_without_lease_cancel_and_the_brain_resumes_from_cursor() {\r\n        let (sub, _client, _recv) = seat_socket_pair(Duration::from_secs(60));\r\n        let mut log = StreamLog::new(9, 4096);\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        // Park the seat's writer on its first write (the gate pin — bounded\r\n        // by the 60s deadline, far past this test) so the queue fills.\r\n        let pin = sub.pin_gate_for_test();\r\n        let mut detached_at = None;\r\n        for i in 0..(SUBSCRIBER_QUEUE_CAP + 8) {\r\n            log.append(&[(i % 251) as u8]);\r\n            if log.subscriber.is_none() {\r\n                detached_at = Some(i);\r\n                break;\r\n            }\r\n        }\r\n        let at = detached_at.expect(\"the bounded queue must overflow-detach\");\r\n        assert!(\r\n            at >= SUBSCRIBER_QUEUE_CAP,\r\n            \"no premature eviction: detached at append {at} with cap {SUBSCRIBER_QUEUE_CAP}\"\r\n        );\r\n        assert!(\r\n            !lease.is_canceled(),\r\n            \"overflow is a slow subscriber, not poison — the serve lease survives\"\r\n        );\r\n\r\n        // Resume-from-cursor: every appended chunk (0..=at) is retained; a\r\n        // re-attach from an arbitrary cursor replays exactly the tail.\r\n        let replayed = log\r\n            .begin_attach(Arc::clone(&sub), 5)\r\n            .expect(\"the detached brain re-attaches after the overflow\");\r\n        assert_eq!(\r\n            replayed,\r\n            at + 1 - 5,\r\n            \"gapless replay of seqs 5..={at} (nothing lost to the overflow)\"\r\n        );\r\n        drop(pin); // release the parked writers so they drain and exit\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] gate follow-up (the main-CI\r\n    // xfer re-serve red, \"got seq 134 want 6\"): replay-then-live order is\r\n    // STRUCTURAL — a producer appending WHILE the attach replay is still\r\n    // undrained can never interleave a live frame ahead of (or into) the\r\n    // replay on the wire. DETERMINISTIC: the conn's write gate stays pinned\r\n    // across the attach AND the concurrent appends, so the seat writer cannot\r\n    // emit a byte until both have fully raced — the wire order then proves\r\n    // the seat's handoff ordering, not scheduler luck. RED on the 0c70253\r\n    // shape (caller-side replay enqueue racing producer enqueues on one\r\n    // queue) — demonstrated by the xfer push_survives repeat-run in the fix\r\n    // PR; this unit pins the ordering contract going forward.\r\n    #[test]\r\n    fn attach_replay_never_interleaves_with_concurrent_live_appends() {\r\n        let (sub, mut client, _recv) = seat_socket_pair(Duration::from_secs(30));\r\n        let mut log = StreamLog::new(5, 4096);\r\n        for i in 0..6u8 {\r\n            log.append(&[i]); // ring seqs 0..=5 — the attach replay\r\n        }\r\n\r\n        // Pin the wire: nothing the writer does lands until we release.\r\n        let pin = sub.pin_gate_for_test();\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach with a pending replay\");\r\n        // Live appends land while the replay is still entirely undrained.\r\n        for i in 6..10u8 {\r\n            log.append(&[i]); // live seqs 6..=9\r\n        }\r\n        drop(pin);\r\n\r\n        // The wire must show 0..=9 DENSE — any live frame ahead of the\r\n        // replay tail is exactly the re-serve gap the field saw.\r\n        for want in 0u64..10 {\r\n            let env = crate::codec::read_frame(&mut client).expect(\"frame on the wire\");\r\n            assert_eq!(env.kind, crate::msg::KIND_NET_STREAM_DATA);\r\n            let seq = env.payload.get(\"seq\").and_then(|v| v.as_u64()).expect(\"seq\");\r\n            assert_eq!(\r\n                seq, want,\r\n                \"replay-then-live seq order must be structural; a live frame \\\r\n                 interleaved into the replay = the xfer re-serve gap\"\r\n            );\r\n        }\r\n        drop(log);\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] fix-PR gate round 1: every\r\n    // install mints a FRESH lease Arc, so a HEALTHILY DISPLACED writer's\r\n    // eventual write failure (its conn dying — every brain swap / daemon\r\n    // refresh) cancels only ITS OWN generation's Arc, never the successor's.\r\n    // RED on the renewal-on-canceled shape: healthy displacement left the\r\n    // displaced writer and the successor SHARING one Arc, and the old\r\n    // writer's queued-frame failure serve-killed the successor — a spurious\r\n    // lease refusal + breaker trip per displaced stream on exactly the\r\n    // post-refresh path this wave calms.\r\n    #[test]\r\n    fn displaced_writer_failure_never_cancels_the_successor_lease() {\r\n        let (a, client_a, _ra) = seat_socket_pair(Duration::from_secs(5));\r\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(11, 64);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"attach A\");\r\n        let lease_a = Arc::clone(&log.lease);\r\n\r\n        // Queue a frame for A's writer and PARK its write (gate pinned) so\r\n        // the failure lands AFTER the displacement, as in a real brain swap.\r\n        let pin = a.pin_gate_for_test();\r\n        log.append(b\"queued-for-the-displaced-writer\");\r\n\r\n        // HEALTHY displacement: B takes the seat (the brain-swap contract).\r\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\r\n        let lease_b = Arc::clone(&log.lease);\r\n        assert!(\r\n            !Arc::ptr_eq(&lease_a, &lease_b),\r\n            \"every install mints its own lease generation\"\r\n        );\r\n\r\n        // A's conn dies; its parked write fails; the displaced writer\r\n        // poisons and cancels ITS OWN lease...\r\n        drop(client_a);\r\n        drop(pin);\r\n        for _ in 0..1200 {\r\n            if lease_a.is_canceled() {\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(\r\n            lease_a.is_canceled(),\r\n            \"the displaced writer cancels its own generation's lease\"\r\n        );\r\n        // ...and the SUCCESSOR's lease stays live — its serve leg keeps\r\n        // forwarding (send_stream reads this current Arc).\r\n        assert!(\r\n            !lease_b.is_canceled(),\r\n            \"the displaced writer's death must never cancel the successor's lease\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the PresenceLog rides the\r\n    // SAME seat discipline (Amendment fixes 2+3 mirrors — gate ruling 5\r\n    // counts these sites): push through a poisoned seat halts-and-removes,\r\n    // retained events replay on attach, and the poisoned-not-gone reinstall\r\n    // refusal matches StreamLog's.\r","truncated":true,"truncatedBy":"lines","totalLines":487,"totalBytes":20832,"outputLines":430,"outputBytes":20832,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn opener_capture_gives_up_bounded_on_a_newline_less_stream() {\n        let mut log = StreamLog::new(8, 4);\r\n        log.append(&vec![b'x'; OPENER_PIN_MAX + 1]);\r\n        assert_eq!(log.opener_line(), None, \"over the cap without a newline: gave up\");\r\n        log.append(b\"late-line\\n\");\r\n        assert_eq!(log.opener_line(), None, \"Oversize is terminal\");\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\r\n    // named row from dispatch eligibility: the retired stream leaves the\r\n    // dispatch enumeration (stream_infos) while the active sibling stays\r\n    // claimable, a second retire is a no-op, and the pinned-opener query\r\n    // answers no-such-stream afterwards. Retirement is visibility, not\r\n    // teardown — the transport half of the contract is the sibling test\r\n    // `retired_row_still_flushes_a_late_reply`.\r\n    #[test]\r\n    fn retire_hides_the_row_while_active_rows_stay() {\r\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\r\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\r\n        host.send_stream(owner, b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\\n\", false)\r\n            .expect(\"send opener\");\r\n        // The pump is async — wait for the shell row to hold the opener.\r\n        let mut pinned = None;\r\n        for _ in 0..400 {\r\n            pinned = host.stream_opener(shell).expect(\"shell row exists\");\r\n            if pinned.is_some() {\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert_eq!(\r\n            pinned.as_deref(),\r\n            Some(&b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\"[..]),\r\n            \"the peer row pinned the opener the owner wrote\"\r\n        );\r\n\r\n        assert!(host.retire_stream(shell), \"first retire hides the row\");\r\n        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\r\n        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\r\n        assert!(ids.contains(&owner), \"the active sibling row stays\");\r\n        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\r\n        assert!(\r\n            host.stream_opener(shell).is_none(),\r\n            \"a retired stream answers no-such-stream\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\r\n    // transport: a reply sent AFTER the row was retired still flushes to the\r\n    // peer. This is the field race's lost ordering made deterministic — the\r\n    // worker's reply send is fire-and-forget (brain returns before the broker\r\n    // processes the envelope), so the acked retire on its own conn can be\r\n    // processed FIRST. Pre-fix, retire removed the entry: the late send hit\r\n    // no-such-stream (or the drop reset the never-finished send half) and the\r\n    // requester got torn-EOF instead of its reply (wansend NoReply, CI red\r\n    // @2a110ee, 30/150 under contention vs 0/150 with retire disabled).\r\n    #[test]\r\n    fn retired_row_still_flushes_a_late_reply() {\r\n        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\r\n        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\r\n        // The requester's one-shot request: write + FIN (the rest/sync shape —\r\n        // the row is born finished on the serving side).\r\n        host.send_stream(\r\n            owner,\r\n            b\"{\\\"kind\\\":\\\"request\\\",\\\"endpoint\\\":\\\"e\\\",\\\"event\\\":\\\"suspend\\\"}\\n\",\r\n            true,\r\n        )\r\n        .expect(\"send request\");\r\n\r\n        // Retire FIRST — the ordering the race loses in the field.\r\n        assert!(host.retire_stream(shell), \"retire the serving row\");\r\n\r\n        // The worker's reply lands AFTER retirement and must still deliver.\r\n        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\r\n            .expect(\"a retired row still accepts the in-flight reply\");\r\n        let mut got = Vec::new();\r\n        let mut finished = false;\r\n        for _ in 0..400 {\r\n            let (bytes, fin) = host.drain_stream(owner).expect(\"owner row exists\");\r\n            got.extend_from_slice(&bytes);\r\n            if fin {\r\n                finished = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(\r\n            String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),\r\n            \"the reply flushed through the retired row (got {got:?})\"\r\n        );\r\n        assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");\r\n    }\r\n\r\n    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\r\n    // stream rows with it: the closed-watcher sweep leaves nothing for a\r\n    // future dispatcher generation to replay on a transport that can never\r\n    // serve again (the field bug's historical-rows-forever half).\r\n    #[test]\r\n    fn conn_close_sweeps_its_stream_rows() {\r\n        let a = NetHost::start(hermetic(&Identity::generate())).expect(\"host a\");\r\n        let b = NetHost::start(hermetic(&Identity::generate())).expect(\"host b\");\r\n        let (conn_id, _) = a.dial(b.addr()).expect(\"dial\");\r\n        let sid = a.open_stream(conn_id).expect(\"open stream\");\r\n        a.send_stream(sid, b\"{\\\"hello\\\":1}\\n\", false).expect(\"send\");\r\n        // B's acceptor registers the peer row (async — poll).\r\n        let mut saw = false;\r\n        for _ in 0..400 {\r\n            if !b.stream_infos().is_empty() {\r\n                saw = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(saw, \"the peer stream row registered on b\");\r\n\r\n        a.close(); // graceful CLOSE — b's closed-watcher fires promptly\r\n        drop(a);\r\n        let mut swept = false;\r\n        for _ in 0..600 {\r\n            if b.stream_infos().is_empty() {\r\n                swept = true;\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(10));\r\n        }\r\n        assert!(swept, \"the closed-watcher swept the dead conn's stream rows\");\r\n    }\r\n\r\n    // ── ADR-0038 Amendment fixes 2+3+4 — the subscriber-seat discipline at\r\n    //    the StreamLog/PresenceLog seam (REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE).\r\n    //    A real socket pair per seat: dropping the client side makes the\r\n    //    seat's writer fail its next conn write (the poison class); pinning\r\n    //    the conn's write gate parks the writer (the slow-consumer class). ──\r\n\r\n    /// A connected local-socket pair whose SERVER send half wraps as the\r\n    /// `SharedSend` a subscriber seat writes through — `controller_socket_pair`'s\r\n    /// idiom (broker.rs) with a caller-chosen write bound.\r\n    fn seat_socket_pair(\r\n        bound: Duration,\r\n    ) -> (\r\n        SharedSend,\r\n        interprocess::local_socket::Stream,\r\n        interprocess::local_socket::RecvHalf,\r\n    ) {\r\n        use crate::transport::{DaemonTransport, LocalSocketTransport};\r\n        use interprocess::local_socket::prelude::*;\r\n        use std::sync::atomic::AtomicU32;\r\n        static SEQ: AtomicU32 = AtomicU32::new(0);\r\n        let name = format!(\r\n            \"spt-daemon-seat-{}-{}.sock\",\r\n            std::process::id(),\r\n            SEQ.fetch_add(1, Ordering::Relaxed)\r\n        );\r\n        let listener = LocalSocketTransport::bind(&name).expect(\"bind listener\");\r\n        let client = LocalSocketTransport::connect(&name).expect(\"connect client\");\r\n        let server = listener.accept().expect(\"accept server side\");\r\n        let (recv, send) = server.split();\r\n        (\r\n            Arc::new(crate::conn::BrokerConn::new(send, bound)),\r\n            client,\r\n            recv,\r\n        )\r\n    }\r\n\r\n    /// Poll until the installed seat reports POISONED (its writer died on a\r\n    /// failed conn write) — or is already gone. Bounded; panics on timeout.\r\n    fn wait_seat_poisoned(seat_poisoned: impl Fn() -> bool) {\r\n        for _ in 0..600 {\r\n            if seat_poisoned() {\r\n                return;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        panic!(\"seat never poisoned — the dead conn's write should have failed\");\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the APPEND producer site\r\n    // (Amendment fix 2): the seat's writer halts at its FIRST failed write\r\n    // (poison), the next append removes the seat, the stream's serve lease is\r\n    // canceled (fix 4), and later appends never re-feed the dead subscriber —\r\n    // the pre-Amendment shape iterated the ring with `let _ = sub.write()`\r\n    // discarded errors, serially eating a 15s bounded write per frame.\r\n    #[test]\r\n    fn append_halts_at_first_failed_write_removes_the_seat_and_cancels_the_lease() {\r\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(7, 64);\r\n        assert_eq!(log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\"), 0);\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        // Kill the conn: the peer side drops, so the writer's next write fails.\r\n        drop(client);\r\n        log.append(b\"one\"); // enqueue fine; the WRITE fails writer-side\r\n        wait_seat_poisoned(|| {\r\n            log.subscriber\r\n                .as_ref()\r\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\r\n        });\r\n\r\n        // The next producer append sees the poisoned seat: halt-and-remove +\r\n        // lease cancel — never another write attempt against the dead conn.\r\n        log.append(b\"two\");\r\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the append site\");\r\n        assert!(lease.is_canceled(), \"poison cancels the serve lease (fix 4)\");\r\n\r\n        // Later appends stay seatless (no reinstall, no panic, no re-feed).\r\n        log.append(b\"three\");\r\n        assert!(log.subscriber.is_none());\r\n\r\n        // Recovery (the T5 not-abandonment half): a NEW subscriber generation\r\n        // RENEWS the lease — the old worker's handle stays canceled, fresh\r\n        // sends serve again. One poison never permanently dead-ends a stream.\r\n        let (fresh, _cf, _rf) = seat_socket_pair(Duration::from_secs(5));\r\n        log.begin_attach(Arc::clone(&fresh), 0).expect(\"new generation attaches\");\r\n        assert!(\r\n            !log.lease.is_canceled(),\r\n            \"a new subscriber generation renews the serve lease\"\r\n        );\r\n        assert!(\r\n            lease.is_canceled(),\r\n            \"the OLD worker's lease handle stays canceled (its leg stays dead)\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the FINISH producer site\r\n    // (Amendment fix 2 is ALL-sites — gate ruling 5: a fix that patches\r\n    // attach only fails): eof delivery through a poisoned seat removes it\r\n    // and cancels the lease, and the log still records finished.\r\n    #[test]\r\n    fn finish_halts_and_removes_a_poisoned_seat() {\r\n        let (sub, client, _recv) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(8, 64);\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        drop(client);\r\n        log.append(b\"x\");\r\n        wait_seat_poisoned(|| {\r\n            log.subscriber\r\n                .as_ref()\r\n                .is_none_or(|s| s.poisoned.load(Ordering::Acquire))\r\n        });\r\n\r\n        log.finish();\r\n        assert!(log.finished, \"the read side still records its clean end\");\r\n        assert!(log.subscriber.is_none(), \"halt-and-remove at the finish site\");\r\n        assert!(lease.is_canceled(), \"poison at finish cancels the lease too\");\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the reinstall gate: a POISONED\r\n    // prior seat whose writer is still draining refuses a replacement\r\n    // (`WouldBlock` \"subscriber busy\" — the string the dispatcher classifies\r\n    // SeatBlocked), a HEALTHY prior is DISPLACED as before (the\r\n    // newer-brain-takes-over contract a blanket no-reinstall broke — it\r\n    // TIMEOUTed the applyhost brain-swap tests), and a fully-GONE prior\r\n    // admits the replacement. The poisoned-not-gone state is white-boxed\r\n    // (flags on a live parked writer) — the deterministic stand-in for a\r\n    // writer stuck mid-15s bounded write; T5's int covers the composition.\r\n    #[test]\r\n    fn begin_attach_refuses_only_while_a_poisoned_prior_still_drains() {\r\n        let (a, _ca, _ra) = seat_socket_pair(Duration::from_secs(5));\r\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(3, 64);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"first attach\");\r\n\r\n        // Healthy prior → displaced (the legit brain-swap path).\r\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\r\n        assert!(log.subscriber.as_ref().unwrap().is(&b));\r\n\r\n        // Poisoned but NOT gone (writer still draining) → refuse WouldBlock.\r\n        let seat = log.subscriber.as_ref().unwrap();\r\n        seat.poisoned.store(true, Ordering::Release);\r\n        let err = log\r\n            .begin_attach(Arc::clone(&a), 0)\r\n            .expect_err(\"no reinstall until the prior seat is fully gone\");\r\n        assert_eq!(err.kind(), io::ErrorKind::WouldBlock);\r\n        assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");\r\n\r\n        // Fully gone → the replacement installs.\r\n        log.subscriber.as_ref().unwrap().done.store(true, Ordering::Release);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"gone prior admits the replacement\");\r\n        assert!(log.subscriber.as_ref().unwrap().is(&a));\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the OVERFLOW class\r\n    // (Amendment fix 3): a slow-but-healthy subscriber whose bounded queue\r\n    // fills is DETACHED — never a producer stall — with NO lease cancel\r\n    // (overflow is a subscriber problem, not a conn poison), and every\r\n    // appended chunk is still in the ring so the brain resumes from its\r\n    // cursor gaplessly.\r\n    #[test]\r\n    fn overflow_detaches_without_lease_cancel_and_the_brain_resumes_from_cursor() {\r\n        let (sub, _client, _recv) = seat_socket_pair(Duration::from_secs(60));\r\n        let mut log = StreamLog::new(9, 4096);\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach\");\r\n        let lease = Arc::clone(&log.lease);\r\n\r\n        // Park the seat's writer on its first write (the gate pin — bounded\r\n        // by the 60s deadline, far past this test) so the queue fills.\r\n        let pin = sub.pin_gate_for_test();\r\n        let mut detached_at = None;\r\n        for i in 0..(SUBSCRIBER_QUEUE_CAP + 8) {\r\n            log.append(&[(i % 251) as u8]);\r\n            if log.subscriber.is_none() {\r\n                detached_at = Some(i);\r\n                break;\r\n            }\r\n        }\r\n        let at = detached_at.expect(\"the bounded queue must overflow-detach\");\r\n        assert!(\r\n            at >= SUBSCRIBER_QUEUE_CAP,\r\n            \"no premature eviction: detached at append {at} with cap {SUBSCRIBER_QUEUE_CAP}\"\r\n        );\r\n        assert!(\r\n            !lease.is_canceled(),\r\n            \"overflow is a slow subscriber, not poison — the serve lease survives\"\r\n        );\r\n\r\n        // Resume-from-cursor: every appended chunk (0..=at) is retained; a\r\n        // re-attach from an arbitrary cursor replays exactly the tail.\r\n        let replayed = log\r\n            .begin_attach(Arc::clone(&sub), 5)\r\n            .expect(\"the detached brain re-attaches after the overflow\");\r\n        assert_eq!(\r\n            replayed,\r\n            at + 1 - 5,\r\n            \"gapless replay of seqs 5..={at} (nothing lost to the overflow)\"\r\n        );\r\n        drop(pin); // release the parked writers so they drain and exit\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] gate follow-up (the main-CI\r\n    // xfer re-serve red, \"got seq 134 want 6\"): replay-then-live order is\r\n    // STRUCTURAL — a producer appending WHILE the attach replay is still\r\n    // undrained can never interleave a live frame ahead of (or into) the\r\n    // replay on the wire. DETERMINISTIC: the conn's write gate stays pinned\r\n    // across the attach AND the concurrent appends, so the seat writer cannot\r\n    // emit a byte until both have fully raced — the wire order then proves\r\n    // the seat's handoff ordering, not scheduler luck. RED on the 0c70253\r\n    // shape (caller-side replay enqueue racing producer enqueues on one\r\n    // queue) — demonstrated by the xfer push_survives repeat-run in the fix\r\n    // PR; this unit pins the ordering contract going forward.\r\n    #[test]\r\n    fn attach_replay_never_interleaves_with_concurrent_live_appends() {\r\n        let (sub, mut client, _recv) = seat_socket_pair(Duration::from_secs(30));\r\n        let mut log = StreamLog::new(5, 4096);\r\n        for i in 0..6u8 {\r\n            log.append(&[i]); // ring seqs 0..=5 — the attach replay\r\n        }\r\n\r\n        // Pin the wire: nothing the writer does lands until we release.\r\n        let pin = sub.pin_gate_for_test();\r\n        log.begin_attach(Arc::clone(&sub), 0).expect(\"attach with a pending replay\");\r\n        // Live appends land while the replay is still entirely undrained.\r\n        for i in 6..10u8 {\r\n            log.append(&[i]); // live seqs 6..=9\r\n        }\r\n        drop(pin);\r\n\r\n        // The wire must show 0..=9 DENSE — any live frame ahead of the\r\n        // replay tail is exactly the re-serve gap the field saw.\r\n        for want in 0u64..10 {\r\n            let env = crate::codec::read_frame(&mut client).expect(\"frame on the wire\");\r\n            assert_eq!(env.kind, crate::msg::KIND_NET_STREAM_DATA);\r\n            let seq = env.payload.get(\"seq\").and_then(|v| v.as_u64()).expect(\"seq\");\r\n            assert_eq!(\r\n                seq, want,\r\n                \"replay-then-live seq order must be structural; a live frame \\\r\n                 interleaved into the replay = the xfer re-serve gap\"\r\n            );\r\n        }\r\n        drop(log);\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] fix-PR gate round 1: every\r\n    // install mints a FRESH lease Arc, so a HEALTHILY DISPLACED writer's\r\n    // eventual write failure (its conn dying — every brain swap / daemon\r\n    // refresh) cancels only ITS OWN generation's Arc, never the successor's.\r\n    // RED on the renewal-on-canceled shape: healthy displacement left the\r\n    // displaced writer and the successor SHARING one Arc, and the old\r\n    // writer's queued-frame failure serve-killed the successor — a spurious\r\n    // lease refusal + breaker trip per displaced stream on exactly the\r\n    // post-refresh path this wave calms.\r\n    #[test]\r\n    fn displaced_writer_failure_never_cancels_the_successor_lease() {\r\n        let (a, client_a, _ra) = seat_socket_pair(Duration::from_secs(5));\r\n        let (b, _cb, _rb) = seat_socket_pair(Duration::from_secs(5));\r\n        let mut log = StreamLog::new(11, 64);\r\n        log.begin_attach(Arc::clone(&a), 0).expect(\"attach A\");\r\n        let lease_a = Arc::clone(&log.lease);\r\n\r\n        // Queue a frame for A's writer and PARK its write (gate pinned) so\r\n        // the failure lands AFTER the displacement, as in a real brain swap.\r\n        let pin = a.pin_gate_for_test();\r\n        log.append(b\"queued-for-the-displaced-writer\");\r\n\r\n        // HEALTHY displacement: B takes the seat (the brain-swap contract).\r\n        log.begin_attach(Arc::clone(&b), 0).expect(\"healthy displacement\");\r\n        let lease_b = Arc::clone(&log.lease);\r\n        assert!(\r\n            !Arc::ptr_eq(&lease_a, &lease_b),\r\n            \"every install mints its own lease generation\"\r\n        );\r\n\r\n        // A's conn dies; its parked write fails; the displaced writer\r\n        // poisons and cancels ITS OWN lease...\r\n        drop(client_a);\r\n        drop(pin);\r\n        for _ in 0..1200 {\r\n            if lease_a.is_canceled() {\r\n                break;\r\n            }\r\n            std::thread::sleep(Duration::from_millis(5));\r\n        }\r\n        assert!(\r\n            lease_a.is_canceled(),\r\n            \"the displaced writer cancels its own generation's lease\"\r\n        );\r\n        // ...and the SUCCESSOR's lease stays live — its serve leg keeps\r\n        // forwarding (send_stream reads this current Arc).\r\n        assert!(\r\n            !lease_b.is_canceled(),\r\n            \"the displaced writer's death must never cancel the successor's lease\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the PresenceLog rides the\r\n    // SAME seat discipline (Amendment fixes 2+3 mirrors — gate ruling 5\r\n    // counts these sites): push through a poisoned seat halts-and-removes,\r\n    // retained events replay on attach, and the poisoned-not-gone reinstall\r\n    // refusal matches StreamLog's.\r","startLine":2378,"lineNumbers":[2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":20832,"outputLines":430,"outputBytes":20832,"shownRange":{"start":2379,"end":2808},"nextOffset":2809}}},"isError":false,"timestamp":1784271591784}}
{"type":"message","id":"955e8da1","parentId":"524c510b","timestamp":"2026-07-17T06:59:51.957Z","message":{"role":"toolResult","toolCallId":"call_YeDPuaBUgbwFiolA1imVUnax|fc_089095e18118ee7d016a59d2e67a1c8190aeb7976a24ba0694","toolName":"read","content":[{"type":"text","text":"    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n…\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n…\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n…\n    }\n\n[Showing lines 3254-3298 of 6298. Use :3299 to continue]"}],"details":{"truncation":{"content":"                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {","truncated":true,"truncatedBy":"lines","totalLines":3045,"totalBytes":2179,"outputLines":45,"outputBytes":2179,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n…\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n…\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n…\n    }","startLine":3025,"lineNumbers":[3025,null,3055,null,3061,null,3249,null,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,null,3512]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":2179,"outputLines":45,"outputBytes":2179,"shownRange":{"start":3254,"end":3298},"nextOffset":3299}}},"isError":false,"timestamp":1784271591957}}
{"type":"message","id":"9a3b8619","parentId":"955e8da1","timestamp":"2026-07-17T06:59:51.990Z","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\nFresh v0.36 evidence: repeated Registry-family stream subscriber poison. Examples stream 524/1161/1347/3053/3810: CONN_WRITE_POISONED after ~15s, DISPATCH_EV family=Registry endpoint=- elapsed 15.5-16.4s. Current broker ~550 threads; brain reads ~64 MiB/s. Investigate Registry stream lifecycle/replay specifically.","attribution":"agent","timestamp":1784271582387,"steering":true}}
{"type":"message","id":"1567917b","parentId":"9a3b8619","timestamp":"2026-07-17T07:00:05.097Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing registry feed semantics**\n\n**Inspecting peerloop registry send**"},{"type":"toolCall","id":"call_c6HLJvYKLZykLWUxaCjApIgn|fc_089095e18118ee7d016a59d2f4c5888190a08814805e283bfe","name":"grep","arguments":{"i":"Tracing registry stream producers","pattern":"RegistryFeedRecord|RegistryUpdate|net_stream_send\\(|open_stream|registry.*feed|send_registry|push_registry|encode_line","path":"crates/spt-daemon/src","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_NtWFQwH8CT731VcI0LeebQMz|fc_089095e18118ee7d016a59d2f4c5c081908f35abe93932e64a","name":"grep","arguments":{"i":"Finding registry replay tests","pattern":"Registry|registry.*stream|serve_registry_feed|registry feed|stream.*registry|redispatch","path":"crates/spt-daemon/tests; crates/spt-daemon/src/dispatch.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_MyAfIOKBlneFWwJxQY3FtuJg|fc_089095e18118ee7d016a59d2f4c5d081909cc042c5481462cf","name":"read","arguments":{"i":"Reading stall RCA summary","path":".claude/reports/2026-07-16-redispatch-stall/rca-summary.md","selector":null}},{"type":"toolCall","id":"call_gVzHb4Gm9GRBCd9emjEmzCPY|fc_089095e18118ee7d016a59d2f4c5dc819089e37f877a21e3b6","name":"read","arguments":{"i":"Reading redispatch truth RCA","path":".claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt","selector":null}},{"type":"toolCall","id":"call_2hIiLxOXKlmQ7VjmJLqjoKLI|fc_089095e18118ee7d016a59d2f4c5e88190aeae5f0a4721f633","name":"grep","arguments":{"i":"Locating registry cadence loop","pattern":"due_reg|REGISTRY|registry","path":"crates/spt-daemon/src/peerloop.rs; crates/spt-daemon/src/pump.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_bVCcvDMJCVuOPQpUa0It71jt|fc_089095e18118ee7d016a59d2f4c5f08190a3a1764f02e75f1f","name":"read","arguments":{"i":"Mapping peer loop declarations","path":"crates/spt-daemon/src/peerloop.rs","selector":null}},{"type":"toolCall","id":"call_x70ZLTTMqBmpxggCRxqpRf2g|fc_089095e18118ee7d016a59d2f4c6588190b22fb7a83ab94f83","name":"read","arguments":{"i":"Mapping pump declarations","path":"crates/spt-daemon/src/pump.rs","selector":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":10176,"output":572,"cacheRead":172544,"cacheWrite":0,"totalTokens":183292,"reasoningTokens":172,"cost":{"input":0.05088,"output":0.01716,"cacheRead":0.086272,"cacheWrite":0,"total":0.154312}},"stopReason":"toolUse","timestamp":1784271592003,"responseId":"resp_089095e18118ee7d016a59d2e8450881909ac36d2061670a0f","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2ea63048190a1a4f368f99f54d5","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdL0sFqaeHuKREfHo9zEOLttiIv7l8xUVspRpjpsID2A3SLtE6h79BwLVz0WpNVjPNrea_vzk6M9B8kKEkzlqWD8o32j17IzSNsV_EzXy2Vx2VjBZuGHCVFMquoGb3wMtXKhoonNtG8KmIOUaukLaeBJ8g_B88HFB1YavbIJvjx-qo1FIugbe9KSsjblmtH9jZYAo2fu3GUbiNUNyUwAC5dnYZ70YryTblv4HBuEoTGz5O9g7zz6a9GAvpQlHKU3A4UW2ELqP1QukKxe_2HcSNKLE8OkspYrR4OZg5rm-pb2fNVVUx6E0jYjMMCVfx9BysZt5zM2gglJM-QRZMkJ73397YdaX9K-CkIjGfW7LSavRQoziyX8x7rkuSp1CfDbxDs-_2vZovyRwDwQgqOx0bMSq3g2ezlIf8cg_64Q1K2ruH3SLaUdJlYrtE_9TWtVmDZ8dTcbu2kYG2wdxY9EDsoMHHTkfdpFZ56VmottLaLp9589PL0shCQBgLRupn__Y_U9VsEM2hCPt5WC9oU1mnaHBPu84FJvCUWGzkE76J5ybSVmP_2oMgM6s32WtLD_N2xJkFQIsHCxoE5DiwwTteJlTvJwuJOaDlllpzw0IScJXkXg6MrQalvPVEEvZCANEhgso-BFPou1PfUImemn4jhJ8QIu6vFkMyM-7pt5C8F0yu1RWNEOL1dcC00xhTgRApJ47QzaYTB589VTakje8KAy1D3Uy4pR7HH3Lcd5npUl26Kl3H5GUF02wlYHlAOJjcKhj83XBmhKRj4GqD8C76i5-GexuLvfTD1Ti9fzNDzeCTX0PLOaUDvbrbXoubhvOyHjY8YvJd_o46xYmqYmqKfMvB-9aCvHX0pNmvGluRaxpwvdPTxxpPmAHNi2XEbjjvFDNb56cRklDBinrEIASd0ee1NFnmuRom3dTp48dIuEL-EqdJeoPSxcAOBQxStBX2MU7q8MZjErpCnT2xNpMkfCECjDhGpbu4d9iosOniX44By6-P664ccEKjoluSXHyunC8TrsEO4ovdYoqOgIhwatrfuXhUIWbobVJTS-FU1-xhyfq6xZHDoP4ZySCEC4VrD3vy3uw6tLEb5SIz__P2OGs_JUpjBM1NJ79qdVDBa4LUXvoIqSD411qWYsw7In549U0p_BmJLcWVObWZInXYP-2fKNRNXWTT7-RVj7ey-R3PcKeWr252NLl6GU4Ne2vngo_Q3h-Mcr0iQ0J21oKRSAgCAzchVnFahR2-t30FRKomn3hIB1TwTh8ji-Ouo1JOI7ed_5IDZVpj_OdUiQN2WowQuoK5WWmjUFHNl3waLLhVvU42Z_Yg2wIjI9SEVElCIoMtiKbR26JJu9Iseqh0Z-WFrlrZ-FJdYCMI0oWtVat0v46UhyxEp_n8fazIcKehCQLWAU0KZblax02ZGlxs1UBy1f8gtOsO3nrY7tXT1sYojrvUZfqM6DmmpgSZfyQREgJfC9yjuJ-XI5s_I9UNP8-ILuuIzhjQHhCshRiKKkxmLzgm-hmQmsafxoFMb0igkgKgiKeJNeZ2gtENI8VkS-Wej5MbRXkx4N8ZkcNLe1c8jdFcbaBK9911TivB8c84huZBo9IbRhgxnYHi2AWpj8fWSK53_bRDC0_dI-UEcETsH1U975b_CFqY2rH1C1ZtE5FYF_OpcZO5O-AO2ah5uqROO0L5_VZXOqAqxZk0moECz_zFikur1g98fElhsJLK4fchBhJr8xW9J-xpIQEQRRUOw9hPdOsEOL4S52Fl8Vyv4KDKi_h_2e7bnaO4Y4JXg9Xpg98TusGKssfIoLwOgukjPOY2dOt4ssXCQx5r0DEEU7f2qnU2hmWctAb4Mz3bofaDAOHyud6xzESAYCuH2eYWw0u4z8OKyNAw-u-Yud7jjPmyY_wY8vJ0d77GIXSiy1w16nbq5r-zhkaipp2UrGgPFxf8KzhMkSnXpnVvxw3e-lbSosY20rgtnL1eXTLZNCnvR6vk17n_NPPUDzV-1QLTmLZtZcIvcfQ-YJE7jrZyOC61AuWeMKGztosr_yh-qqv_5SMCDQXZSaTTrpzfEVDmRafWESp92mNWBjMwQmvR6R4R3wf5Qbj-Ucw0lp0o5o","summary":[{"type":"summary_text","text":"**Tracing registry feed semantics**"},{"type":"summary_text","text":"**Inspecting peerloop registry send**"}]},{"id":"fc_089095e18118ee7d016a59d2f4c5888190a08814805e283bfe","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing registry stream producers\",\"pattern\":\"RegistryFeedRecord|RegistryUpdate|net_stream_send\\\\(|open_stream|registry.*feed|send_registry|push_registry|encode_line\",\"path\":\"crates/spt-daemon/src\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_c6HLJvYKLZykLWUxaCjApIgn","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2f4c5c081908f35abe93932e64a","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding registry replay tests\",\"pattern\":\"Registry|registry.*stream|serve_registry_feed|registry feed|stream.*registry|redispatch\",\"path\":\"crates/spt-daemon/tests; crates/spt-daemon/src/dispatch.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_NtWFQwH8CT731VcI0LeebQMz","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2f4c5d081909cc042c5481462cf","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stall RCA summary\",\"path\":\".claude/reports/2026-07-16-redispatch-stall/rca-summary.md\",\"selector\":null}","call_id":"call_MyAfIOKBlneFWwJxQY3FtuJg","name":"read"},{"id":"fc_089095e18118ee7d016a59d2f4c5dc819089e37f877a21e3b6","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch truth RCA\",\"path\":\".claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt\",\"selector\":null}","call_id":"call_gVzHb4Gm9GRBCd9emjEmzCPY","name":"read"},{"id":"fc_089095e18118ee7d016a59d2f4c5e88190aeae5f0a4721f633","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating registry cadence loop\",\"pattern\":\"due_reg|REGISTRY|registry\",\"path\":\"crates/spt-daemon/src/peerloop.rs; crates/spt-daemon/src/pump.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_2hIiLxOXKlmQ7VjmJLqjoKLI","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2f4c5f08190a3a1764f02e75f1f","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping peer loop declarations\",\"path\":\"crates/spt-daemon/src/peerloop.rs\",\"selector\":null}","call_id":"call_bVCcvDMJCVuOPQpUa0It71jt","name":"read"},{"id":"fc_089095e18118ee7d016a59d2f4c6588190b22fb7a83ab94f83","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping pump declarations\",\"path\":\"crates/spt-daemon/src/pump.rs\",\"selector\":null}","call_id":"call_x70ZLTTMqBmpxggCRxqpRf2g","name":"read"}]},"duration":13070.449900001287,"ttft":2403.7210000008345,"contextSnapshot":{"promptTokens":182720,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_c6HLJvYKLZykLWUxaCjApIgn|fc_089095e18118ee7d016a59d2f4c5888190a08814805e283bfe","toolName":"grep","startedAt":"2026-07-17T07:00:05.098Z","args":{"path":"crates/spt-daemon/src"},"intent":"Tracing registry stream producers"},"id":"f1286e6a","parentId":"1567917b","timestamp":"2026-07-17T07:00:05.098Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_NtWFQwH8CT731VcI0LeebQMz|fc_089095e18118ee7d016a59d2f4c5c081908f35abe93932e64a","toolName":"grep","startedAt":"2026-07-17T07:00:05.098Z","args":{"path":"crates/spt-daemon/tests; crates/spt-daemon/src/dispatch.rs"},"intent":"Finding registry replay tests"},"id":"8a18dec6","parentId":"f1286e6a","timestamp":"2026-07-17T07:00:05.098Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_MyAfIOKBlneFWwJxQY3FtuJg|fc_089095e18118ee7d016a59d2f4c5d081909cc042c5481462cf","toolName":"read","startedAt":"2026-07-17T07:00:05.098Z","args":{"path":".claude/reports/2026-07-16-redispatch-stall/rca-summary.md"},"intent":"Reading stall RCA summary"},"id":"50edc210","parentId":"8a18dec6","timestamp":"2026-07-17T07:00:05.098Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_gVzHb4Gm9GRBCd9emjEmzCPY|fc_089095e18118ee7d016a59d2f4c5dc819089e37f877a21e3b6","toolName":"read","startedAt":"2026-07-17T07:00:05.098Z","args":{"path":".claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt"},"intent":"Reading redispatch truth RCA"},"id":"92f442a6","parentId":"50edc210","timestamp":"2026-07-17T07:00:05.098Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_2hIiLxOXKlmQ7VjmJLqjoKLI|fc_089095e18118ee7d016a59d2f4c5e88190aeae5f0a4721f633","toolName":"grep","startedAt":"2026-07-17T07:00:05.119Z","args":{"path":"crates/spt-daemon/src/peerloop.rs; crates/spt-daemon/src/pump.rs"},"intent":"Locating registry cadence loop"},"id":"a90d62f8","parentId":"92f442a6","timestamp":"2026-07-17T07:00:05.119Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bVCcvDMJCVuOPQpUa0It71jt|fc_089095e18118ee7d016a59d2f4c5f08190a3a1764f02e75f1f","toolName":"read","startedAt":"2026-07-17T07:00:05.119Z","args":{"path":"crates/spt-daemon/src/peerloop.rs"},"intent":"Mapping peer loop declarations"},"id":"5bbcd16a","parentId":"a90d62f8","timestamp":"2026-07-17T07:00:05.119Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_x70ZLTTMqBmpxggCRxqpRf2g|fc_089095e18118ee7d016a59d2f4c6588190b22fb7a83ab94f83","toolName":"read","startedAt":"2026-07-17T07:00:05.119Z","args":{"path":"crates/spt-daemon/src/pump.rs"},"intent":"Mapping pump declarations"},"id":"a3b2f2d3","parentId":"5bbcd16a","timestamp":"2026-07-17T07:00:05.119Z"}
{"type":"message","id":"0a2d79f9","parentId":"a3b2f2d3","timestamp":"2026-07-17T07:00:05.121Z","message":{"role":"toolResult","toolCallId":"call_2hIiLxOXKlmQ7VjmJLqjoKLI|fc_089095e18118ee7d016a59d2f4c5e88190aeae5f0a4721f633","toolName":"grep","content":[{"type":"text","text":"Path not found: crates/spt-daemon/src/peerloop.rs, crates/spt-daemon/src/pump.rs"}],"details":{},"isError":true,"timestamp":1784271605121}}
{"type":"message","id":"f34561ec","parentId":"0a2d79f9","timestamp":"2026-07-17T07:00:05.138Z","message":{"role":"toolResult","toolCallId":"call_bVCcvDMJCVuOPQpUa0It71jt|fc_089095e18118ee7d016a59d2f4c5f08190a3a1764f02e75f1f","toolName":"read","content":[{"type":"text","text":"Path 'crates/spt-daemon/src/peerloop.rs' not found"}],"details":{},"isError":true,"timestamp":1784271605138}}
{"type":"message","id":"1b07f782","parentId":"f34561ec","timestamp":"2026-07-17T07:00:05.138Z","message":{"role":"toolResult","toolCallId":"call_x70ZLTTMqBmpxggCRxqpRf2g|fc_089095e18118ee7d016a59d2f4c6588190b22fb7a83ab94f83","toolName":"read","content":[{"type":"text","text":"Path 'crates/spt-daemon/src/pump.rs' not found"}],"details":{},"isError":true,"timestamp":1784271605138}}
{"type":"message","id":"9bdc6832","parentId":"1b07f782","timestamp":"2026-07-17T07:00:05.151Z","message":{"role":"toolResult","toolCallId":"call_c6HLJvYKLZykLWUxaCjApIgn|fc_089095e18118ee7d016a59d2f4c5888190a08814805e283bfe","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## attach.rs\n 254|    if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {\n*255|        wire.net_stream_send(stream_id, &[], None, true)?;\n 256|        return Ok(Some(AttachServeOutcome::Refused));\n 257|    }\n 258|    let sid = remote_session.unwrap_or(session_id);\n...\n 413|                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n*414|                        let line = ndjson::encode_line(&AttachRecord::Output {\n 415|                            seq: brain.controller_resume_floor(),\n 416|                            data_b64: encode_bytes(notice),\n 417|                        });\n*418|                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n 419|                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 420|                        return Err(e);\n 421|                    }\n...\n 538|                        // renders the busy guidance client-side from driven_by.\n*539|                        wire.net_stream_send(stream_id, &[], None, true)?;\n 540|                        return Ok(AttachServeOutcome::Refused);\n 541|                    }\n 542|                }\n...\n 545|            BrokerEvent::Size { rows, cols, .. } if attached => {\n*546|                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n*547|                wire.net_stream_send(stream_id, &line, None, false)?;\n 548|            }\n 549|            // This controller was kicked by a different operator's `--take`.\n 550|            BrokerEvent::Displaced { by, .. } if attached => {\n*551|                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n*552|                wire.net_stream_send(stream_id, &line, None, true)?;\n 553|                // No dormant edge: the endpoint stays driven by the taker (whose\n 554|                // own serve loop already fired the wake).\n 555|                return Ok(AttachServeOutcome::Displaced);\n...\n 581|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*582|                wire.net_stream_send(stream_id, &[], None, true)?;\n 583|                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 584|                // info.json, carrying the current driven_by through), THEN release\n 585|                // the role over IPC so the broker's driven_by=None is the LAST\n...\n 600|            BrokerEvent::Output { seq, bytes, .. } if attached => {\n*601|                let line = ndjson::encode_line(&AttachRecord::Output {\n 602|                    seq,\n 603|                    data_b64: encode_bytes(&bytes),\n 604|                });\n...\n 606|                // the record, and the operator's render cursor is the dedup.\n*607|                wire.net_stream_send(stream_id, &line, None, false)?;\n 608|            }\n 609|            BrokerEvent::Exit { code, .. } if attached => {\n*610|                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n*611|                wire.net_stream_send(stream_id, &line, None, true)?;\n 612|                if role == Some(ServeRole::Controller) {\n 613|                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 614|                }\n...\n 666|) -> io::Result<u64> {\n*667|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*668|    let line = ndjson::encode_line(&AttachRecord::Request {\n 669|        session_id,\n 670|        from_seq,\n 671|        intent,\n...\n 678|    });\n*679|    brain.net_stream_send(opened.stream_id, &line, None, false)?;\n 680|    Ok(opened.stream_id)\n 681|}\n 682|\n...\n 694|) -> io::Result<()> {\n*695|    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });\n*696|    brain.net_stream_send(stream_id, &line, None, false)?;\n 697|    Ok(())\n 698|}\n 699|\n...\n 710|) -> io::Result<()> {\n*711|    let line = ndjson::encode_line(&AttachRecord::Input {\n 712|        data_b64: encode_bytes(bytes),\n 713|        op_id: wire_op,\n 714|    });\n*715|    brain.net_stream_send(stream_id, &line, None, false)?;\n 716|    Ok(())\n 717|}\n## brain.rs\n 882|    /// supervised-restarting. The `TimedOut` poison stays RESERVED for a genuine\n*883|    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n 884|    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n 885|    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n 886|    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n...\n 1372|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1373|    pub fn net_open_stream(\n 1374|        &mut self,\n 1375|        conn_id: u64,\n 1376|        op: Option<MintedOp>,\n...\n 1401|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1402|    pub fn net_stream_send(\n 1403|        &mut self,\n 1404|        stream_id: u64,\n 1405|        bytes: &[u8],\n## broker.rs\n 3136|                KIND_NET_STREAM_SEND => {\n*3137|                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n 3138|                        send_error(&send, &msg);\n 3139|                    }\n 3140|                }\n...\n 4288|            None => {\n*4289|                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;\n 4290|                send_frame(send, &build(stream_id, req.conn_id, None, true));\n 4291|                Ok(())\n 4292|            }\n...\n 4302|                    .apply_once(key, EffectKind::NetSend, || {\n*4303|                        let stream_id = host.open_stream(req.conn_id)?;\n 4304|                        host.record_stream_op(minter, op, stream_id);\n 4305|                        Ok(())\n 4306|                    })\n...\n 4325|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*4326|    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4327|        let req: NetStreamSendReq = serde_json::from_value(env.payload)\n 4328|            .map_err(|e| format!(\"bad net-stream-send payload: {e}\"))?;\n 4329|        let bytes =\n## config.rs\n 35|/// Default peer-pump cadences (D9-1): how often the daemon pushes its\n*36|/// registry/notif feeds to trusted peers and pulls context syncs from them.\n 37|/// Conservative defaults — every loop is a thin call over an idempotent\n 38|/// driver, so a missed tick costs only latency, never correctness.\n 39|pub const DEFAULT_REGISTRY_PUMP_PERIOD: Duration = Duration::from_secs(30);\n...\n 152|    pub peer_pump: bool,\n*153|    /// Registry feed push cadence (D9-1).\n 154|    pub registry_pump_period: Duration,\n 155|    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10): rows from a\n 156|    /// node not heard within this window are evicted on the next registry\n## dispatch.rs\n 16|//! requester stream would deadlock the requester against ourselves. The flag\n*17|//! is broker-recorded at registration (`open_stream` vs the accept loop),\n 18|//! never inferred.\n 19|//!\n 20|//! ## Classification (first-record demux)\n...\n 36|//! | untagged + `target`+`op_id` | WAN message feed ([`crate::wan::receive_wan`]) |\n*37|//! | untagged + `subnet`+`instance` | registry feed (D9-1-2) |\n 38|//! | anything else | unknown — dropped, loud |\n 39|//!\n 40|//! A family whose production wiring lands in a later slice is still\n...\n 66|use spt_net::net::replicate::{\n*67|    FeedDecoder as RegistryDecoder, NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate,\n 68|};\n 69|use spt_net::net::wanmsg::{WanDecoder, WanReply};\n 70|use spt_store::contextstore::ContextStore;\n...\n 99|    WanMsg,\n*100|    /// A registry replication feed (untagged `RegistryUpdate` lines).\n 101|    Registry,\n 102|    /// A remote-drive attach (`AttachRecord::Request`).\n 103|    Attach,\n...\n 352|/// request and await the reply on the same stream (sync.rs `request_sync` and\n*353|/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n 354|/// opener), so every pull that arrives while the brain is down reads\n 355|/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n 356|/// across exactly the refresh window this milestone fixes.\n...\n 830|        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n*831|        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n 832|        StreamFamily::Attach => {\n 833|            // The gate subject is the session's OWNING endpoint, resolved\n 834|            // from the broker's table (the spawn's label) — never from wire\n...\n 897|                    // Refuse politely: finish our send side, claim stays.\n*898|                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n 899|                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n 900|                }\n 901|            }\n...\n 922|                None => {\n*923|                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n 924|                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n 925|                }\n 926|            }\n...\n 969|\n*970|/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n 971|/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n 972|/// origin trusted in that subnet), until the peer finishes.\n 973|///\n...\n 983|// [impl->REQ-INST-7]\n*984|fn serve_registry_feed(\n 985|    brain: &mut Brain,\n 986|    stream_id: u64,\n 987|    origin: &str,\n...\n 1008|                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n*1009|                let mut updates: Vec<RegistryUpdate> = Vec::new();\n 1010|                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n 1011|                for rec in records {\n 1012|                    match rec {\n*1013|                        RegistryFeedRecord::Instance(u) => updates.push(u),\n*1014|                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n 1015|                    }\n 1016|                }\n 1017|                if !labels.is_empty() {\n...\n 1022|                }\n*1023|                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n 1024|                merged += verdicts.len();\n 1025|                // Attention shift (D9-2): only ids this node actually hosts\n 1026|                // (a perch with a record); everything else is a no-op here.\n...\n 1041|            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n*1042|                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n 1043|            }\n 1044|            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n 1045|            Ok(_) => {}\n...\n 1174|                        };\n*1175|                        let _ = brain.net_stream_send(\n 1176|                            stream_id,\n*1177|                            &reply.encode_line(),\n 1178|                            None,\n 1179|                            true,\n 1180|                        );\n## effect.rs\n 252|/// exactly-once dedup is load-bearing, so they are never wrapped (the retry is\n*253|/// placed at the caller sites, never below `Brain::net_open_stream`).\n 254|///\n 255|/// `mint` yields a fresh same-minter [`MintedOp`] per call and may itself fail\n 256|/// (e.g. `EpochSource::next_epoch` IO) — that error propagates untouched. `run`\n## linkhost.rs\n 478|                    {\n*479|                        brain.net_stream_send(stream_id, &[], None, true)?;\n 480|                        return Ok(ShellLinkServeOutcome::Refused);\n 481|                    }\n 482|                    let (outcome, reply) =\n 483|                        run_action(owlery, &owner, &shell_ref, &action, &args, &op_id);\n*484|                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n 485|                    return Ok(outcome);\n 486|                }\n 487|            }\n 488|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*489|                brain.net_stream_send(stream_id, &[], None, true)?;\n 490|                return Ok(ShellLinkServeOutcome::BadRequest(\n 491|                    \"stream ended before a request\".into(),\n 492|                ));\n...\n 672|) -> io::Result<ShellLinkRequestOutcome> {\n*673|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*674|    let line = ndjson::encode_line(&ShellLinkRecord::Request {\n 675|        owner: owner.to_string(),\n 676|        shell_ref: shell_ref.to_string(),\n 677|        action: action.to_string(),\n...\n 680|    });\n*681|    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 682|\n 683|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 684|    let mut decoder = ShellLinkDecoder::new();\n## nethost.rs\n 59|/// Broker-side QUIC-op deadline (REQ-HAZARD-BROKER-QUIC-DEADLINE), in ms. Each\n*60|/// brain-waiting QUIC await — `dial` / `open_stream` / `send_stream` — is bounded\n 61|/// by this so a black-holed or dead peer fails PROMPTLY with an ORDINARY error\n 62|/// the broker replies, never an unbounded await that wedges the single-threaded\n 63|/// pump (the B-half of REQ-HAZARD-PUMP-IPC-DEADLINE; the 2.2h hfenduleam wedge).\n...\n 306|    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\n*307|    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n 308|    /// there is no accept loop and no closed-watcher (it lives for the host).\n 309|    Loopback,\n 310|}\n...\n 778|    remote_id_hex: String,\n*779|    /// Whether THIS node opened the stream (`open_stream`) vs the peer\n 780|    /// (accept loop). The inbound dispatcher (D9-1) claims only\n 781|    /// peer-initiated streams; without this fact the table is ambiguous —\n 782|    /// it holds locally-opened outbound streams too (gotcha: a dispatcher\n...\n 998|/// `initiated_locally` records the direction fact for the table (true only\n*999|/// from [`NetHost::open_stream`]).\n 1000|fn register_stream(\n 1001|    shared: &Arc<NetShared>,\n 1002|    conn_id: u64,\n...\n 1349|    /// frame, so the brain reconstructs an `ErrorKind::Other` (`Brain::net_dial` /\n*1350|    /// `net_open_stream` / `net_stream_send` → `io::Error::other`) and\n 1351|    /// `pump::peer_outcome` takes the ordinary per-peer arm (drop conn + redial,\n 1352|    /// round continues, heartbeat advances) — DISTINCT from the brain's OWN\n 1353|    /// read-deadline `TimedOut` (the A-half poison → supervised-restart signal).\n...\n 1650|    // [impl->REQ-RC-1]\n*1651|    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\n 1652|        enum Backing {\n 1653|            Quic(Connection),\n 1654|            Loopback,\n...\n 1928|    /// drives and the cross-wired peer row the shell side drives. The loopback\n*1929|    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\n 1930|    /// write to either send appears on the other's read log). Serialized so the\n 1931|    /// before/after diff sees exactly the one peer row this open minted.\n 1932|    // [impl->REQ-SHELL-4]\n...\n 1936|        let before = self.inbound_rows_on(conn_id);\n*1937|        let owner_stream = self.open_stream(conn_id)?;\n 1938|        let shell_stream = self\n 1939|            .inbound_rows_on(conn_id)\n 1940|            .difference(&before)\n...\n 2142|    // mints a REUSED singleton whose origin is this node's OWN id, and\n*2143|    // `open_stream` on it cross-wires a PAIR in-process — an operator row and a\n 2144|    // peer row (the one the dispatcher serves) — with the operator's writes\n 2145|    // crossing to the peer row's read pump. No iroh handshake, no QUIC.\n 2146|    #[test]\n...\n 2162|\n*2163|        // open_stream mints the cross-wired pair: operator row + peer row.\n*2164|        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n 2165|        let infos = host.stream_infos();\n 2166|        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167|        let op = infos\n...\n 2479|        let (conn_id, _) = a.dial(b.addr()).expect(\"dial\");\n*2480|        let sid = a.open_stream(conn_id).expect(\"open stream\");\n 2481|        a.send_stream(sid, b\"{\\\"hello\\\":1}\\n\", false).expect(\"send\");\n 2482|        // B's acceptor registers the peer row (async — poll).\n 2483|        let mut saw = false;\n## notifsync.rs\n 85|    for row in store.list(subnet).map_err(|e| format!(\"notif list: {e}\"))? {\n*86|        wire.extend_from_slice(&NotifRecord::Row { row }.encode_line());\n 87|    }\n 88|    Ok(wire)\n 89|}\n## propagate.rs\n 157|                            };\n*158|                            brain.net_stream_send(stream_id, &status.encode_line(), None, true)?;\n 159|                            return Ok(if trusted {\n 160|                                UpdateServeOutcome::StatusServed\n 161|                            } else {\n...\n 189|                                    }\n*190|                                    .encode_line();\n*191|                                    brain.net_stream_send(stream_id, &line, None, false)?;\n 192|                                    offered = Some(upd_id);\n 193|                                }\n 194|                                None => {\n*195|                                    let line = UpdRecord::UpToDate { upd_id }.encode_line();\n*196|                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 197|                                    return Ok(if trusted {\n 198|                                        UpdateServeOutcome::UpToDate\n 199|                                    } else {\n...\n 214|                                    }\n*215|                                    .encode_line();\n*216|                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 217|                                    return Ok(UpdateServeOutcome::Failed(e.to_string()));\n 218|                                }\n 219|                            };\n...\n 226|                                }\n*227|                                .encode_line();\n*228|                                brain.net_stream_send(stream_id, &line, None, false)?;\n 229|                                offset += chunk.len() as u64;\n 230|                            }\n 231|                            let done = UpdRecord::Done {\n...\n 234|                            }\n*235|                            .encode_line();\n*236|                            brain.net_stream_send(stream_id, &done, None, true)?;\n 237|                            return Ok(UpdateServeOutcome::Served {\n 238|                                bytes: artifact.len() as u64,\n 239|                            });\n...\n 256|                                    }\n*257|                                    .encode_line();\n*258|                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 259|                                    return Ok(UpdateServeOutcome::Failed(e.to_string()));\n 260|                                }\n 261|                            };\n...\n 268|                                }\n*269|                                .encode_line();\n*270|                                brain.net_stream_send(stream_id, &line, None, false)?;\n 271|                                offset += chunk.len() as u64;\n 272|                            }\n 273|                            let done = UpdRecord::Done {\n...\n 276|                            }\n*277|                            .encode_line();\n*278|                            brain.net_stream_send(stream_id, &done, None, true)?;\n 279|                            return Ok(UpdateServeOutcome::Served {\n 280|                                bytes: artifact.len() as u64,\n 281|                            });\n...\n 287|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*288|                brain.net_stream_send(stream_id, &[], None, true)?;\n 289|                return Ok(UpdateServeOutcome::Detached);\n 290|            }\n 291|            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n...\n 353|) -> io::Result<UpdatePullOutcome> {\n*354|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 355|    let stream_id = opened.stream_id;\n 356|    brain.net_stream_subscribe(stream_id, 0)?;\n 357|\n...\n 363|    }\n*364|    .encode_line();\n## reconcile.rs\n 27|/// endpoint's registry rows (every instance, every node). Pure — the daemon\n*28|/// feeds it the live registry; tests feed it rows.\n 29|pub fn is_reconciler(rows: &[Instance], self_node: &str) -> bool {\n 30|    let active: Vec<&Instance> = rows.iter().filter(|i| i.status == Status::Active).collect();\n 31|    if !active.is_empty() {\n## registryhost.rs\n 5|//! lease, the join-time collision check, visibility enforcement) and D4d the\n*6|//! wire record (`RegistryUpdate`), both consumed only by tests. This type is\n 7|//! the daemon-side instance: one in-memory `SubnetRegistry` per member\n 8|//! subnet, fed from two directions —\n 9|//!\n*10|//! - **inbound**: the dispatcher's registry arm applies peer feeds under the\n 11|//!   same fail-closed gate as every replicated store (member subnet ∧ origin\n 12|//!   trusted *in that subnet*, subject = handshake-proven stream origin —\n 13|//!   KNOWN-HAZARDS 7.5);\n...\n 36|};\n*37|use spt_net::net::replicate::{NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate};\n 38|use spt_store::epoch::EpochSource;\n 39|use spt_store::liveness::{is_perch_alive, is_perch_unbound};\n 40|use spt_store::perch::{self, ParentHint};\n...\n 113|    heard: Mutex<BTreeMap<String, u64>>,\n*114|    /// Wall-clock ms of the LAST admitted inbound registry/label feed from\n 115|    /// ANY origin — the \"last admitted registry update\" health field\n 116|    /// (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): real inbound gossip\n 117|    /// progress, as opposed to `heard`'s per-origin recency. `0` = never.\n...\n 217|        origin_node: &str,\n*218|        updates: &[RegistryUpdate],\n 219|        policy: &RegistryGatePolicy,\n 220|    ) -> Vec<RegistryApplyVerdict> {\n 221|        self.apply_feed_flips(origin_node, updates, policy).0\n...\n 243|        origin_node: &str,\n*244|        updates: &[RegistryUpdate],\n 245|        policy: &RegistryGatePolicy,\n 246|    ) -> (Vec<RegistryApplyVerdict>, Vec<String>) {\n 247|        let mut regs = self.regs.lock().unwrap();\n...\n 352|\n*353|    /// Wall-clock ms of the last admitted inbound registry/label feed, if any\n 354|    /// this process lifetime (ADR-0039 Decision 5 status field).\n 355|    // [impl->REQ-PUMP-STAGE-TRUTH]\n 356|    pub fn last_admit_ms(&self) -> Option<u64> {\n...\n 372|    // [impl->REQ-INST-7]\n*373|    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n 374|        let subnets = SubnetStore::load();\n 375|        let mut vis = VisibilityStore::load();\n 376|        // First-join home adoption (D9-5, REQ-INST-15): within one\n...\n 536|                ) {\n*537|                    Ok(Some(_)) => out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n 538|                        subnet: sub.name.clone(),\n 539|                        endpoint_id: id.clone(),\n 540|                        instance,\n...\n 598|                reg.merge_instance(&id, instance.clone());\n*599|                out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n 600|                    subnet: sub.name.clone(),\n 601|                    endpoint_id: id,\n 602|                    instance,\n...\n 621|                reg.merge_node_label(&self.node_hex, label.clone(), ep);\n*622|                out.push(RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\n 623|                    subnet: sub.name.clone(),\n 624|                    node: self.node_hex.clone(),\n 625|                    label: label.clone(),\n...\n 1165|\n*1166|    fn upd(subnet: &str, id: &str, node: &str, epoch: u64) -> RegistryUpdate {\n*1167|        RegistryUpdate {\n 1168|            subnet: subnet.to_string(),\n 1169|            endpoint_id: id.to_string(),\n 1170|            instance: Instance {\n...\n 1319|            \"old-key\",\n*1320|            &[RegistryUpdate {\n 1321|                subnet: \"home\".into(),\n 1322|                endpoint_id: \"ling\".into(),\n 1323|                instance: identified(\"old-key\", \"BOX\", \"mid-1\", 5),\n...\n 1405|            \"old2\",\n*1406|            &[RegistryUpdate {\n 1407|                subnet: \"home\".into(),\n 1408|                endpoint_id: \"ling\".into(),\n 1409|                instance: identified(\"old2\", \"BOX\", \"mid-1\", 5),\n...\n 1570|            epoch: u64,\n*1571|        ) -> RegistryUpdate {\n*1572|            RegistryUpdate {\n 1573|                subnet: subnet.to_string(),\n 1574|                endpoint_id: id.to_string(),\n 1575|                instance: Instance {\n...\n 1912|                    u,\n*1913|                    RegistryFeedRecord::Instance(r)\n 1914|                        if r.endpoint_id == \"ghost-ag\" && r.instance.status == Status::Offline\n 1915|                )),\n 1916|                \"offline heal advertised to peers: {out:?}\"\n...\n 1953|                    u,\n*1954|                    RegistryFeedRecord::Instance(r) if r.endpoint_id == \"vis-ag\" && r.subnet == \"adv\"\n 1955|                )),\n 1956|                \"visible perch advertised: {out:?}\"\n 1957|            );\n...\n 1960|                    u,\n*1961|                    RegistryFeedRecord::Instance(r) if r.endpoint_id == \"hid-ag\"\n 1962|                )),\n 1963|                \"hidden perch is EXCLUDED (ADR-0006 §6), not merely unlisted\"\n 1964|            );\n## resthost.rs\n 100|                    let Some(rest_event) = parse_event(&event) else {\n*101|                        let line = ndjson::encode_line(&RestRecord::Reply {\n 102|                            outcome: \"fail\".into(),\n 103|                            detail: format!(\"unknown rest event {event:?}\"),\n 104|                        });\n*105|                        brain.net_stream_send(stream_id, &line, None, true)?;\n 106|                        return Ok(RestServeOutcome::BadRequest(format!(\n 107|                            \"unknown event {event:?} (op {op_id})\"\n 108|                        )));\n...\n 114|                    {\n*115|                        brain.net_stream_send(stream_id, &[], None, true)?;\n 116|                        return Ok(RestServeOutcome::Refused);\n 117|                    }\n 118|                    // The local transition host: echo gate + wake effects +\n...\n 146|                        };\n*147|                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n 148|                    return Ok(outcome);\n 149|                }\n 150|            }\n...\n 152|                // Finished without a request line: dropped, loud.\n*153|                brain.net_stream_send(stream_id, &[], None, true)?;\n 154|                return Ok(RestServeOutcome::BadRequest(\n 155|                    \"stream ended before a request\".into(),\n 156|                ));\n...\n 191|) -> io::Result<RestRequestOutcome> {\n*192|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*193|    let line = ndjson::encode_line(&RestRecord::Request {\n 194|        endpoint: endpoint.to_string(),\n 195|        event: event_tag.to_string(),\n 196|        op_id: op_id.to_string(),\n...\n 199|    // the serve see a clean single-record stream.\n*200|    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 201|\n 202|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 203|    let mut decoder = RestDecoder::new();\n## serveprobe.rs\n 88|                    let reply = ServeProbeRecord::Reply { serving };\n*89|                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n 90|                    return Ok(ServeProbeServeOutcome::Answered {\n 91|                        subnet: serve_probe,\n 92|                        serving,\n...\n 96|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*97|                brain.net_stream_send(stream_id, &[], None, true)?;\n 98|                return Ok(ServeProbeServeOutcome::BadRequest(\n 99|                    \"stream ended before a request\".into(),\n 100|                ));\n...\n 114|pub fn request_subnet_probe(brain: &mut Brain, conn_id: u64, subnet: &str) -> io::Result<bool> {\n*115|    let opened = brain.net_open_stream(conn_id, None)?;\n*116|    let line = ndjson::encode_line(&ServeProbeRecord::Request {\n 117|        serve_probe: subnet.to_string(),\n 118|    });\n*119|    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 120|\n 121|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 122|    let mut decoder = ServeProbeDecoder::new();\n## sync.rs\n 226|                    }\n*227|                    .encode_line();\n*228|                    brain.net_stream_send(stream_id, &line, None, false)?;\n 229|                }\n 230|\n 231|                // Incremental bundle for the allowed refs (absent branches and\n...\n 245|                        }\n*246|                        .encode_line();\n*247|                        brain.net_stream_send(stream_id, &line, None, true)?;\n 248|                        return Ok(SyncServeOutcome::Failed(e.to_string()));\n 249|                    }\n 250|                };\n...\n 256|                    }\n*257|                    .encode_line();\n*258|                    brain.net_stream_send(stream_id, &line, None, true)?;\n 259|                    return Ok(SyncServeOutcome::UpToDate { denied });\n 260|                }\n 261|\n...\n 279|                        }\n*280|                        .encode_line();\n*281|                        brain.net_stream_send(stream_id, &line, None, false)?;\n 282|                        offset += n as u64;\n 283|                    }\n 284|                    total\n...\n 291|                }\n*292|                .encode_line();\n*293|                brain.net_stream_send(stream_id, &done, None, true)?;\n 294|                return Ok(SyncServeOutcome::Sent {\n 295|                    bytes: bytes_total,\n 296|                    refs: included,\n...\n 300|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*301|                brain.net_stream_send(stream_id, &[], None, true)?;\n 302|                return Ok(SyncServeOutcome::Detached);\n 303|            }\n 304|            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n...\n 342|) -> io::Result<SyncPullReport> {\n*343|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 344|    let stream_id = opened.stream_id;\n 345|    brain.net_stream_subscribe(stream_id, 0)?;\n 346|\n...\n 358|    }\n*359|    .encode_line();\n*360|    brain.net_stream_send(stream_id, &line, None, true)?;\n 361|\n 362|    std::fs::create_dir_all(scratch_dir)?;\n 363|    let bundle: PathBuf = scratch_dir.join(format!(\"recv-{}.bundle\", safe_stem(&sync_id)));\n## tunnelhub.rs\n 8|//! link-break closes the tunnel). It rides the **D4 net-stream substrate**\n*9|//! ([`crate::nethost::NetHost::open_stream`], broker-owned, survives a brain\n 10|//! restart) — a NEW held-stream lifecycle, NOT the one-shot request/reply\n 11|//! [`crate::linkhost::serve_shell_link`].\n 12|//!\n...\n 39|//! **W3 scope = same-node mock.** The opening conn is the [`NetHost`] loopback\n*40|//! singleton (`open_stream` cross-wires the operator + peer rows in-process), which\n 41|//! proves the held-stream lifecycle + byte-exact opaque relay + link-break close.\n 42|//! The cross-node-on-LAN real-Iroh wire is the W5 `[twohost]` rung (doyle gate R2)\n 43|//! — never claimed as proven by this loopback mock.\n## wan.rs\n 198|) -> std::io::Result<WanRequestOutcome> {\n*199|    let opened = brain.net_open_stream(conn_id, None)?;\n 200|    // One-shot: the message is everything we send; finishing our side lets the\n 201|    // receiver's serve see a clean single-record stream and reply.\n*202|    brain.net_stream_send(opened.stream_id, &ndjson::encode_line(msg), None, true)?;\n 203|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 204|    let mut decoder = WanReplyDecoder::new();\n 205|    loop {\n## xfer.rs\n 244|            }\n*245|            .encode_line();\n*246|            brain.net_stream_send(stream_id, &line, None, true)?;\n 247|            Ok(XferServeOutcome::Failed(message))\n 248|        };\n 249|\n...\n 272|                            {\n*273|                                brain.net_stream_send(stream_id, &[], None, true)?;\n 274|                                return Ok(XferServeOutcome::Refused);\n 275|                            }\n 276|                            let src = match resolve_under(root, &path) {\n...\n 303|                                }\n*304|                                .encode_line();\n 305|                                // Fire-and-forget by design (module docs):\n 306|                                // the offset is the dedup, not a send journal.\n*307|                                brain.net_stream_send(stream_id, &line, None, false)?;\n 308|                                offset += n as u64;\n 309|                                let _ = write_progress_at(\n 310|                                    progress_dir,\n...\n 318|                            }\n*319|                            .encode_line();\n*320|                            brain.net_stream_send(stream_id, &done, None, true)?;\n 321|                            let _ = write_progress_at(\n 322|                                progress_dir,\n 323|                                XferRole::Send,\n...\n 338|                            {\n*339|                                brain.net_stream_send(stream_id, &[], None, true)?;\n 340|                                return Ok(XferServeOutcome::Refused);\n 341|                            }\n 342|                            let dest = match resolve_under(root, &path) {\n...\n 394|                            // Echo the commit so the operator observes durability.\n*395|                            let echo = XferRecord::Done { xfer_id, total }.encode_line();\n*396|                            brain.net_stream_send(stream_id, &echo, None, true)?;\n 397|                            return Ok(XferServeOutcome::Received { bytes: total });\n 398|                        }\n 399|                        XferRecord::Err { message, .. } => {\n...\n 406|            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*407|                brain.net_stream_send(stream_id, &[], None, true)?;\n 408|                return Ok(XferServeOutcome::Detached);\n 409|            }\n 410|            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n...\n 434|) -> io::Result<u64> {\n*435|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 436|    let stream_id = opened.stream_id;\n 437|    brain.net_stream_subscribe(stream_id, 0)?;\n 438|    let line = XferRecord::Fetch {\n...\n 442|    }\n*443|    .encode_line();\n*444|    brain.net_stream_send(stream_id, &line, None, false)?;\n 445|\n 446|    if let Some(parent) = dest.parent() {\n 447|        std::fs::create_dir_all(parent)?;\n...\n 544|\n*545|    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 546|    let stream_id = opened.stream_id;\n 547|    brain.net_stream_subscribe(stream_id, 0)?;\n 548|    let push = XferRecord::Push {\n...\n 553|    }\n*554|    .encode_line();\n*555|    brain.net_stream_send(stream_id, &push, None, false)?;\n 556|    let _ = write_progress_at(\n 557|        progress_dir,\n 558|        XferRole::Send,\n...\n 573|        }\n*574|        .encode_line();\n*575|        brain.net_stream_send(stream_id, &line, None, false)?;\n 576|        offset += n as u64;\n 577|        let _ = write_progress_at(\n 578|            progress_dir,\n...\n 586|    }\n*587|    .encode_line();\n\n## pump/\n### mod.rs\n 784|/// advances. A genuine broker-IPC-CARRIER desync — a raw `TimedOut` (the carrier\n*785|/// ops `net_open_stream` / `subscribe` / `send` keep it) — STILL bubbles `Err` to\n 786|/// a SUPERVISED RESTART: a dead BROKER conn restarts the pump; a dead PEER never\n 787|/// does (the decouple W2 delivers).\n 788|// [impl->REQ-PUMP-PEER-ISOLATION]\n...\n 1119|fn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n*1120|    let opened = brain.net_open_stream(conn_id, None)?;\n*1121|    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n 1122|    Ok(())\n 1123|}\n 1124|\n### registry.rs\n 17|\n*18|use spt_net::net::replicate::RegistryFeedRecord;\n 19|use spt_store::subnet::SubnetStore;\n 20|\n 21|use crate::registryhost::RegistryHost;\n...\n 63|/// The registry leg as a [`PumpWorker`]: own-row re-advertisement\n*64|/// ([`RegistryHost::advertise_local`] → one feed stream per peer). The node\n 65|/// re-advertises its own rows; peers reconverge within one cadence\n 66|/// (replay-is-recovery). Own rows only — a relayed third-party row would carry\n 67|/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\n...\n 83|    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\n*84|    adverts: Vec<RegistryFeedRecord>,\n 85|}\n 86|\n 87|impl RegistryWorker {\n...\n 171|            .filter(|u| u.subnet() == subnet)\n*172|            .flat_map(|u| u.encode_line())\n 173|            .collect();\n 174|        if !lines.is_empty() {\n 175|            io.push_feed(&lines)?;"}],"details":{"scopePath":"crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":165,"fileCount":20,"files":["crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/config.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/effect.rs","crates/spt-daemon/src/linkhost.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/notifsync.rs","crates/spt-daemon/src/propagate.rs","crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/registry.rs","crates/spt-daemon/src/reconcile.rs","crates/spt-daemon/src/registryhost.rs","crates/spt-daemon/src/resthost.rs","crates/spt-daemon/src/serveprobe.rs","crates/spt-daemon/src/sync.rs","crates/spt-daemon/src/tunnelhub.rs","crates/spt-daemon/src/wan.rs","crates/spt-daemon/src/xfer.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/attach.rs","count":20},{"path":"crates/spt-daemon/src/brain.rs","count":3},{"path":"crates/spt-daemon/src/broker.rs","count":4},{"path":"crates/spt-daemon/src/config.rs","count":2},{"path":"crates/spt-daemon/src/dispatch.rs","count":17},{"path":"crates/spt-daemon/src/effect.rs","count":1},{"path":"crates/spt-daemon/src/linkhost.rs","count":6},{"path":"crates/spt-daemon/src/nethost.rs","count":12},{"path":"crates/spt-daemon/src/notifsync.rs","count":1},{"path":"crates/spt-daemon/src/propagate.rs","count":20},{"path":"crates/spt-daemon/src/pump/mod.rs","count":3},{"path":"crates/spt-daemon/src/pump/registry.rs","count":4},{"path":"crates/spt-daemon/src/reconcile.rs","count":1},{"path":"crates/spt-daemon/src/registryhost.rs","count":20},{"path":"crates/spt-daemon/src/resthost.rs","count":8},{"path":"crates/spt-daemon/src/serveprobe.rs","count":5},{"path":"crates/spt-daemon/src/sync.rs","count":14},{"path":"crates/spt-daemon/src/tunnelhub.rs","count":2},{"path":"crates/spt-daemon/src/wan.rs","count":2},{"path":"crates/spt-daemon/src/xfer.rs","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/src/\n## attach.rs\n 254│    if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {\n*255│        wire.net_stream_send(stream_id, &[], None, true)?;\n 256│        return Ok(Some(AttachServeOutcome::Refused));\n 257│    }\n 258│    let sid = remote_session.unwrap_or(session_id);\n    │...\n 413│                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n*414│                        let line = ndjson::encode_line(&AttachRecord::Output {\n 415│                            seq: brain.controller_resume_floor(),\n 416│                            data_b64: encode_bytes(notice),\n 417│                        });\n*418│                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n 419│                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 420│                        return Err(e);\n 421│                    }\n    │...\n 538│                        // renders the busy guidance client-side from driven_by.\n*539│                        wire.net_stream_send(stream_id, &[], None, true)?;\n 540│                        return Ok(AttachServeOutcome::Refused);\n 541│                    }\n 542│                }\n    │...\n 545│            BrokerEvent::Size { rows, cols, .. } if attached => {\n*546│                let line = ndjson::encode_line(&AttachRecord::Size { rows, cols });\n*547│                wire.net_stream_send(stream_id, &line, None, false)?;\n 548│            }\n 549│            // This controller was kicked by a different operator's `--take`.\n 550│            BrokerEvent::Displaced { by, .. } if attached => {\n*551│                let line = ndjson::encode_line(&AttachRecord::Displaced { by });\n*552│                wire.net_stream_send(stream_id, &line, None, true)?;\n 553│                // No dormant edge: the endpoint stays driven by the taker (whose\n 554│                // own serve loop already fired the wake).\n 555│                return Ok(AttachServeOutcome::Displaced);\n    │...\n 581│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*582│                wire.net_stream_send(stream_id, &[], None, true)?;\n 583│                // ORDER MATTERS: feed the resting edge FIRST (it read-modify-writes\n 584│                // info.json, carrying the current driven_by through), THEN release\n 585│                // the role over IPC so the broker's driven_by=None is the LAST\n    │...\n 600│            BrokerEvent::Output { seq, bytes, .. } if attached => {\n*601│                let line = ndjson::encode_line(&AttachRecord::Output {\n 602│                    seq,\n 603│                    data_b64: encode_bytes(&bytes),\n 604│                });\n    │...\n 606│                // the record, and the operator's render cursor is the dedup.\n*607│                wire.net_stream_send(stream_id, &line, None, false)?;\n 608│            }\n 609│            BrokerEvent::Exit { code, .. } if attached => {\n*610│                let line = ndjson::encode_line(&AttachRecord::Exit { code });\n*611│                wire.net_stream_send(stream_id, &line, None, true)?;\n 612│                if role == Some(ServeRole::Controller) {\n 613│                    feed_rest(endpoint, crate::resting::RestEvent::Detach);\n 614│                }\n    │...\n 666│) -> io::Result<u64> {\n*667│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*668│    let line = ndjson::encode_line(&AttachRecord::Request {\n 669│        session_id,\n 670│        from_seq,\n 671│        intent,\n    │...\n 678│    });\n*679│    brain.net_stream_send(opened.stream_id, &line, None, false)?;\n 680│    Ok(opened.stream_id)\n 681│}\n 682│\n    │...\n 694│) -> io::Result<()> {\n*695│    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });\n*696│    brain.net_stream_send(stream_id, &line, None, false)?;\n 697│    Ok(())\n 698│}\n 699│\n    │...\n 710│) -> io::Result<()> {\n*711│    let line = ndjson::encode_line(&AttachRecord::Input {\n 712│        data_b64: encode_bytes(bytes),\n 713│        op_id: wire_op,\n 714│    });\n*715│    brain.net_stream_send(stream_id, &line, None, false)?;\n 716│    Ok(())\n 717│}\n## brain.rs\n  882│    /// supervised-restarting. The `TimedOut` poison stays RESERVED for a genuine\n *883│    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n  884│    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n  885│    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n  886│    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n     │...\n 1372│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1373│    pub fn net_open_stream(\n 1374│        &mut self,\n 1375│        conn_id: u64,\n 1376│        op: Option<MintedOp>,\n     │...\n 1401│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*1402│    pub fn net_stream_send(\n 1403│        &mut self,\n 1404│        stream_id: u64,\n 1405│        bytes: &[u8],\n## broker.rs\n 3136│                KIND_NET_STREAM_SEND => {\n*3137│                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n 3138│                        send_error(&send, &msg);\n 3139│                    }\n 3140│                }\n     │...\n 4288│            None => {\n*4289│                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;\n 4290│                send_frame(send, &build(stream_id, req.conn_id, None, true));\n 4291│                Ok(())\n 4292│            }\n     │...\n 4302│                    .apply_once(key, EffectKind::NetSend, || {\n*4303│                        let stream_id = host.open_stream(req.conn_id)?;\n 4304│                        host.record_stream_op(minter, op, stream_id);\n 4305│                        Ok(())\n 4306│                    })\n     │...\n 4325│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*4326│    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4327│        let req: NetStreamSendReq = serde_json::from_value(env.payload)\n 4328│            .map_err(|e| format!(\"bad net-stream-send payload: {e}\"))?;\n 4329│        let bytes =\n## config.rs\n  35│/// Default peer-pump cadences (D9-1): how often the daemon pushes its\n *36│/// registry/notif feeds to trusted peers and pulls context syncs from them.\n  37│/// Conservative defaults — every loop is a thin call over an idempotent\n  38│/// driver, so a missed tick costs only latency, never correctness.\n  39│pub const DEFAULT_REGISTRY_PUMP_PERIOD: Duration = Duration::from_secs(30);\n    │...\n 152│    pub peer_pump: bool,\n*153│    /// Registry feed push cadence (D9-1).\n 154│    pub registry_pump_period: Duration,\n 155│    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10): rows from a\n 156│    /// node not heard within this window are evicted on the next registry\n## dispatch.rs\n   16│//! requester stream would deadlock the requester against ourselves. The flag\n  *17│//! is broker-recorded at registration (`open_stream` vs the accept loop),\n   18│//! never inferred.\n   19│//!\n   20│//! ## Classification (first-record demux)\n     │...\n   36│//! | untagged + `target`+`op_id` | WAN message feed ([`crate::wan::receive_wan`]) |\n  *37│//! | untagged + `subnet`+`instance` | registry feed (D9-1-2) |\n   38│//! | anything else | unknown — dropped, loud |\n   39│//!\n   40│//! A family whose production wiring lands in a later slice is still\n     │...\n   66│use spt_net::net::replicate::{\n  *67│    FeedDecoder as RegistryDecoder, NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate,\n   68│};\n   69│use spt_net::net::wanmsg::{WanDecoder, WanReply};\n   70│use spt_store::contextstore::ContextStore;\n     │...\n   99│    WanMsg,\n *100│    /// A registry replication feed (untagged `RegistryUpdate` lines).\n  101│    Registry,\n  102│    /// A remote-drive attach (`AttachRecord::Request`).\n  103│    Attach,\n     │...\n  352│/// request and await the reply on the same stream (sync.rs `request_sync` and\n *353│/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n  354│/// opener), so every pull that arrives while the brain is down reads\n  355│/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n  356│/// across exactly the refresh window this milestone fixes.\n     │...\n  830│        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n *831│        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n  832│        StreamFamily::Attach => {\n  833│            // The gate subject is the session's OWNING endpoint, resolved\n  834│            // from the broker's table (the spawn's label) — never from wire\n     │...\n  897│                    // Refuse politely: finish our send side, claim stays.\n *898│                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n  899│                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n  900│                }\n  901│            }\n     │...\n  922│                None => {\n *923│                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n  924│                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n  925│                }\n  926│            }\n     │...\n  969│\n *970│/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n  971│/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n  972│/// origin trusted in that subnet), until the peer finishes.\n  973│///\n     │...\n  983│// [impl->REQ-INST-7]\n *984│fn serve_registry_feed(\n  985│    brain: &mut Brain,\n  986│    stream_id: u64,\n  987│    origin: &str,\n     │...\n 1008│                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n*1009│                let mut updates: Vec<RegistryUpdate> = Vec::new();\n 1010│                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n 1011│                for rec in records {\n 1012│                    match rec {\n*1013│                        RegistryFeedRecord::Instance(u) => updates.push(u),\n*1014│                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n 1015│                    }\n 1016│                }\n 1017│                if !labels.is_empty() {\n     │...\n 1022│                }\n*1023│                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n 1024│                merged += verdicts.len();\n 1025│                // Attention shift (D9-2): only ids this node actually hosts\n 1026│                // (a perch with a record); everything else is a no-op here.\n     │...\n 1041│            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n*1042│                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n 1043│            }\n 1044│            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n 1045│            Ok(_) => {}\n     │...\n 1174│                        };\n*1175│                        let _ = brain.net_stream_send(\n 1176│                            stream_id,\n*1177│                            &reply.encode_line(),\n 1178│                            None,\n 1179│                            true,\n 1180│                        );\n## effect.rs\n 252│/// exactly-once dedup is load-bearing, so they are never wrapped (the retry is\n*253│/// placed at the caller sites, never below `Brain::net_open_stream`).\n 254│///\n 255│/// `mint` yields a fresh same-minter [`MintedOp`] per call and may itself fail\n 256│/// (e.g. `EpochSource::next_epoch` IO) — that error propagates untouched. `run`\n## linkhost.rs\n 478│                    {\n*479│                        brain.net_stream_send(stream_id, &[], None, true)?;\n 480│                        return Ok(ShellLinkServeOutcome::Refused);\n 481│                    }\n 482│                    let (outcome, reply) =\n 483│                        run_action(owlery, &owner, &shell_ref, &action, &args, &op_id);\n*484│                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n 485│                    return Ok(outcome);\n 486│                }\n 487│            }\n 488│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*489│                brain.net_stream_send(stream_id, &[], None, true)?;\n 490│                return Ok(ShellLinkServeOutcome::BadRequest(\n 491│                    \"stream ended before a request\".into(),\n 492│                ));\n    │...\n 672│) -> io::Result<ShellLinkRequestOutcome> {\n*673│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*674│    let line = ndjson::encode_line(&ShellLinkRecord::Request {\n 675│        owner: owner.to_string(),\n 676│        shell_ref: shell_ref.to_string(),\n 677│        action: action.to_string(),\n    │...\n 680│    });\n*681│    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 682│\n 683│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 684│    let mut decoder = ShellLinkDecoder::new();\n## nethost.rs\n   59│/// Broker-side QUIC-op deadline (REQ-HAZARD-BROKER-QUIC-DEADLINE), in ms. Each\n  *60│/// brain-waiting QUIC await — `dial` / `open_stream` / `send_stream` — is bounded\n   61│/// by this so a black-holed or dead peer fails PROMPTLY with an ORDINARY error\n   62│/// the broker replies, never an unbounded await that wedges the single-threaded\n   63│/// pump (the B-half of REQ-HAZARD-PUMP-IPC-DEADLINE; the 2.2h hfenduleam wedge).\n     │...\n  306│    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\n *307│    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n  308│    /// there is no accept loop and no closed-watcher (it lives for the host).\n  309│    Loopback,\n  310│}\n     │...\n  778│    remote_id_hex: String,\n *779│    /// Whether THIS node opened the stream (`open_stream`) vs the peer\n  780│    /// (accept loop). The inbound dispatcher (D9-1) claims only\n  781│    /// peer-initiated streams; without this fact the table is ambiguous —\n  782│    /// it holds locally-opened outbound streams too (gotcha: a dispatcher\n     │...\n  998│/// `initiated_locally` records the direction fact for the table (true only\n *999│/// from [`NetHost::open_stream`]).\n 1000│fn register_stream(\n 1001│    shared: &Arc<NetShared>,\n 1002│    conn_id: u64,\n     │...\n 1349│    /// frame, so the brain reconstructs an `ErrorKind::Other` (`Brain::net_dial` /\n*1350│    /// `net_open_stream` / `net_stream_send` → `io::Error::other`) and\n 1351│    /// `pump::peer_outcome` takes the ordinary per-peer arm (drop conn + redial,\n 1352│    /// round continues, heartbeat advances) — DISTINCT from the brain's OWN\n 1353│    /// read-deadline `TimedOut` (the A-half poison → supervised-restart signal).\n     │...\n 1650│    // [impl->REQ-RC-1]\n*1651│    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\n 1652│        enum Backing {\n 1653│            Quic(Connection),\n 1654│            Loopback,\n     │...\n 1928│    /// drives and the cross-wired peer row the shell side drives. The loopback\n*1929│    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\n 1930│    /// write to either send appears on the other's read log). Serialized so the\n 1931│    /// before/after diff sees exactly the one peer row this open minted.\n 1932│    // [impl->REQ-SHELL-4]\n     │...\n 1936│        let before = self.inbound_rows_on(conn_id);\n*1937│        let owner_stream = self.open_stream(conn_id)?;\n 1938│        let shell_stream = self\n 1939│            .inbound_rows_on(conn_id)\n 1940│            .difference(&before)\n     │...\n 2142│    // mints a REUSED singleton whose origin is this node's OWN id, and\n*2143│    // `open_stream` on it cross-wires a PAIR in-process — an operator row and a\n 2144│    // peer row (the one the dispatcher serves) — with the operator's writes\n 2145│    // crossing to the peer row's read pump. No iroh handshake, no QUIC.\n 2146│    #[test]\n     │...\n 2162│\n*2163│        // open_stream mints the cross-wired pair: operator row + peer row.\n*2164│        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n 2165│        let infos = host.stream_infos();\n 2166│        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167│        let op = infos\n     │...\n 2479│        let (conn_id, _) = a.dial(b.addr()).expect(\"dial\");\n*2480│        let sid = a.open_stream(conn_id).expect(\"open stream\");\n 2481│        a.send_stream(sid, b\"{\\\"hello\\\":1}\\n\", false).expect(\"send\");\n 2482│        // B's acceptor registers the peer row (async — poll).\n 2483│        let mut saw = false;\n## notifsync.rs\n 85│    for row in store.list(subnet).map_err(|e| format!(\"notif list: {e}\"))? {\n*86│        wire.extend_from_slice(&NotifRecord::Row { row }.encode_line());\n 87│    }\n 88│    Ok(wire)\n 89│}\n## propagate.rs\n 157│                            };\n*158│                            brain.net_stream_send(stream_id, &status.encode_line(), None, true)?;\n 159│                            return Ok(if trusted {\n 160│                                UpdateServeOutcome::StatusServed\n 161│                            } else {\n    │...\n 189│                                    }\n*190│                                    .encode_line();\n*191│                                    brain.net_stream_send(stream_id, &line, None, false)?;\n 192│                                    offered = Some(upd_id);\n 193│                                }\n 194│                                None => {\n*195│                                    let line = UpdRecord::UpToDate { upd_id }.encode_line();\n*196│                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 197│                                    return Ok(if trusted {\n 198│                                        UpdateServeOutcome::UpToDate\n 199│                                    } else {\n    │...\n 214│                                    }\n*215│                                    .encode_line();\n*216│                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 217│                                    return Ok(UpdateServeOutcome::Failed(e.to_string()));\n 218│                                }\n 219│                            };\n    │...\n 226│                                }\n*227│                                .encode_line();\n*228│                                brain.net_stream_send(stream_id, &line, None, false)?;\n 229│                                offset += chunk.len() as u64;\n 230│                            }\n 231│                            let done = UpdRecord::Done {\n    │...\n 234│                            }\n*235│                            .encode_line();\n*236│                            brain.net_stream_send(stream_id, &done, None, true)?;\n 237│                            return Ok(UpdateServeOutcome::Served {\n 238│                                bytes: artifact.len() as u64,\n 239│                            });\n    │...\n 256│                                    }\n*257│                                    .encode_line();\n*258│                                    brain.net_stream_send(stream_id, &line, None, true)?;\n 259│                                    return Ok(UpdateServeOutcome::Failed(e.to_string()));\n 260│                                }\n 261│                            };\n    │...\n 268│                                }\n*269│                                .encode_line();\n*270│                                brain.net_stream_send(stream_id, &line, None, false)?;\n 271│                                offset += chunk.len() as u64;\n 272│                            }\n 273│                            let done = UpdRecord::Done {\n    │...\n 276│                            }\n*277│                            .encode_line();\n*278│                            brain.net_stream_send(stream_id, &done, None, true)?;\n 279│                            return Ok(UpdateServeOutcome::Served {\n 280│                                bytes: artifact.len() as u64,\n 281│                            });\n    │...\n 287│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*288│                brain.net_stream_send(stream_id, &[], None, true)?;\n 289│                return Ok(UpdateServeOutcome::Detached);\n 290│            }\n 291│            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n    │...\n 353│) -> io::Result<UpdatePullOutcome> {\n*354│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 355│    let stream_id = opened.stream_id;\n 356│    brain.net_stream_subscribe(stream_id, 0)?;\n 357│\n    │...\n 363│    }\n*364│    .encode_line();\n## reconcile.rs\n 27│/// endpoint's registry rows (every instance, every node). Pure — the daemon\n*28│/// feeds it the live registry; tests feed it rows.\n 29│pub fn is_reconciler(rows: &[Instance], self_node: &str) -> bool {\n 30│    let active: Vec<&Instance> = rows.iter().filter(|i| i.status == Status::Active).collect();\n 31│    if !active.is_empty() {\n## registryhost.rs\n    5│//! lease, the join-time collision check, visibility enforcement) and D4d the\n   *6│//! wire record (`RegistryUpdate`), both consumed only by tests. This type is\n    7│//! the daemon-side instance: one in-memory `SubnetRegistry` per member\n    8│//! subnet, fed from two directions —\n    9│//!\n  *10│//! - **inbound**: the dispatcher's registry arm applies peer feeds under the\n   11│//!   same fail-closed gate as every replicated store (member subnet ∧ origin\n   12│//!   trusted *in that subnet*, subject = handshake-proven stream origin —\n   13│//!   KNOWN-HAZARDS 7.5);\n     │...\n   36│};\n  *37│use spt_net::net::replicate::{NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate};\n   38│use spt_store::epoch::EpochSource;\n   39│use spt_store::liveness::{is_perch_alive, is_perch_unbound};\n   40│use spt_store::perch::{self, ParentHint};\n     │...\n  113│    heard: Mutex<BTreeMap<String, u64>>,\n *114│    /// Wall-clock ms of the LAST admitted inbound registry/label feed from\n  115│    /// ANY origin — the \"last admitted registry update\" health field\n  116│    /// (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): real inbound gossip\n  117│    /// progress, as opposed to `heard`'s per-origin recency. `0` = never.\n     │...\n  217│        origin_node: &str,\n *218│        updates: &[RegistryUpdate],\n  219│        policy: &RegistryGatePolicy,\n  220│    ) -> Vec<RegistryApplyVerdict> {\n  221│        self.apply_feed_flips(origin_node, updates, policy).0\n     │...\n  243│        origin_node: &str,\n *244│        updates: &[RegistryUpdate],\n  245│        policy: &RegistryGatePolicy,\n  246│    ) -> (Vec<RegistryApplyVerdict>, Vec<String>) {\n  247│        let mut regs = self.regs.lock().unwrap();\n     │...\n  352│\n *353│    /// Wall-clock ms of the last admitted inbound registry/label feed, if any\n  354│    /// this process lifetime (ADR-0039 Decision 5 status field).\n  355│    // [impl->REQ-PUMP-STAGE-TRUTH]\n  356│    pub fn last_admit_ms(&self) -> Option<u64> {\n     │...\n  372│    // [impl->REQ-INST-7]\n *373│    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n  374│        let subnets = SubnetStore::load();\n  375│        let mut vis = VisibilityStore::load();\n  376│        // First-join home adoption (D9-5, REQ-INST-15): within one\n     │...\n  536│                ) {\n *537│                    Ok(Some(_)) => out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n  538│                        subnet: sub.name.clone(),\n  539│                        endpoint_id: id.clone(),\n  540│                        instance,\n     │...\n  598│                reg.merge_instance(&id, instance.clone());\n *599│                out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n  600│                    subnet: sub.name.clone(),\n  601│                    endpoint_id: id,\n  602│                    instance,\n     │...\n  621│                reg.merge_node_label(&self.node_hex, label.clone(), ep);\n *622│                out.push(RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\n  623│                    subnet: sub.name.clone(),\n  624│                    node: self.node_hex.clone(),\n  625│                    label: label.clone(),\n     │...\n 1165│\n*1166│    fn upd(subnet: &str, id: &str, node: &str, epoch: u64) -> RegistryUpdate {\n*1167│        RegistryUpdate {\n 1168│            subnet: subnet.to_string(),\n 1169│            endpoint_id: id.to_string(),\n 1170│            instance: Instance {\n     │...\n 1319│            \"old-key\",\n*1320│            &[RegistryUpdate {\n 1321│                subnet: \"home\".into(),\n 1322│                endpoint_id: \"ling\".into(),\n 1323│                instance: identified(\"old-key\", \"BOX\", \"mid-1\", 5),\n     │...\n 1405│            \"old2\",\n*1406│            &[RegistryUpdate {\n 1407│                subnet: \"home\".into(),\n 1408│                endpoint_id: \"ling\".into(),\n 1409│                instance: identified(\"old2\", \"BOX\", \"mid-1\", 5),\n     │...\n 1570│            epoch: u64,\n*1571│        ) -> RegistryUpdate {\n*1572│            RegistryUpdate {\n 1573│                subnet: subnet.to_string(),\n 1574│                endpoint_id: id.to_string(),\n 1575│                instance: Instance {\n     │...\n 1912│                    u,\n*1913│                    RegistryFeedRecord::Instance(r)\n 1914│                        if r.endpoint_id == \"ghost-ag\" && r.instance.status == Status::Offline\n 1915│                )),\n 1916│                \"offline heal advertised to peers: {out:?}\"\n     │...\n 1953│                    u,\n*1954│                    RegistryFeedRecord::Instance(r) if r.endpoint_id == \"vis-ag\" && r.subnet == \"adv\"\n 1955│                )),\n 1956│                \"visible perch advertised: {out:?}\"\n 1957│            );\n     │...\n 1960│                    u,\n*1961│                    RegistryFeedRecord::Instance(r) if r.endpoint_id == \"hid-ag\"\n 1962│                )),\n 1963│                \"hidden perch is EXCLUDED (ADR-0006 §6), not merely unlisted\"\n 1964│            );\n## resthost.rs\n 100│                    let Some(rest_event) = parse_event(&event) else {\n*101│                        let line = ndjson::encode_line(&RestRecord::Reply {\n 102│                            outcome: \"fail\".into(),\n 103│                            detail: format!(\"unknown rest event {event:?}\"),\n 104│                        });\n*105│                        brain.net_stream_send(stream_id, &line, None, true)?;\n 106│                        return Ok(RestServeOutcome::BadRequest(format!(\n 107│                            \"unknown event {event:?} (op {op_id})\"\n 108│                        )));\n    │...\n 114│                    {\n*115│                        brain.net_stream_send(stream_id, &[], None, true)?;\n 116│                        return Ok(RestServeOutcome::Refused);\n 117│                    }\n 118│                    // The local transition host: echo gate + wake effects +\n    │...\n 146│                        };\n*147│                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n 148│                    return Ok(outcome);\n 149│                }\n 150│            }\n    │...\n 152│                // Finished without a request line: dropped, loud.\n*153│                brain.net_stream_send(stream_id, &[], None, true)?;\n 154│                return Ok(RestServeOutcome::BadRequest(\n 155│                    \"stream ended before a request\".into(),\n 156│                ));\n    │...\n 191│) -> io::Result<RestRequestOutcome> {\n*192│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n*193│    let line = ndjson::encode_line(&RestRecord::Request {\n 194│        endpoint: endpoint.to_string(),\n 195│        event: event_tag.to_string(),\n 196│        op_id: op_id.to_string(),\n    │...\n 199│    // the serve see a clean single-record stream.\n*200│    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 201│\n 202│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 203│    let mut decoder = RestDecoder::new();\n## serveprobe.rs\n  88│                    let reply = ServeProbeRecord::Reply { serving };\n *89│                    brain.net_stream_send(stream_id, &ndjson::encode_line(&reply), None, true)?;\n  90│                    return Ok(ServeProbeServeOutcome::Answered {\n  91│                        subnet: serve_probe,\n  92│                        serving,\n    │...\n  96│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n *97│                brain.net_stream_send(stream_id, &[], None, true)?;\n  98│                return Ok(ServeProbeServeOutcome::BadRequest(\n  99│                    \"stream ended before a request\".into(),\n 100│                ));\n    │...\n 114│pub fn request_subnet_probe(brain: &mut Brain, conn_id: u64, subnet: &str) -> io::Result<bool> {\n*115│    let opened = brain.net_open_stream(conn_id, None)?;\n*116│    let line = ndjson::encode_line(&ServeProbeRecord::Request {\n 117│        serve_probe: subnet.to_string(),\n 118│    });\n*119│    brain.net_stream_send(opened.stream_id, &line, None, true)?;\n 120│\n 121│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 122│    let mut decoder = ServeProbeDecoder::new();\n## sync.rs\n 226│                    }\n*227│                    .encode_line();\n*228│                    brain.net_stream_send(stream_id, &line, None, false)?;\n 229│                }\n 230│\n 231│                // Incremental bundle for the allowed refs (absent branches and\n    │...\n 245│                        }\n*246│                        .encode_line();\n*247│                        brain.net_stream_send(stream_id, &line, None, true)?;\n 248│                        return Ok(SyncServeOutcome::Failed(e.to_string()));\n 249│                    }\n 250│                };\n    │...\n 256│                    }\n*257│                    .encode_line();\n*258│                    brain.net_stream_send(stream_id, &line, None, true)?;\n 259│                    return Ok(SyncServeOutcome::UpToDate { denied });\n 260│                }\n 261│\n    │...\n 279│                        }\n*280│                        .encode_line();\n*281│                        brain.net_stream_send(stream_id, &line, None, false)?;\n 282│                        offset += n as u64;\n 283│                    }\n 284│                    total\n    │...\n 291│                }\n*292│                .encode_line();\n*293│                brain.net_stream_send(stream_id, &done, None, true)?;\n 294│                return Ok(SyncServeOutcome::Sent {\n 295│                    bytes: bytes_total,\n 296│                    refs: included,\n    │...\n 300│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*301│                brain.net_stream_send(stream_id, &[], None, true)?;\n 302│                return Ok(SyncServeOutcome::Detached);\n 303│            }\n 304│            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n    │...\n 342│) -> io::Result<SyncPullReport> {\n*343│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 344│    let stream_id = opened.stream_id;\n 345│    brain.net_stream_subscribe(stream_id, 0)?;\n 346│\n    │...\n 358│    }\n*359│    .encode_line();\n*360│    brain.net_stream_send(stream_id, &line, None, true)?;\n 361│\n 362│    std::fs::create_dir_all(scratch_dir)?;\n 363│    let bundle: PathBuf = scratch_dir.join(format!(\"recv-{}.bundle\", safe_stem(&sync_id)));\n## tunnelhub.rs\n  8│//! link-break closes the tunnel). It rides the **D4 net-stream substrate**\n *9│//! ([`crate::nethost::NetHost::open_stream`], broker-owned, survives a brain\n 10│//! restart) — a NEW held-stream lifecycle, NOT the one-shot request/reply\n 11│//! [`crate::linkhost::serve_shell_link`].\n 12│//!\n   │...\n 39│//! **W3 scope = same-node mock.** The opening conn is the [`NetHost`] loopback\n*40│//! singleton (`open_stream` cross-wires the operator + peer rows in-process), which\n 41│//! proves the held-stream lifecycle + byte-exact opaque relay + link-break close.\n 42│//! The cross-node-on-LAN real-Iroh wire is the W5 `[twohost]` rung (doyle gate R2)\n 43│//! — never claimed as proven by this loopback mock.\n## wan.rs\n 198│) -> std::io::Result<WanRequestOutcome> {\n*199│    let opened = brain.net_open_stream(conn_id, None)?;\n 200│    // One-shot: the message is everything we send; finishing our side lets the\n 201│    // receiver's serve see a clean single-record stream and reply.\n*202│    brain.net_stream_send(opened.stream_id, &ndjson::encode_line(msg), None, true)?;\n 203│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 204│    let mut decoder = WanReplyDecoder::new();\n 205│    loop {\n## xfer.rs\n 244│            }\n*245│            .encode_line();\n*246│            brain.net_stream_send(stream_id, &line, None, true)?;\n 247│            Ok(XferServeOutcome::Failed(message))\n 248│        };\n 249│\n    │...\n 272│                            {\n*273│                                brain.net_stream_send(stream_id, &[], None, true)?;\n 274│                                return Ok(XferServeOutcome::Refused);\n 275│                            }\n 276│                            let src = match resolve_under(root, &path) {\n    │...\n 303│                                }\n*304│                                .encode_line();\n 305│                                // Fire-and-forget by design (module docs):\n 306│                                // the offset is the dedup, not a send journal.\n*307│                                brain.net_stream_send(stream_id, &line, None, false)?;\n 308│                                offset += n as u64;\n 309│                                let _ = write_progress_at(\n 310│                                    progress_dir,\n    │...\n 318│                            }\n*319│                            .encode_line();\n*320│                            brain.net_stream_send(stream_id, &done, None, true)?;\n 321│                            let _ = write_progress_at(\n 322│                                progress_dir,\n 323│                                XferRole::Send,\n    │...\n 338│                            {\n*339│                                brain.net_stream_send(stream_id, &[], None, true)?;\n 340│                                return Ok(XferServeOutcome::Refused);\n 341│                            }\n 342│                            let dest = match resolve_under(root, &path) {\n    │...\n 394│                            // Echo the commit so the operator observes durability.\n*395│                            let echo = XferRecord::Done { xfer_id, total }.encode_line();\n*396│                            brain.net_stream_send(stream_id, &echo, None, true)?;\n 397│                            return Ok(XferServeOutcome::Received { bytes: total });\n 398│                        }\n 399│                        XferRecord::Err { message, .. } => {\n    │...\n 406│            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n*407│                brain.net_stream_send(stream_id, &[], None, true)?;\n 408│                return Ok(XferServeOutcome::Detached);\n 409│            }\n 410│            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n    │...\n 434│) -> io::Result<u64> {\n*435│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 436│    let stream_id = opened.stream_id;\n 437│    brain.net_stream_subscribe(stream_id, 0)?;\n 438│    let line = XferRecord::Fetch {\n    │...\n 442│    }\n*443│    .encode_line();\n*444│    brain.net_stream_send(stream_id, &line, None, false)?;\n 445│\n 446│    if let Some(parent) = dest.parent() {\n 447│        std::fs::create_dir_all(parent)?;\n    │...\n 544│\n*545│    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n 546│    let stream_id = opened.stream_id;\n 547│    brain.net_stream_subscribe(stream_id, 0)?;\n 548│    let push = XferRecord::Push {\n    │...\n 553│    }\n*554│    .encode_line();\n*555│    brain.net_stream_send(stream_id, &push, None, false)?;\n 556│    let _ = write_progress_at(\n 557│        progress_dir,\n 558│        XferRole::Send,\n    │...\n 573│        }\n*574│        .encode_line();\n*575│        brain.net_stream_send(stream_id, &line, None, false)?;\n 576│        offset += n as u64;\n 577│        let _ = write_progress_at(\n 578│            progress_dir,\n    │...\n 586│    }\n*587│    .encode_line();\n\n## pump/\n### mod.rs\n  784│/// advances. A genuine broker-IPC-CARRIER desync — a raw `TimedOut` (the carrier\n *785│/// ops `net_open_stream` / `subscribe` / `send` keep it) — STILL bubbles `Err` to\n  786│/// a SUPERVISED RESTART: a dead BROKER conn restarts the pump; a dead PEER never\n  787│/// does (the decouple W2 delivers).\n  788│// [impl->REQ-PUMP-PEER-ISOLATION]\n     │...\n 1119│fn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n*1120│    let opened = brain.net_open_stream(conn_id, None)?;\n*1121│    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n 1122│    Ok(())\n 1123│}\n 1124│\n### registry.rs\n  17│\n *18│use spt_net::net::replicate::RegistryFeedRecord;\n  19│use spt_store::subnet::SubnetStore;\n  20│\n  21│use crate::registryhost::RegistryHost;\n    │...\n  63│/// The registry leg as a [`PumpWorker`]: own-row re-advertisement\n *64│/// ([`RegistryHost::advertise_local`] → one feed stream per peer). The node\n  65│/// re-advertises its own rows; peers reconverge within one cadence\n  66│/// (replay-is-recovery). Own rows only — a relayed third-party row would carry\n  67│/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\n    │...\n  83│    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\n *84│    adverts: Vec<RegistryFeedRecord>,\n  85│}\n  86│\n  87│impl RegistryWorker {\n    │...\n 171│            .filter(|u| u.subnet() == subnet)\n*172│            .flat_map(|u| u.encode_line())\n 173│            .collect();\n 174│        if !lines.is_empty() {\n 175│            io.push_feed(&lines)?;"},"isError":false,"timestamp":1784271605151}}
{"type":"message","id":"f5022118","parentId":"9bdc6832","timestamp":"2026-07-17T07:00:05.160Z","message":{"role":"toolResult","toolCallId":"call_NtWFQwH8CT731VcI0LeebQMz|fc_089095e18118ee7d016a59d2f4c5c081908f35abe93932e64a","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## access.rs\n 21|use spt_daemon::nethost::{NetConfig, NetHost};\n*22|use spt_daemon::registryhost::RegistryHost;\n 23|use spt_daemon::wan::{receive_wan, WanOutcome};\n 24|use spt_daemon::Broker;\n 25|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n...\n 111|    owlery: &std::path::Path,\n*112|    registry: &RegistryHost,\n 113|) -> WanOutcome {\n 114|    loop {\n 115|        match brain.read_event().expect(\"read event\") {\n...\n 121|                if let Some(rec) = decoder.push(&bytes).into_iter().next() {\n*122|                    return receive_wan(&rec, origin, owlery, registry);\n 123|                }\n 124|            }\n 125|            BrokerEvent::NetStreamEof { .. } => panic!(\"eof before the record arrived\"),\n...\n 158|    let owlery = perch::owlery_dir();\n*159|    // An empty registry: no origin advertises a Gateway type, so the user-msg\n 160|    // gate fail-closes — these tests assert the access gate, not the type gate.\n*161|    let registry = RegistryHost::new(\"b-access\");\n 162|\n 163|    // Restrict the target BEFORE any traffic: whitelist names some other node,\n 164|    // so sender A is non-listed.\n...\n 196|    assert_eq!(\n*197|        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 198|        WanOutcome::Refused,\n 199|        \"non-listed unsolicited refused\"\n 200|    );\n...\n 214|    assert_eq!(\n*215|        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 216|        WanOutcome::Spooled,\n 217|        \"correlated reply exempt from the whitelist\"\n 218|    );\n...\n 229|    assert_eq!(\n*230|        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 231|        WanOutcome::Spooled,\n 232|        \"whitelisted node admitted\"\n 233|    );\n## daemon_lifecycle_real_brain.rs\n 60|    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*61|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 62|\n 63|    // An ONLINE live endpoint on disk — as a real `api bind/listen` left it (the\n 64|    // establish→online path is proven by live_firsthost/live_bind ints; here the\n## digest.rs\n 62|    std::fs::write(src.join(\"manifest.toml\"), manifest).unwrap();\n*63|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 64|\n 65|    let perch_path = resolve_perch_path(id, ParentHint::Infer);\n 66|    std::fs::create_dir_all(&perch_path).unwrap();\n## dispatch.rs\n 106|    };\n*107|    let registry = Arc::new(spt_daemon::registryhost::RegistryHost::new(\"b-dispatch\"));\n 108|    thread::spawn(move || {\n*109|        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\n 110|    });\n 111|    stop\n 112|}\n...\n 529|// the attach landed on the right session without A ever naming a session id.\n*530|// A named endpoint with NO live session on B (the D6 stale-registry-row case)\n 531|// refuses cleanly: the stream finishes with no output, never a false attach.\n 532|#[test]\n 533|fn dispatcher_resolves_attach_by_endpoint_and_refuses_a_stale_endpoint() {\n...\n 727|    // The endpoint lives in the (shared in-proc) owlery, locally stamped\n*728|    // 1_000; the gossiped registry snapshot says ITS B-node instance was\n 729|    // active at 9_000 — so at A the winner is remote(B), at B it is local\n 730|    // (B skips its own-node row and reads the live perch).\n 731|    let target = \"ling-d6-pres\";\n...\n 739|    info::set_last_active(&perch_path, 1_000).unwrap();\n*740|    let mut reg = spt_net::net::registry::SubnetRegistry::new();\n 741|    reg.merge_instance(\n 742|        target,\n*743|        spt_net::net::registry::Instance {\n 744|            node: b_hex.clone(),\n*745|            status: spt_net::net::registry::Status::Active,\n 746|            epoch: 1,\n 747|            resources: None,\n 748|            last_active_ms: Some(9_000),\n...\n 760|    );\n*761|    let snap_dir = spt_daemon::presence::registry_snapshot_dir();\n 762|    std::fs::create_dir_all(&snap_dir).unwrap();\n 763|    std::fs::write(\n 764|        snap_dir.join(\"d6-pres.json\"),\n...\n 786|    };\n*787|    let regs = spt_daemon::presence::load_registry_snapshots(&snap_dir);\n 788|    let fired = spt_daemon::notif::first_fire_at(\n 789|        &store,\n 790|        &row,\n...\n 890|    .unwrap();\n*891|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 892|    let owlery = perch::owlery_dir();\n 893|    let id = spt_store::shellinfo::spawn_record(&owlery, \"doyle-d8c\", \"D8cShell\", Some(\"Scout\"))\n 894|        .expect(\"mint\");\n...\n 1039|// peer sends a NodeLabelUpdate first (no instance row), so the feed's FIRST\n*1040|// line is `{subnet,node,label,epoch}` — it must classify as the Registry\n 1041|// family (same serve arm + gate) rather than dropping as Unknown. An instance\n*1042|// row still classifies Registry; a bare unrelated object stays Unknown.\n 1043|#[test]\n*1044|fn classify_routes_a_node_label_feed_to_registry() {\n 1045|    use spt_daemon::dispatch::{classify_first_line, StreamFamily};\n*1046|    use spt_net::net::replicate::{NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate};\n*1047|    use spt_net::net::registry::{Instance, Status};\n 1048|\n*1049|    let label = RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\n 1050|        subnet: \"home\".into(),\n 1051|        node: \"n1\".into(),\n 1052|        label: \"HFENDULEAM\".into(),\n...\n 1058|        classify_first_line(&line),\n*1059|        StreamFamily::Registry,\n*1060|        \"an endpoint-less node's label-first feed routes to Registry\"\n 1061|    );\n 1062|\n*1063|    let inst = RegistryFeedRecord::Instance(RegistryUpdate {\n 1064|        subnet: \"home\".into(),\n 1065|        endpoint_id: \"doyle\".into(),\n 1066|        instance: Instance {\n...\n 1085|        serde_json::from_slice(&inst.encode_line()[..inst.encode_line().len() - 1]).unwrap();\n*1086|    assert_eq!(classify_first_line(&iline), StreamFamily::Registry);\n 1087|\n 1088|    let junk: serde_json::Value = serde_json::json!({\"hello\": \"world\"});\n 1089|    assert_eq!(classify_first_line(&junk), StreamFamily::Unknown);\n## endpoint_survival.rs\n 52|    std::fs::write(src.join(\"manifest.toml\"), toml).unwrap();\n*53|    spt_runtime::registry::register(&spt_store::perch::adapters_dir(), &src, 1000)\n 54|        .expect(\"register the resume adapter\");\n 55|}\n 56|\n...\n 120|    let adapters_dir = spt_store::perch::adapters_dir();\n*121|    let registered = spt_runtime::registry::registered(&adapters_dir);\n 122|    spt_daemon::livehost::resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\n 123|\n 124|    // The endpoint's harness was re-run → a live broker session now exists for it.\n## inject_control_wedge.rs\n 61|//    live translation binary — no api poll, no KIND_ENDPOINT_INPUT from a test. ──\n*62|use spt_daemon::registryhost::RegistryHost;\n 63|use spt_daemon::wan::{receive_wan, WanOutcome};\n 64|use spt_net::net::wanmsg::WanMessage;\n 65|\n...\n 93|/// (ack <=3.8s even under pegged CPU; the 30–120s band is pure starvation noise).\n*94|/// Mirrors the `SPT_REGISTRY_BUSY_TIMEOUT_MS` env-knob precedent. The knob grants ack\n 95|/// wall-clock ONLY; it never relaxes the `subscribed` / `got_output` assertions.\n 96|fn attach_gate_watchdog() -> Duration {\n 97|    match std::env::var(\"SPT_ATTACH_GATE_WATCHDOG_MS\") {\n...\n 123|/// bites FIRST, so the watchdog knob alone cannot grant this headroom — two bounds, two\n*124|/// knobs. Mirrors the `SPT_ATTACH_GATE_WATCHDOG_MS` / `SPT_REGISTRY_BUSY_TIMEOUT_MS`\n 125|/// env-knob precedent. Grants IPC-read wall-clock ONLY; never relaxes the `subscribed`\n 126|/// / `got_output` assertions.\n 127|fn attach_ipc_deadline() -> Duration {\n...\n 522|        std::env::set_var(\"SPT_HOME\", dir.path());\n*523|        // F-036 env-inheritance scrub (doyle RCA, REDISPATCH-STALL gate round\n 524|        // 1): a live-agent dev shell exports SPT_INJECT_VERIFY_ECHO, the\n 525|        // in-process test broker inherits it, Layer-2 echo-verify force-\n 526|        // enables host-wide, and the mock PTY children here never re-render\n...\n 2952|    let origin = \"deadbeefcafef00d\"; // the QUIC handshake identity = transport truth\n*2953|    let registry = RegistryHost::new(\"wan-idle-node\");\n 2954|    let owlery = spt_store::perch::owlery_dir();\n 2955|    let wan = WanMessage {\n 2956|        target: endpoint.to_string(),\n...\n 2960|    };\n*2961|    let outcome = receive_wan(&wan, origin, &owlery, &registry);\n 2962|\n 2963|    // BYTE RECEIPT: the binary's `{text:\"XLATE_OK\"}` choreography actually reached\n 2964|    // the PTY — observed via the echo child over the REAL broker ring, XON-unsticking\n## legacy_resident_sweep_e2e.rs\n 34|use spt_daemon::livehost::sweep_legacy_resident_psyches;\n*35|use spt_runtime::registry::AdapterRecord;\n 36|use spt_runtime::Manifest;\n 37|use spt_store::liveness::STATUS_ONLINE;\n 38|use spt_store::perch::{self, ParentHint};\n...\n 143|    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*144|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 145|\n 146|    // Seed both parents ONLINE + their nested ready registrations (the wrapper residue).\n 147|    seed_parent(\"legacyhost\");\n...\n 152|    let registered: Vec<(AdapterRecord, Manifest)> =\n*153|        spt_runtime::registry::registered(&perch::adapters_dir());\n 154|\n 155|    // ── THE BRAIN-START SWEEP (one-shot) ────────────────────────────────────────\n 156|    sweep_legacy_resident_psyches(&perch::owlery_dir(), &registered, &perch::adapters_dir());\n## mesh.rs\n 5|//! directly-paired peers only, so A and C never reached each other); they flip\n*6|//! green at **Mesh-D6**, where the registry push target widens from the\n 7|//! directly-paired set to the full roster (`pump/mod.rs` push =\n 8|//! `roster.members_in`). This is the REQ-MESH-3 `int` evidence.\n 9|//!\n...\n 24|//!\n*25|//! Regression (REQ-MESH-3): rows stay own-authored — every registry row a node\n 26|//! holds is authored by the node that owns that perch (origin == author); B\n 27|//! never relays a third party's row.\n 28|\n...\n 42|};\n*43|use spt_daemon::registryhost::RegistryHost;\n 44|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n 45|use spt_proto::identity::Identity;\n 46|use spt_store::epoch::EpochSource;\n...\n 119|\n*120|/// The inert/registry-only cadence: only registry feeds flow (notif/sync/\n 121|/// update are pushed far enough out to never fire), so the shared canonical\n 122|/// notif/context stores stay untouched across nodes.\n 123|fn cadence() -> PumpCadence {\n 124|    PumpCadence {\n*125|        registry: Duration::from_millis(100),\n 126|        notif: Duration::from_secs(3_000),\n 127|        sync: Duration::from_secs(3_000),\n 128|        update: Duration::from_secs(3_000),\n*129|        registry_evict_after: Duration::from_secs(3_000),\n 130|    }\n 131|}\n 132|\n*133|/// One mesh node: its broker + registry + on-disk pump roots. `perch_id` is\n 134|/// the node's sole Self perch (the row peers must come to see).\n 135|struct MeshNode {\n 136|    name: String,\n...\n 139|    perch_id: String,\n*140|    registry: Arc<RegistryHost>,\n 141|    paths: PumpPaths,\n 142|    dispatch: DispatchPaths,\n 143|}\n...\n 146|    /// Stand up a node under `root`: broker, a single advertised perch, a\n*147|    /// registry, and pump paths whose roster lists exactly `members` (the push\n 148|    /// target — seed the full mesh membership for the converged-roster state).\n 149|    fn new(root: &std::path::Path, perch_id: &str, members: &[&str], dir: &std::path::Path) -> Self {\n 150|        let name = unique_name();\n...\n 194|\n*195|        let registry = Arc::new(RegistryHost::new_at(\n 196|            &hex,\n 197|            EpochSource::load_from(&root.join(\"epoch.json\")),\n*198|            root.join(\"registry\"),\n 199|        ));\n 200|        let dispatch = DispatchPaths {\n 201|            owlery,\n...\n 209|            perch_id: perch_id.to_string(),\n*210|            registry,\n 211|            paths,\n 212|            dispatch,\n 213|        }\n...\n 222|                self.name.clone(),\n*223|                Arc::clone(&self.registry),\n 224|                clone_paths(&self.paths),\n 225|                Arc::clone(&stop),\n 226|            );\n...\n 246|                self.dispatch.clone(),\n*247|                Arc::clone(&self.registry),\n 248|                Arc::clone(&stop),\n 249|            );\n 250|            thread::spawn(move || {\n...\n 256|\n*257|    /// Does this node's registry hold a row for `endpoint_id` authored by\n 258|    /// node `node_hex`? (Visibility — and, with an address for `node_hex`,\n 259|    /// reach.)\n 260|    fn sees(&self, endpoint_id: &str, node_hex: &str) -> bool {\n*261|        self.registry\n 262|            .rows(SUBNET, endpoint_id)\n 263|            .iter()\n 264|            .any(|i| i.node == node_hex)\n...\n 268|/// Establish the permissive canonical gate roster: every member is a roster\n*269|/// member in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` (which\n 270|/// reads the canonical `roster.json`) admits any member feed. The per-node push\n 271|/// target lives in each node's *explicit* `root/roster.json`, not here.\n 272|fn canonical_full_roster(hexes: &[&str]) {\n...\n 420|\n*421|    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C\n 422|    // hold is authored by the node that owns that perch (origin == author).\n 423|    assert_no_relay(&a, &[(\"perchA\", &a.hex), (\"perchB\", &b.hex), (\"perchC\", &c.hex)]);\n 424|    assert_no_relay(&c, &[(\"perchA\", &a.hex), (\"perchB\", &b.hex), (\"perchC\", &c.hex)]);\n...\n 478|\n*479|/// REQ-MESH-3 no-relay regression: every registry row `node` holds for each\n 480|/// named perch is authored by that perch's true owner (origin == author) — a\n 481|/// relayed third-party row (a perchC row authored by B) would fail this.\n 482|fn assert_no_relay(node: &MeshNode, owners: &[(&str, &str)]) {\n 483|    for (perch_id, owner) in owners {\n*484|        for inst in node.registry.rows(SUBNET, perch_id) {\n 485|            assert_eq!(\n 486|                &inst.node, owner,\n 487|                \"{} row in a node must be own-authored ({}), never relayed (got {})\",\n## mesh_recovery.rs\n 22|use spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\n*23|use spt_daemon::registryhost::RegistryHost;\n 24|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n 25|use spt_proto::identity::Identity;\n 26|use spt_store::epoch::EpochSource;\n...\n 110|) {\n*111|    let registry = Arc::new(RegistryHost::new_at(\n 112|        node_hex,\n 113|        EpochSource::load_from(&dir.join(\"dispatch-epoch.json\")),\n*114|        dir.join(\"dispatch-registry\"),\n 115|    ));\n 116|    let name = broker.to_string();\n 117|    let flag = Arc::clone(stop);\n...\n 123|    thread::spawn(move || {\n*124|        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\n 125|    });\n 126|}\n 127|\n...\n 175|    let stop = Arc::new(AtomicBool::new(false));\n*176|    let registry = Arc::new(RegistryHost::new_at(\n 177|        self_hex,\n 178|        EpochSource::load_from(&root.join(\"epoch.json\")),\n*179|        root.join(\"registry\"),\n 180|    ));\n 181|    let name = broker.to_string();\n 182|    let flag = Arc::clone(&stop);\n...\n 185|            cadence: PumpCadence {\n*186|                registry: Duration::from_millis(100),\n 187|                notif: Duration::from_millis(100),\n 188|                sync: Duration::from_millis(100),\n 189|                update: Duration::from_millis(100),\n*190|                registry_evict_after: Duration::from_secs(300),\n 191|            },\n 192|            full_auto_update: false,\n 193|        };\n...\n 195|            &name,\n*196|            registry,\n 197|            &paths,\n 198|            resolver,\n 199|            config,\n## psyche_context_file_e2e.rs\n 144|    std::fs::write(src.join(\"manifest.toml\"), &manifest_toml).unwrap();\n*145|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 146|\n 147|    seed_bound_perch(id, sid);\n 148|\n## psyche_event_turn_e2e.rs\n 29|use spt_daemon::{BrainLifecycle, DaemonConfig};\n*30|use spt_runtime::registry::AdapterRecord;\n 31|use spt_runtime::Manifest;\n 32|use spt_store::liveness::STATUS_ONLINE;\n 33|use spt_store::perch::{self, ParentHint};\n...\n 161|    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*162|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 163|\n 164|    // An online live_agent perch with the adapter set — reconcile hosts it.\n 165|    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);\n...\n 172|    let registered: Vec<(AdapterRecord, Manifest)> =\n*173|        spt_runtime::registry::registered(&perch::adapters_dir());\n 174|    let cfg = DaemonConfig {\n 175|        pulse_period: Duration::from_millis(15),\n 176|        ..DaemonConfig::default()\n## psyche_residency_expectation_e2e.rs\n 51|use spt_daemon::livehost::{reconcile_once, LiveSet};\n*52|use spt_runtime::registry::AdapterRecord;\n 53|use spt_runtime::Manifest;\n 54|use spt_store::liveness::STATUS_ONLINE;\n 55|use spt_store::perch::{self, ParentHint};\n...\n 145|    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*146|    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 147|\n 148|    // An ONLINE live_agent perch WITH a ready marker — the deliverable parent endpoint.\n 149|    // Its session id IS the multi-word ARGV_MARKER: the psyche turn fills\n...\n 162|    let registered: Vec<(AdapterRecord, Manifest)> =\n*163|        spt_runtime::registry::registered(&perch::adapters_dir());\n 164|    let cfg = DaemonConfig {\n 165|        pulse_period: Duration::from_millis(15),\n 166|        ..DaemonConfig::default()\n## pump.rs\n 2|//! runs the production pump loop against \"node B\" running the production\n*3|//! dispatcher — and registry rows, notif rows, and a mind A never held all\n 4|//! converge with NO test code driving a single protocol exchange.\n 5|//!\n 6|//! Topology: B owns the process's canonical SPT_HOME (its dispatcher's gates\n...\n 22|};\n*23|use spt_daemon::registryhost::{RegistryGatePolicy, RegistryHost};\n 24|use spt_daemon::relcache::ReleaseCache;\n 25|use spt_daemon::release::{sha256_hex, ReleaseMetadata, SignedRelease};\n 26|use spt_daemon::update::BROKER_RESOURCE_ABI;\n 27|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n*28|use spt_net::net::registry::{Instance, Status};\n*29|use spt_net::net::replicate::RegistryUpdate;\n 30|use spt_proto::identity::Identity;\n 31|use spt_store::contextstore::ContextStore;\n 32|use spt_store::epoch::EpochSource;\n...\n 145|// [int->REQ-INST-5] [int->REQ-INST-7] [int->REQ-NOTIF-1] the full self-drive:\n*146|// A's pump pushes its registry advertisement and notif spool to B and pulls\n*147|// from B a mind A NEVER HELD (registry-derived bootstrap ref) — all three\n 148|// converge undriven, A's pump against B's dispatcher, both production loops.\n 149|#[test]\n 150|fn pump_and_dispatch_self_drive_the_subnet() {\n...\n 223|\n*224|    let b_registry = Arc::new(RegistryHost::new_at(\n 225|        &b_hex,\n 226|        EpochSource::load_from(&dir.path().join(\"b-epoch.json\")),\n*227|        dir.path().join(\"b-registry\"),\n 228|    ));\n 229|    let stop = Arc::new(AtomicBool::new(false));\n 230|    {\n 231|        let name = b_name.clone();\n*232|        let reg = Arc::clone(&b_registry);\n 233|        let flag = Arc::clone(&stop);\n 234|        let paths = DispatchPaths {\n 235|            owlery: perch::owlery_dir(),\n...\n 317|\n*318|    let a_registry = Arc::new(RegistryHost::new_at(\n 319|        &a_hex,\n 320|        EpochSource::load_from(&a_root.join(\"epoch.json\")),\n*321|        a_root.join(\"registry\"),\n 322|    ));\n 323|    // A learned of B's endpoint via an earlier inbound feed (seeded directly\n 324|    // here — B runs no pump in this test): the bootstrap-ref source.\n...\n 335|        });\n*336|        let pol = RegistryGatePolicy {\n 337|            subnets: vec![subnet.to_string()],\n 338|            roster,\n 339|        };\n*340|        a_registry.apply_feed(\n 341|            &b_hex,\n*342|            &[RegistryUpdate {\n 343|                subnet: subnet.to_string(),\n 344|                endpoint_id: \"ling-pumped\".to_string(),\n 345|                instance: Instance {\n...\n 380|        let name = a_name.clone();\n*381|        let reg = Arc::clone(&a_registry);\n 382|        let flag = Arc::clone(&stop);\n 383|        let paths = PumpPaths {\n 384|            owlery: a_paths.owlery.clone(),\n...\n 401|                cadence: PumpCadence {\n*402|                    registry: Duration::from_millis(100),\n 403|                    notif: Duration::from_millis(100),\n 404|                    sync: Duration::from_millis(100),\n 405|                    update: Duration::from_millis(100),\n...\n 407|                    // tick, so nothing is ever silent for 300s.\n*408|                    registry_evict_after: Duration::from_secs(300),\n 409|                },\n 410|                // Gated default (REQ-UPD-4) — the consent notif is the assert.\n 411|                full_auto_update: false,\n...\n 424|\n*425|    // 1. Registry converged at B: A's perch row arrived through the\n*426|    //    dispatcher's gated registry arm.\n*427|    converge(\"B registry holds pump-ag@A\", || {\n## redispatch.rs\n*1|//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n 2|//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n 3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n 4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n...\n 211|\n*212|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214|// active Attach on the same endpoint/origin; only the dispatcher generation\n 215|// restarts. The historical stream must neither take nor clear the current\n...\n 219|// and the retired row is GONE from the broker's enumeration\n*220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n 222|fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223|    init_home();\n...\n 302|\n*303|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304|// finished-UNRETIRED orderings the Served-report path can never see: the\n 305|// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306|// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n...\n 315|// ever being served (retire-on-sight drains the backlog).\n*316|// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317|#[test]\n 318|fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319|    init_home();\n...\n 413|\n*414|// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n 415|// discriminator (gate round 1 counter-check): a request/reply stream reads\n 416|// `finished=true` the moment its requester FINs with the request — sync's\n 417|// `request_sync` does exactly that — so a finished row must NOT be blanket-\n## redispatch_stall.rs\n*1|//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n*2|//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n 3|//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n 4|//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n 5|//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n...\n 31|//!\n*32|//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n 33|//! binary alongside — the retire/steal regressions are substrate).\n 34|//!\n 35|//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n...\n 122|\n*123|/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124|/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125|/// brain death. Stderr lands in `log`.\n 126|struct Generation {\n...\n 167|\n*168|/// Render the operator's viewport until `needle` appears (the redispatch.rs\n 169|/// helper, verbatim). Returns whether an EOF for the stream arrived.\n 170|fn render_until(\n 171|    operator: &mut Brain,\n...\n 308|\n*309|// [int->REQ-HAZARD-REDISPATCH-STALL]\n 310|// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 311|// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n 312|// production broker subscribe path: a REAL subscriber conn that attaches with\n...\n 684|    // UNFINISHED (and unretired) on a live conn: invisible to the\n*685|    // finished-row discriminator, eligible for redispatch.\n 686|    gen1.kill();\n 687|    {\n 688|        // Sanity: the row is genuinely unfinished (the T6 precondition that\n## replicate.rs\n*1|//! D4d regression — registry replication over the wire: transport → merge.\n 2|//!\n 3|//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play\n 4|//! two daemons. Node A advertises a local endpoint (through the D3d/D3e\n...\n 12|//!   newer one merges `Stale` and the stored row is untouched\n*13|//!   (REQ-HAZARD-REGISTRY-EPOCH-LEASE, now wire-fed);\n 14|//! - the framing survives **real QUIC chunking**: a record split across two\n 15|//!   separate stream sends reassembles on B's decoder.\n 16|\n...\n 27|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n*28|use spt_net::net::registry::{\n*29|    advertise_if_visible, Instance, MergeOutcome, Status, SubnetRegistry,\n 30|};\n*31|use spt_net::net::replicate::{apply_update, LineDecoder, RegistryUpdate};\n 32|use spt_proto::identity::Identity;\n 33|use spt_store::epoch::EpochSource;\n 34|use spt_store::visibility::VisibilityStore;\n...\n 84|\n*85|// [unit->REQ-INST-7] two daemons converge a registry row over the broker-owned\n 86|// wire: advertise gate → EpochSource-authored update → NDJSON over a QUIC\n 87|// stream → merge_instance at the receiver. A record split across two separate\n 88|// sends reassembles (QUIC chunking is not record framing).\n*89|// [unit->REQ-HAZARD-REGISTRY-EPOCH-LEASE] and the lease holds over the wire:\n 90|// a lagging Active replayed after a newer Offline merges Stale — the stored\n 91|// row keeps the newer state (a stale Active can't clobber a newer Offline,\n 92|// now fed by the real transport).\n...\n 109|\n*110|    let mut a_regs: BTreeMap<String, SubnetRegistry> = BTreeMap::new();\n*111|    a_regs.insert(\"home\".into(), SubnetRegistry::new());\n 112|\n 113|    // The advertise gate runs LOCALLY, before anything is emitted: the wire\n 114|    // only ever carries already-admitted state (D3d collision + D3e visibility).\n...\n 144|\n*145|    let update_active = RegistryUpdate {\n 146|        subnet: \"home\".into(),\n 147|        endpoint_id: \"doyle\".into(),\n 148|        instance: active,\n...\n 151|    assert!(e2 > e1, \"EpochSource is strictly monotonic\");\n*152|    let update_offline = RegistryUpdate {\n 153|        subnet: \"home\".into(),\n 154|        endpoint_id: \"doyle\".into(),\n 155|        instance: Instance {\n...\n 209|    // ── Node B: decode the feed and apply through the merge seam. ───────────\n*210|    let mut b_regs: BTreeMap<String, SubnetRegistry> = BTreeMap::new();\n*211|    b_regs.insert(\"home\".into(), SubnetRegistry::new());\n 212|\n 213|    let stream = wait_for_stream(&mut b_probe);\n 214|    b_probe.net_stream_subscribe(stream, 0).expect(\"subscribe\");\n...\n 248|    );\n*249|    // And B's registry equals A's post-advertise state merged with the newer\n 250|    // update — the two nodes agree on the endpoint's instance.\n 251|    let a_row = &a_regs[\"home\"].instances(\"doyle\")[0];\n 252|    assert_eq!(a_row.node, row.node);\n## rosterprop.rs\n 10|//! address for both B and C (the reach precondition Mesh-D6 consumes) — with B\n*11|//! never relaying a registry row. The store here is an in-memory `RosterStore`\n 12|//! per host (the file-backed `production_roster_exchange` is exercised by its own\n 13|//! unit tests); this test proves the *propagation over the wire*.\n 14|\n## sync.rs\n 38|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n*39|use spt_net::net::registry::{Instance, Status};\n 40|use spt_net::net::sync::{SyncDecoder, SyncRecord};\n 41|use spt_proto::identity::Identity;\n 42|use spt_runtime::{Manifest, ManifestRuntime};\n## two_origin_spanning.rs\n 72|    std::fs::write(asrc.join(\"manifest.toml\"), manifest).unwrap();\n*73|    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();\n 74|\n 75|    // Establish the endpoint + its session ledger (boot A, then /clear → B).\n 76|    let perch = resolve_perch_path(\"cc\", ParentHint::Infer);\n...\n 229|    std::fs::write(asrc.join(\"manifest.toml\"), manifest).unwrap();\n*230|    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();\n 231|\n 232|    let perch = resolve_perch_path(\"ccgen\", ParentHint::Infer);\n 233|    std::fs::create_dir_all(&perch).unwrap();\n\n## fixtures/\n### dispatch_fixture.rs\n*1|//! Test-only dispatcher-generation fixture (REDISPATCH-TRUTH D-regressions,\n*2|//! `tests/redispatch.rs`): runs the REAL `dispatch::run_dispatch_loop` against\n 3|//! the named broker, as a child process the test can KILL — a killed\n 4|//! generation drops its workers' broker connections exactly like a production\n 5|//! brain death, which no in-process thread can simulate (a parked serve-loop\n...\n 15|    let broker = std::env::args().nth(1).expect(\"usage: dispatch_fixture <broker_name>\");\n*16|    let registry = Arc::new(spt_daemon::registryhost::RegistryHost::new_at(\n 17|        \"fixturenode\",\n 18|        spt_store::epoch::EpochSource::load_from(&spt_store::perch::epoch_file()),\n*19|        spt_daemon::presence::registry_snapshot_dir(),\n 20|    ));\n 21|    let paths = spt_daemon::dispatch::DispatchPaths {\n 22|        owlery: spt_store::perch::owlery_dir(),\n...\n 29|        &paths,\n*30|        registry,\n 31|        &stop,\n 32|        Duration::from_millis(50),\n 33|    ) {\n\nShowing files 1-20 of 23+. Use skip=20 for the next page, or narrow paths/pattern."}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt-daemon/src/dispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":133,"fileCount":20,"files":["crates/spt-daemon/tests/access.rs","crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs","crates/spt-daemon/tests/digest.rs","crates/spt-daemon/tests/dispatch.rs","crates/spt-daemon/tests/endpoint_survival.rs","crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","crates/spt-daemon/tests/inject_control_wedge.rs","crates/spt-daemon/tests/legacy_resident_sweep_e2e.rs","crates/spt-daemon/tests/mesh.rs","crates/spt-daemon/tests/mesh_recovery.rs","crates/spt-daemon/tests/psyche_context_file_e2e.rs","crates/spt-daemon/tests/psyche_event_turn_e2e.rs","crates/spt-daemon/tests/psyche_residency_expectation_e2e.rs","crates/spt-daemon/tests/pump.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs","crates/spt-daemon/tests/replicate.rs","crates/spt-daemon/tests/rosterprop.rs","crates/spt-daemon/tests/sync.rs","crates/spt-daemon/tests/two_origin_spanning.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/access.rs","count":8},{"path":"crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs","count":1},{"path":"crates/spt-daemon/tests/digest.rs","count":1},{"path":"crates/spt-daemon/tests/dispatch.rs","count":20},{"path":"crates/spt-daemon/tests/endpoint_survival.rs","count":2},{"path":"crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","count":5},{"path":"crates/spt-daemon/tests/inject_control_wedge.rs","count":6},{"path":"crates/spt-daemon/tests/legacy_resident_sweep_e2e.rs","count":3},{"path":"crates/spt-daemon/tests/mesh.rs","count":20},{"path":"crates/spt-daemon/tests/mesh_recovery.rs","count":9},{"path":"crates/spt-daemon/tests/psyche_context_file_e2e.rs","count":1},{"path":"crates/spt-daemon/tests/psyche_event_turn_e2e.rs","count":3},{"path":"crates/spt-daemon/tests/psyche_residency_expectation_e2e.rs","count":3},{"path":"crates/spt-daemon/tests/pump.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch.rs","count":6},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":7},{"path":"crates/spt-daemon/tests/replicate.rs","count":14},{"path":"crates/spt-daemon/tests/rosterprop.rs","count":1},{"path":"crates/spt-daemon/tests/sync.rs","count":1},{"path":"crates/spt-daemon/tests/two_origin_spanning.rs","count":2}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/tests/\n## access.rs\n  21│use spt_daemon::nethost::{NetConfig, NetHost};\n *22│use spt_daemon::registryhost::RegistryHost;\n  23│use spt_daemon::wan::{receive_wan, WanOutcome};\n  24│use spt_daemon::Broker;\n  25│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n    │...\n 111│    owlery: &std::path::Path,\n*112│    registry: &RegistryHost,\n 113│) -> WanOutcome {\n 114│    loop {\n 115│        match brain.read_event().expect(\"read event\") {\n    │...\n 121│                if let Some(rec) = decoder.push(&bytes).into_iter().next() {\n*122│                    return receive_wan(&rec, origin, owlery, registry);\n 123│                }\n 124│            }\n 125│            BrokerEvent::NetStreamEof { .. } => panic!(\"eof before the record arrived\"),\n    │...\n 158│    let owlery = perch::owlery_dir();\n*159│    // An empty registry: no origin advertises a Gateway type, so the user-msg\n 160│    // gate fail-closes — these tests assert the access gate, not the type gate.\n*161│    let registry = RegistryHost::new(\"b-access\");\n 162│\n 163│    // Restrict the target BEFORE any traffic: whitelist names some other node,\n 164│    // so sender A is non-listed.\n    │...\n 196│    assert_eq!(\n*197│        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 198│        WanOutcome::Refused,\n 199│        \"non-listed unsolicited refused\"\n 200│    );\n    │...\n 214│    assert_eq!(\n*215│        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 216│        WanOutcome::Spooled,\n 217│        \"correlated reply exempt from the whitelist\"\n 218│    );\n    │...\n 229│    assert_eq!(\n*230│        funnel_one(&mut receiver, stream, &mut decoder, &origin, &owlery, &registry),\n 231│        WanOutcome::Spooled,\n 232│        \"whitelisted node admitted\"\n 233│    );\n## daemon_lifecycle_real_brain.rs\n 60│    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*61│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 62│\n 63│    // An ONLINE live endpoint on disk — as a real `api bind/listen` left it (the\n 64│    // establish→online path is proven by live_firsthost/live_bind ints; here the\n## digest.rs\n 62│    std::fs::write(src.join(\"manifest.toml\"), manifest).unwrap();\n*63│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 64│\n 65│    let perch_path = resolve_perch_path(id, ParentHint::Infer);\n 66│    std::fs::create_dir_all(&perch_path).unwrap();\n## dispatch.rs\n  106│    };\n *107│    let registry = Arc::new(spt_daemon::registryhost::RegistryHost::new(\"b-dispatch\"));\n  108│    thread::spawn(move || {\n *109│        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\n  110│    });\n  111│    stop\n  112│}\n     │...\n  529│// the attach landed on the right session without A ever naming a session id.\n *530│// A named endpoint with NO live session on B (the D6 stale-registry-row case)\n  531│// refuses cleanly: the stream finishes with no output, never a false attach.\n  532│#[test]\n  533│fn dispatcher_resolves_attach_by_endpoint_and_refuses_a_stale_endpoint() {\n     │...\n  727│    // The endpoint lives in the (shared in-proc) owlery, locally stamped\n *728│    // 1_000; the gossiped registry snapshot says ITS B-node instance was\n  729│    // active at 9_000 — so at A the winner is remote(B), at B it is local\n  730│    // (B skips its own-node row and reads the live perch).\n  731│    let target = \"ling-d6-pres\";\n     │...\n  739│    info::set_last_active(&perch_path, 1_000).unwrap();\n *740│    let mut reg = spt_net::net::registry::SubnetRegistry::new();\n  741│    reg.merge_instance(\n  742│        target,\n *743│        spt_net::net::registry::Instance {\n  744│            node: b_hex.clone(),\n *745│            status: spt_net::net::registry::Status::Active,\n  746│            epoch: 1,\n  747│            resources: None,\n  748│            last_active_ms: Some(9_000),\n     │...\n  760│    );\n *761│    let snap_dir = spt_daemon::presence::registry_snapshot_dir();\n  762│    std::fs::create_dir_all(&snap_dir).unwrap();\n  763│    std::fs::write(\n  764│        snap_dir.join(\"d6-pres.json\"),\n     │...\n  786│    };\n *787│    let regs = spt_daemon::presence::load_registry_snapshots(&snap_dir);\n  788│    let fired = spt_daemon::notif::first_fire_at(\n  789│        &store,\n  790│        &row,\n     │...\n  890│    .unwrap();\n *891│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n  892│    let owlery = perch::owlery_dir();\n  893│    let id = spt_store::shellinfo::spawn_record(&owlery, \"doyle-d8c\", \"D8cShell\", Some(\"Scout\"))\n  894│        .expect(\"mint\");\n     │...\n 1039│// peer sends a NodeLabelUpdate first (no instance row), so the feed's FIRST\n*1040│// line is `{subnet,node,label,epoch}` — it must classify as the Registry\n 1041│// family (same serve arm + gate) rather than dropping as Unknown. An instance\n*1042│// row still classifies Registry; a bare unrelated object stays Unknown.\n 1043│#[test]\n*1044│fn classify_routes_a_node_label_feed_to_registry() {\n 1045│    use spt_daemon::dispatch::{classify_first_line, StreamFamily};\n*1046│    use spt_net::net::replicate::{NodeLabelUpdate, RegistryFeedRecord, RegistryUpdate};\n*1047│    use spt_net::net::registry::{Instance, Status};\n 1048│\n*1049│    let label = RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\n 1050│        subnet: \"home\".into(),\n 1051│        node: \"n1\".into(),\n 1052│        label: \"HFENDULEAM\".into(),\n     │...\n 1058│        classify_first_line(&line),\n*1059│        StreamFamily::Registry,\n*1060│        \"an endpoint-less node's label-first feed routes to Registry\"\n 1061│    );\n 1062│\n*1063│    let inst = RegistryFeedRecord::Instance(RegistryUpdate {\n 1064│        subnet: \"home\".into(),\n 1065│        endpoint_id: \"doyle\".into(),\n 1066│        instance: Instance {\n     │...\n 1085│        serde_json::from_slice(&inst.encode_line()[..inst.encode_line().len() - 1]).unwrap();\n*1086│    assert_eq!(classify_first_line(&iline), StreamFamily::Registry);\n 1087│\n 1088│    let junk: serde_json::Value = serde_json::json!({\"hello\": \"world\"});\n 1089│    assert_eq!(classify_first_line(&junk), StreamFamily::Unknown);\n## endpoint_survival.rs\n  52│    std::fs::write(src.join(\"manifest.toml\"), toml).unwrap();\n *53│    spt_runtime::registry::register(&spt_store::perch::adapters_dir(), &src, 1000)\n  54│        .expect(\"register the resume adapter\");\n  55│}\n  56│\n    │...\n 120│    let adapters_dir = spt_store::perch::adapters_dir();\n*121│    let registered = spt_runtime::registry::registered(&adapters_dir);\n 122│    spt_daemon::livehost::resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\n 123│\n 124│    // The endpoint's harness was re-run → a live broker session now exists for it.\n## inject_control_wedge.rs\n   61│//    live translation binary — no api poll, no KIND_ENDPOINT_INPUT from a test. ──\n  *62│use spt_daemon::registryhost::RegistryHost;\n   63│use spt_daemon::wan::{receive_wan, WanOutcome};\n   64│use spt_net::net::wanmsg::WanMessage;\n   65│\n     │...\n   93│/// (ack <=3.8s even under pegged CPU; the 30–120s band is pure starvation noise).\n  *94│/// Mirrors the `SPT_REGISTRY_BUSY_TIMEOUT_MS` env-knob precedent. The knob grants ack\n   95│/// wall-clock ONLY; it never relaxes the `subscribed` / `got_output` assertions.\n   96│fn attach_gate_watchdog() -> Duration {\n   97│    match std::env::var(\"SPT_ATTACH_GATE_WATCHDOG_MS\") {\n     │...\n  123│/// bites FIRST, so the watchdog knob alone cannot grant this headroom — two bounds, two\n *124│/// knobs. Mirrors the `SPT_ATTACH_GATE_WATCHDOG_MS` / `SPT_REGISTRY_BUSY_TIMEOUT_MS`\n  125│/// env-knob precedent. Grants IPC-read wall-clock ONLY; never relaxes the `subscribed`\n  126│/// / `got_output` assertions.\n  127│fn attach_ipc_deadline() -> Duration {\n     │...\n  522│        std::env::set_var(\"SPT_HOME\", dir.path());\n *523│        // F-036 env-inheritance scrub (doyle RCA, REDISPATCH-STALL gate round\n  524│        // 1): a live-agent dev shell exports SPT_INJECT_VERIFY_ECHO, the\n  525│        // in-process test broker inherits it, Layer-2 echo-verify force-\n  526│        // enables host-wide, and the mock PTY children here never re-render\n     │...\n 2952│    let origin = \"deadbeefcafef00d\"; // the QUIC handshake identity = transport truth\n*2953│    let registry = RegistryHost::new(\"wan-idle-node\");\n 2954│    let owlery = spt_store::perch::owlery_dir();\n 2955│    let wan = WanMessage {\n 2956│        target: endpoint.to_string(),\n     │...\n 2960│    };\n*2961│    let outcome = receive_wan(&wan, origin, &owlery, &registry);\n 2962│\n 2963│    // BYTE RECEIPT: the binary's `{text:\"XLATE_OK\"}` choreography actually reached\n 2964│    // the PTY — observed via the echo child over the REAL broker ring, XON-unsticking\n## legacy_resident_sweep_e2e.rs\n  34│use spt_daemon::livehost::sweep_legacy_resident_psyches;\n *35│use spt_runtime::registry::AdapterRecord;\n  36│use spt_runtime::Manifest;\n  37│use spt_store::liveness::STATUS_ONLINE;\n  38│use spt_store::perch::{self, ParentHint};\n    │...\n 143│    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*144│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 145│\n 146│    // Seed both parents ONLINE + their nested ready registrations (the wrapper residue).\n 147│    seed_parent(\"legacyhost\");\n    │...\n 152│    let registered: Vec<(AdapterRecord, Manifest)> =\n*153│        spt_runtime::registry::registered(&perch::adapters_dir());\n 154│\n 155│    // ── THE BRAIN-START SWEEP (one-shot) ────────────────────────────────────────\n 156│    sweep_legacy_resident_psyches(&perch::owlery_dir(), &registered, &perch::adapters_dir());\n## mesh.rs\n   5│//! directly-paired peers only, so A and C never reached each other); they flip\n  *6│//! green at **Mesh-D6**, where the registry push target widens from the\n   7│//! directly-paired set to the full roster (`pump/mod.rs` push =\n   8│//! `roster.members_in`). This is the REQ-MESH-3 `int` evidence.\n   9│//!\n    │...\n  24│//!\n *25│//! Regression (REQ-MESH-3): rows stay own-authored — every registry row a node\n  26│//! holds is authored by the node that owns that perch (origin == author); B\n  27│//! never relays a third party's row.\n  28│\n    │...\n  42│};\n *43│use spt_daemon::registryhost::RegistryHost;\n  44│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n  45│use spt_proto::identity::Identity;\n  46│use spt_store::epoch::EpochSource;\n    │...\n 119│\n*120│/// The inert/registry-only cadence: only registry feeds flow (notif/sync/\n 121│/// update are pushed far enough out to never fire), so the shared canonical\n 122│/// notif/context stores stay untouched across nodes.\n 123│fn cadence() -> PumpCadence {\n 124│    PumpCadence {\n*125│        registry: Duration::from_millis(100),\n 126│        notif: Duration::from_secs(3_000),\n 127│        sync: Duration::from_secs(3_000),\n 128│        update: Duration::from_secs(3_000),\n*129│        registry_evict_after: Duration::from_secs(3_000),\n 130│    }\n 131│}\n 132│\n*133│/// One mesh node: its broker + registry + on-disk pump roots. `perch_id` is\n 134│/// the node's sole Self perch (the row peers must come to see).\n 135│struct MeshNode {\n 136│    name: String,\n    │...\n 139│    perch_id: String,\n*140│    registry: Arc<RegistryHost>,\n 141│    paths: PumpPaths,\n 142│    dispatch: DispatchPaths,\n 143│}\n    │...\n 146│    /// Stand up a node under `root`: broker, a single advertised perch, a\n*147│    /// registry, and pump paths whose roster lists exactly `members` (the push\n 148│    /// target — seed the full mesh membership for the converged-roster state).\n 149│    fn new(root: &std::path::Path, perch_id: &str, members: &[&str], dir: &std::path::Path) -> Self {\n 150│        let name = unique_name();\n    │...\n 194│\n*195│        let registry = Arc::new(RegistryHost::new_at(\n 196│            &hex,\n 197│            EpochSource::load_from(&root.join(\"epoch.json\")),\n*198│            root.join(\"registry\"),\n 199│        ));\n 200│        let dispatch = DispatchPaths {\n 201│            owlery,\n    │...\n 209│            perch_id: perch_id.to_string(),\n*210│            registry,\n 211│            paths,\n 212│            dispatch,\n 213│        }\n    │...\n 222│                self.name.clone(),\n*223│                Arc::clone(&self.registry),\n 224│                clone_paths(&self.paths),\n 225│                Arc::clone(&stop),\n 226│            );\n    │...\n 246│                self.dispatch.clone(),\n*247│                Arc::clone(&self.registry),\n 248│                Arc::clone(&stop),\n 249│            );\n 250│            thread::spawn(move || {\n    │...\n 256│\n*257│    /// Does this node's registry hold a row for `endpoint_id` authored by\n 258│    /// node `node_hex`? (Visibility — and, with an address for `node_hex`,\n 259│    /// reach.)\n 260│    fn sees(&self, endpoint_id: &str, node_hex: &str) -> bool {\n*261│        self.registry\n 262│            .rows(SUBNET, endpoint_id)\n 263│            .iter()\n 264│            .any(|i| i.node == node_hex)\n    │...\n 268│/// Establish the permissive canonical gate roster: every member is a roster\n*269│/// member in `SUBNET`, so the dispatch-side `RegistryGatePolicy::load()` (which\n 270│/// reads the canonical `roster.json`) admits any member feed. The per-node push\n 271│/// target lives in each node's *explicit* `root/roster.json`, not here.\n 272│fn canonical_full_roster(hexes: &[&str]) {\n    │...\n 420│\n*421│    // Regression (REQ-MESH-3): no row was relayed — every registry row A and C\n 422│    // hold is authored by the node that owns that perch (origin == author).\n 423│    assert_no_relay(&a, &[(\"perchA\", &a.hex), (\"perchB\", &b.hex), (\"perchC\", &c.hex)]);\n 424│    assert_no_relay(&c, &[(\"perchA\", &a.hex), (\"perchB\", &b.hex), (\"perchC\", &c.hex)]);\n    │...\n 478│\n*479│/// REQ-MESH-3 no-relay regression: every registry row `node` holds for each\n 480│/// named perch is authored by that perch's true owner (origin == author) — a\n 481│/// relayed third-party row (a perchC row authored by B) would fail this.\n 482│fn assert_no_relay(node: &MeshNode, owners: &[(&str, &str)]) {\n 483│    for (perch_id, owner) in owners {\n*484│        for inst in node.registry.rows(SUBNET, perch_id) {\n 485│            assert_eq!(\n 486│                &inst.node, owner,\n 487│                \"{} row in a node must be own-authored ({}), never relayed (got {})\",\n## mesh_recovery.rs\n  22│use spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\n *23│use spt_daemon::registryhost::RegistryHost;\n  24│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n  25│use spt_proto::identity::Identity;\n  26│use spt_store::epoch::EpochSource;\n    │...\n 110│) {\n*111│    let registry = Arc::new(RegistryHost::new_at(\n 112│        node_hex,\n 113│        EpochSource::load_from(&dir.join(\"dispatch-epoch.json\")),\n*114│        dir.join(\"dispatch-registry\"),\n 115│    ));\n 116│    let name = broker.to_string();\n 117│    let flag = Arc::clone(stop);\n    │...\n 123│    thread::spawn(move || {\n*124│        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\n 125│    });\n 126│}\n 127│\n    │...\n 175│    let stop = Arc::new(AtomicBool::new(false));\n*176│    let registry = Arc::new(RegistryHost::new_at(\n 177│        self_hex,\n 178│        EpochSource::load_from(&root.join(\"epoch.json\")),\n*179│        root.join(\"registry\"),\n 180│    ));\n 181│    let name = broker.to_string();\n 182│    let flag = Arc::clone(&stop);\n    │...\n 185│            cadence: PumpCadence {\n*186│                registry: Duration::from_millis(100),\n 187│                notif: Duration::from_millis(100),\n 188│                sync: Duration::from_millis(100),\n 189│                update: Duration::from_millis(100),\n*190│                registry_evict_after: Duration::from_secs(300),\n 191│            },\n 192│            full_auto_update: false,\n 193│        };\n    │...\n 195│            &name,\n*196│            registry,\n 197│            &paths,\n 198│            resolver,\n 199│            config,\n## psyche_context_file_e2e.rs\n 144│    std::fs::write(src.join(\"manifest.toml\"), &manifest_toml).unwrap();\n*145│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 146│\n 147│    seed_bound_perch(id, sid);\n 148│\n## psyche_event_turn_e2e.rs\n  29│use spt_daemon::{BrainLifecycle, DaemonConfig};\n *30│use spt_runtime::registry::AdapterRecord;\n  31│use spt_runtime::Manifest;\n  32│use spt_store::liveness::STATUS_ONLINE;\n  33│use spt_store::perch::{self, ParentHint};\n    │...\n 161│    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*162│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 163│\n 164│    // An online live_agent perch with the adapter set — reconcile hosts it.\n 165│    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);\n    │...\n 172│    let registered: Vec<(AdapterRecord, Manifest)> =\n*173│        spt_runtime::registry::registered(&perch::adapters_dir());\n 174│    let cfg = DaemonConfig {\n 175│        pulse_period: Duration::from_millis(15),\n 176│        ..DaemonConfig::default()\n## psyche_residency_expectation_e2e.rs\n  51│use spt_daemon::livehost::{reconcile_once, LiveSet};\n *52│use spt_runtime::registry::AdapterRecord;\n  53│use spt_runtime::Manifest;\n  54│use spt_store::liveness::STATUS_ONLINE;\n  55│use spt_store::perch::{self, ParentHint};\n    │...\n 145│    std::fs::write(src.join(\"manifest.toml\"), &manifest).unwrap();\n*146│    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();\n 147│\n 148│    // An ONLINE live_agent perch WITH a ready marker — the deliverable parent endpoint.\n 149│    // Its session id IS the multi-word ARGV_MARKER: the psyche turn fills\n    │...\n 162│    let registered: Vec<(AdapterRecord, Manifest)> =\n*163│        spt_runtime::registry::registered(&perch::adapters_dir());\n 164│    let cfg = DaemonConfig {\n 165│        pulse_period: Duration::from_millis(15),\n 166│        ..DaemonConfig::default()\n## pump.rs\n   2│//! runs the production pump loop against \"node B\" running the production\n  *3│//! dispatcher — and registry rows, notif rows, and a mind A never held all\n   4│//! converge with NO test code driving a single protocol exchange.\n   5│//!\n   6│//! Topology: B owns the process's canonical SPT_HOME (its dispatcher's gates\n    │...\n  22│};\n *23│use spt_daemon::registryhost::{RegistryGatePolicy, RegistryHost};\n  24│use spt_daemon::relcache::ReleaseCache;\n  25│use spt_daemon::release::{sha256_hex, ReleaseMetadata, SignedRelease};\n  26│use spt_daemon::update::BROKER_RESOURCE_ABI;\n  27│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n *28│use spt_net::net::registry::{Instance, Status};\n *29│use spt_net::net::replicate::RegistryUpdate;\n  30│use spt_proto::identity::Identity;\n  31│use spt_store::contextstore::ContextStore;\n  32│use spt_store::epoch::EpochSource;\n    │...\n 145│// [int->REQ-INST-5] [int->REQ-INST-7] [int->REQ-NOTIF-1] the full self-drive:\n*146│// A's pump pushes its registry advertisement and notif spool to B and pulls\n*147│// from B a mind A NEVER HELD (registry-derived bootstrap ref) — all three\n 148│// converge undriven, A's pump against B's dispatcher, both production loops.\n 149│#[test]\n 150│fn pump_and_dispatch_self_drive_the_subnet() {\n    │...\n 223│\n*224│    let b_registry = Arc::new(RegistryHost::new_at(\n 225│        &b_hex,\n 226│        EpochSource::load_from(&dir.path().join(\"b-epoch.json\")),\n*227│        dir.path().join(\"b-registry\"),\n 228│    ));\n 229│    let stop = Arc::new(AtomicBool::new(false));\n 230│    {\n 231│        let name = b_name.clone();\n*232│        let reg = Arc::clone(&b_registry);\n 233│        let flag = Arc::clone(&stop);\n 234│        let paths = DispatchPaths {\n 235│            owlery: perch::owlery_dir(),\n    │...\n 317│\n*318│    let a_registry = Arc::new(RegistryHost::new_at(\n 319│        &a_hex,\n 320│        EpochSource::load_from(&a_root.join(\"epoch.json\")),\n*321│        a_root.join(\"registry\"),\n 322│    ));\n 323│    // A learned of B's endpoint via an earlier inbound feed (seeded directly\n 324│    // here — B runs no pump in this test): the bootstrap-ref source.\n    │...\n 335│        });\n*336│        let pol = RegistryGatePolicy {\n 337│            subnets: vec![subnet.to_string()],\n 338│            roster,\n 339│        };\n*340│        a_registry.apply_feed(\n 341│            &b_hex,\n*342│            &[RegistryUpdate {\n 343│                subnet: subnet.to_string(),\n 344│                endpoint_id: \"ling-pumped\".to_string(),\n 345│                instance: Instance {\n    │...\n 380│        let name = a_name.clone();\n*381│        let reg = Arc::clone(&a_registry);\n 382│        let flag = Arc::clone(&stop);\n 383│        let paths = PumpPaths {\n 384│            owlery: a_paths.owlery.clone(),\n    │...\n 401│                cadence: PumpCadence {\n*402│                    registry: Duration::from_millis(100),\n 403│                    notif: Duration::from_millis(100),\n 404│                    sync: Duration::from_millis(100),\n 405│                    update: Duration::from_millis(100),\n    │...\n 407│                    // tick, so nothing is ever silent for 300s.\n*408│                    registry_evict_after: Duration::from_secs(300),\n 409│                },\n 410│                // Gated default (REQ-UPD-4) — the consent notif is the assert.\n 411│                full_auto_update: false,\n    │...\n 424│\n*425│    // 1. Registry converged at B: A's perch row arrived through the\n*426│    //    dispatcher's gated registry arm.\n*427│    converge(\"B registry holds pump-ag@A\", || {\n## redispatch.rs\n  *1│//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n   2│//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n   3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n   4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n    │...\n 211│\n*212│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214│// active Attach on the same endpoint/origin; only the dispatcher generation\n 215│// restarts. The historical stream must neither take nor clear the current\n    │...\n 219│// and the retired row is GONE from the broker's enumeration\n*220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n 222│fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223│    init_home();\n    │...\n 302│\n*303│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304│// finished-UNRETIRED orderings the Served-report path can never see: the\n 305│// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306│// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n    │...\n 315│// ever being served (retire-on-sight drains the backlog).\n*316│// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317│#[test]\n 318│fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319│    init_home();\n    │...\n 413│\n*414│// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n 415│// discriminator (gate round 1 counter-check): a request/reply stream reads\n 416│// `finished=true` the moment its requester FINs with the request — sync's\n 417│// `request_sync` does exactly that — so a finished row must NOT be blanket-\n## redispatch_stall.rs\n  *1│//! REDISPATCH-STALL W1 — the seven-leg production-path matrix (ADR-0038\n  *2│//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34\n   3│//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim\n   4│//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek\n   5│//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n    │...\n  31│//!\n *32│//! Binding: redispatch D1/D1b stay green every leg (run the `redispatch`\n  33│//! binary alongside — the retire/steal regressions are substrate).\n  34│//!\n  35│//! HEAVY AT BIRTH (FLAKE-LEDGER #15): every test spins real hermetic QUIC\n    │...\n 122│\n*123│/// One REAL dispatcher generation (the redispatch.rs harness): the\n 124│/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =\n 125│/// brain death. Stderr lands in `log`.\n 126│struct Generation {\n    │...\n 167│\n*168│/// Render the operator's viewport until `needle` appears (the redispatch.rs\n 169│/// helper, verbatim). Returns whether an EOF for the stream arrived.\n 170│fn render_until(\n 171│    operator: &mut Brain,\n    │...\n 308│\n*309│// [int->REQ-HAZARD-REDISPATCH-STALL]\n 310│// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n 311│// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the\n 312│// production broker subscribe path: a REAL subscriber conn that attaches with\n    │...\n 684│    // UNFINISHED (and unretired) on a live conn: invisible to the\n*685│    // finished-row discriminator, eligible for redispatch.\n 686│    gen1.kill();\n 687│    {\n 688│        // Sanity: the row is genuinely unfinished (the T6 precondition that\n## replicate.rs\n  *1│//! D4d regression — registry replication over the wire: transport → merge.\n   2│//!\n   3│//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play\n   4│//! two daemons. Node A advertises a local endpoint (through the D3d/D3e\n    │...\n  12│//!   newer one merges `Stale` and the stored row is untouched\n *13│//!   (REQ-HAZARD-REGISTRY-EPOCH-LEASE, now wire-fed);\n  14│//! - the framing survives **real QUIC chunking**: a record split across two\n  15│//!   separate stream sends reassembles on B's decoder.\n  16│\n    │...\n  27│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n *28│use spt_net::net::registry::{\n *29│    advertise_if_visible, Instance, MergeOutcome, Status, SubnetRegistry,\n  30│};\n *31│use spt_net::net::replicate::{apply_update, LineDecoder, RegistryUpdate};\n  32│use spt_proto::identity::Identity;\n  33│use spt_store::epoch::EpochSource;\n  34│use spt_store::visibility::VisibilityStore;\n    │...\n  84│\n *85│// [unit->REQ-INST-7] two daemons converge a registry row over the broker-owned\n  86│// wire: advertise gate → EpochSource-authored update → NDJSON over a QUIC\n  87│// stream → merge_instance at the receiver. A record split across two separate\n  88│// sends reassembles (QUIC chunking is not record framing).\n *89│// [unit->REQ-HAZARD-REGISTRY-EPOCH-LEASE] and the lease holds over the wire:\n  90│// a lagging Active replayed after a newer Offline merges Stale — the stored\n  91│// row keeps the newer state (a stale Active can't clobber a newer Offline,\n  92│// now fed by the real transport).\n    │...\n 109│\n*110│    let mut a_regs: BTreeMap<String, SubnetRegistry> = BTreeMap::new();\n*111│    a_regs.insert(\"home\".into(), SubnetRegistry::new());\n 112│\n 113│    // The advertise gate runs LOCALLY, before anything is emitted: the wire\n 114│    // only ever carries already-admitted state (D3d collision + D3e visibility).\n    │...\n 144│\n*145│    let update_active = RegistryUpdate {\n 146│        subnet: \"home\".into(),\n 147│        endpoint_id: \"doyle\".into(),\n 148│        instance: active,\n    │...\n 151│    assert!(e2 > e1, \"EpochSource is strictly monotonic\");\n*152│    let update_offline = RegistryUpdate {\n 153│        subnet: \"home\".into(),\n 154│        endpoint_id: \"doyle\".into(),\n 155│        instance: Instance {\n    │...\n 209│    // ── Node B: decode the feed and apply through the merge seam. ───────────\n*210│    let mut b_regs: BTreeMap<String, SubnetRegistry> = BTreeMap::new();\n*211│    b_regs.insert(\"home\".into(), SubnetRegistry::new());\n 212│\n 213│    let stream = wait_for_stream(&mut b_probe);\n 214│    b_probe.net_stream_subscribe(stream, 0).expect(\"subscribe\");\n    │...\n 248│    );\n*249│    // And B's registry equals A's post-advertise state merged with the newer\n 250│    // update — the two nodes agree on the endpoint's instance.\n 251│    let a_row = &a_regs[\"home\"].instances(\"doyle\")[0];\n 252│    assert_eq!(a_row.node, row.node);\n## rosterprop.rs\n 10│//! address for both B and C (the reach precondition Mesh-D6 consumes) — with B\n*11│//! never relaying a registry row. The store here is an in-memory `RosterStore`\n 12│//! per host (the file-backed `production_roster_exchange` is exercised by its own\n 13│//! unit tests); this test proves the *propagation over the wire*.\n 14│\n## sync.rs\n 38│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n*39│use spt_net::net::registry::{Instance, Status};\n 40│use spt_net::net::sync::{SyncDecoder, SyncRecord};\n 41│use spt_proto::identity::Identity;\n 42│use spt_runtime::{Manifest, ManifestRuntime};\n## two_origin_spanning.rs\n  72│    std::fs::write(asrc.join(\"manifest.toml\"), manifest).unwrap();\n *73│    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();\n  74│\n  75│    // Establish the endpoint + its session ledger (boot A, then /clear → B).\n  76│    let perch = resolve_perch_path(\"cc\", ParentHint::Infer);\n    │...\n 229│    std::fs::write(asrc.join(\"manifest.toml\"), manifest).unwrap();\n*230│    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();\n 231│\n 232│    let perch = resolve_perch_path(\"ccgen\", ParentHint::Infer);\n 233│    std::fs::create_dir_all(&perch).unwrap();\n\n## fixtures/\n### dispatch_fixture.rs\n *1│//! Test-only dispatcher-generation fixture (REDISPATCH-TRUTH D-regressions,\n *2│//! `tests/redispatch.rs`): runs the REAL `dispatch::run_dispatch_loop` against\n  3│//! the named broker, as a child process the test can KILL — a killed\n  4│//! generation drops its workers' broker connections exactly like a production\n  5│//! brain death, which no in-process thread can simulate (a parked serve-loop\n   │...\n 15│    let broker = std::env::args().nth(1).expect(\"usage: dispatch_fixture <broker_name>\");\n*16│    let registry = Arc::new(spt_daemon::registryhost::RegistryHost::new_at(\n 17│        \"fixturenode\",\n 18│        spt_store::epoch::EpochSource::load_from(&spt_store::perch::epoch_file()),\n*19│        spt_daemon::presence::registry_snapshot_dir(),\n 20│    ));\n 21│    let paths = spt_daemon::dispatch::DispatchPaths {\n 22│        owlery: spt_store::perch::owlery_dir(),\n   │...\n 29│        &paths,\n*30│        registry,\n 31│        &stop,\n 32│        Duration::from_millis(50),\n 33│    ) {"},"isError":false,"timestamp":1784271605160}}
{"type":"message","id":"3ae3ebd8","parentId":"f5022118","timestamp":"2026-07-17T07:00:05.190Z","message":{"role":"toolResult","toolCallId":"call_MyAfIOKBlneFWwJxQY3FtuJg|fc_089095e18118ee7d016a59d2f4c5d081909cc042c5481462cf","toolName":"read","content":[{"type":"text","text":"# REDISPATCH-STALL — v0.34 field-stall RCA summary (hertz 2026-07-16, doyle seam-verified same day)\n\nRepo-side condensation of the hertz RCA delivered 2026-07-16 (full text rode the\ndoyle sptc-drain spill; fix design 1–9, the seven-test matrix, and the\nfast-field-discriminator were accepted as filed and ride\n`docs/REDISPATCH-STALL-TRIAGE.md` verbatim).\n\n## Field shape\n\n- Live v0.34 boxes: recurrent 20–30s PTY/RC freezes, observed DISPATCH tails\n  17–62s; poison interarrival median 95.5s.\n- 33 observed `CONN_WRITE_POISONED` windows, ALL 15,000–15,154 ms exactly —\n  the round-4 SharedSend bounded-write deadline (15s) firing back-to-back.\n- 4361 `stream-sub-attach` records per brain generation, 584 `PUMP_PEER_FAIL`.\n- Per-stream, not broker-wide: unrelated conns progressed mid-poison\n  (broker-wide-wedge theory FALSIFIED).\n\n## Mechanism (composition, not one new timer)\n\nREDISPATCH-TRUTH W1's retryable claims (500ms/1s ×3) compose with the broad\n`Err(_)` opener fallback (`dispatch.rs:414` — the comment intends\nold-broker-only, the arm catches ANY error including transport\ntimeout/EOF/poison) to repeatedly manufacture throwaway peek subscribers.\nEach `StreamLog::attach` (`nethost.rs:322`) synchronously replays the ENTIRE\nretained ring under the per-stream mutex; `let _ = sub.write(&frame)` discards\nerrors, iteration continues past failure, and the poisoned subscriber stays\ninstalled — so one wedged subscriber conn converts replay into serial 15s\npoison windows held under the lock. Those windows compose with unbounded\nWhole-brain reads + a distinct unbounded wire conn + the NetHost 10s send into\nthe observed 17–62s DISPATCH tails.\n\n## Companion (control-steal precision)\n\nD1/D1b prove FINISHED rows never serve. The observed steal likely rides\nUNFINISHED-stale rows (raw-close no-FIN viewports — the emphasys C2 leak class\nfeeds this). Discriminating observable on the next live catch: the stolen\nrow's `finished` + `retired` flags. Fix 6 (ownership/generation validation\nbefore attach/detach) covers both shapes.\n\n## Explicitly NOT this\n\n`update --restart`'s 30s rc reconnect loop is distinct and bounded.\n\n## Live field catch (hertz, 2026-07-16 — post-triage corroboration)\n\nCaught in-flight on webbie/v0.34 (hertz box, `daemon.stderr.log` lines\n44707–44769; byte-for-byte snapshot preserved at\n`C:\\Users\\decid\\.omp\\state\\spt-endpoint\\webbie-v034-live-stall-daemon.stderr.log.snapshot`,\n6,909,235 bytes): stream 7058 — subscriber conn 18248 attach/write\nwall=1784249643389 → `CONN_WRITE_POISONED` wall=1784249658401 (**+15.012s\nexact** — dead center of the fingerprint) → `DISPATCH:7058 Failed(fill whole\nbuffer)` +1.046s post-poison → retry subscriber conn 18273 +3.034s\npost-poison, clean close in 8ms. `daemon status` answered in 0.39s DURING the\nwindow (net_up=true, heartbeat current) — per-stream transient wedge\nCONFIRMED, broker-wide IPC freeze falsified a second time on live data.\nIdentity caveat: logs do not bind stream 7058's opener to webbie — timing\ncorrelation only; fix 9's endpoint-keyed telemetry closes that gap\n(counter-63 catch = bound proof). This trace is the before-baseline for the\nfield discriminators.\n\n## Where the full design lives\n\n`docs/REDISPATCH-STALL-TRIAGE.md` (fix set 1–9, seven-test matrix, gate\nrulings, field discriminators) + ADR-0038 Amendment (2026-07-16) +\nKNOWN-HAZARDS 7.43.\n"}],"details":{"displayContent":{"text":"# REDISPATCH-STALL — v0.34 field-stall RCA summary (hertz 2026-07-16, doyle seam-verified same day)\n\nRepo-side condensation of the hertz RCA delivered 2026-07-16 (full text rode the\ndoyle sptc-drain spill; fix design 1–9, the seven-test matrix, and the\nfast-field-discriminator were accepted as filed and ride\n`docs/REDISPATCH-STALL-TRIAGE.md` verbatim).\n\n## Field shape\n\n- Live v0.34 boxes: recurrent 20–30s PTY/RC freezes, observed DISPATCH tails\n  17–62s; poison interarrival median 95.5s.\n- 33 observed `CONN_WRITE_POISONED` windows, ALL 15,000–15,154 ms exactly —\n  the round-4 SharedSend bounded-write deadline (15s) firing back-to-back.\n- 4361 `stream-sub-attach` records per brain generation, 584 `PUMP_PEER_FAIL`.\n- Per-stream, not broker-wide: unrelated conns progressed mid-poison\n  (broker-wide-wedge theory FALSIFIED).\n\n## Mechanism (composition, not one new timer)\n\nREDISPATCH-TRUTH W1's retryable claims (500ms/1s ×3) compose with the broad\n`Err(_)` opener fallback (`dispatch.rs:414` — the comment intends\nold-broker-only, the arm catches ANY error including transport\ntimeout/EOF/poison) to repeatedly manufacture throwaway peek subscribers.\nEach `StreamLog::attach` (`nethost.rs:322`) synchronously replays the ENTIRE\nretained ring under the per-stream mutex; `let _ = sub.write(&frame)` discards\nerrors, iteration continues past failure, and the poisoned subscriber stays\ninstalled — so one wedged subscriber conn converts replay into serial 15s\npoison windows held under the lock. Those windows compose with unbounded\nWhole-brain reads + a distinct unbounded wire conn + the NetHost 10s send into\nthe observed 17–62s DISPATCH tails.\n\n## Companion (control-steal precision)\n\nD1/D1b prove FINISHED rows never serve. The observed steal likely rides\nUNFINISHED-stale rows (raw-close no-FIN viewports — the emphasys C2 leak class\nfeeds this). Discriminating observable on the next live catch: the stolen\nrow's `finished` + `retired` flags. Fix 6 (ownership/generation validation\nbefore attach/detach) covers both shapes.\n\n## Explicitly NOT this\n\n`update --restart`'s 30s rc reconnect loop is distinct and bounded.\n\n## Live field catch (hertz, 2026-07-16 — post-triage corroboration)\n\nCaught in-flight on webbie/v0.34 (hertz box, `daemon.stderr.log` lines\n44707–44769; byte-for-byte snapshot preserved at\n`C:\\Users\\decid\\.omp\\state\\spt-endpoint\\webbie-v034-live-stall-daemon.stderr.log.snapshot`,\n6,909,235 bytes): stream 7058 — subscriber conn 18248 attach/write\nwall=1784249643389 → `CONN_WRITE_POISONED` wall=1784249658401 (**+15.012s\nexact** — dead center of the fingerprint) → `DISPATCH:7058 Failed(fill whole\nbuffer)` +1.046s post-poison → retry subscriber conn 18273 +3.034s\npost-poison, clean close in 8ms. `daemon status` answered in 0.39s DURING the\nwindow (net_up=true, heartbeat current) — per-stream transient wedge\nCONFIRMED, broker-wide IPC freeze falsified a second time on live data.\nIdentity caveat: logs do not bind stream 7058's opener to webbie — timing\ncorrelation only; fix 9's endpoint-keyed telemetry closes that gap\n(counter-63 catch = bound proof). This trace is the before-baseline for the\nfield discriminators.\n\n## Where the full design lives\n\n`docs/REDISPATCH-STALL-TRIAGE.md` (fix set 1–9, seven-test matrix, gate\nrulings, field discriminators) + ADR-0038 Amendment (2026-07-16) +\nKNOWN-HAZARDS 7.43.\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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-stall\\rca-summary.md"}}},"isError":false,"timestamp":1784271605190}}
{"type":"message","id":"c648736a","parentId":"3ae3ebd8","timestamp":"2026-07-17T07:00:05.191Z","message":{"role":"toolResult","toolCallId":"call_gVzHb4Gm9GRBCd9emjEmzCPY|fc_089095e18118ee7d016a59d2f4c5dc819089e37f877a21e3b6","toolName":"read","content":[{"type":"text","text":"HERTZ RCA — `spt daemon refresh` / `spt update` freezes existing remote session PTYs\n\nVerdict: target-brain refresh exposes a production redispatch bug. Hosted PTYs and broker-held QUIC streams survive, but active remote attach handlers are brain-owned and must be reconstructed. The fresh dispatcher indiscriminately replays every historical peer stream still in the broker table—including finished attach streams—and can let stale workers steal and then clear current controllers. A second adjacent defect prevents recovery when an active stream's opener has rolled out of its bounded history.\n\nCausal chain:\n1. `spt daemon refresh` raises the supervisor brain-restart signal. Broker and hosted PTY child/master resources remain alive; only the brain process and its dispatcher/serve workers die. `spt update` uses the same brain-cycle path after apply.\n2. Every active cross-node `spt rc` has a peer-initiated broker-held net stream. Its target-side `serve_attach` worker owns a broker connection labelled `role=brain stream-subscriber stream=X controller session=Y`; therefore that semantic relay dies with the old brain even though the PTY and QUIC stream remain.\n3. The new brain intentionally resumes only hosted session cursors at boot. After net startup it launches a fresh `dispatch::run_dispatch_loop`, whose empty per-process `claimed` set enumerates every broker-held peer stream.\n4. Finished streams are never removed from `NetShared.streams`; `StreamLog::finish` only marks them finished. A new dispatcher therefore replays all historical attach streams, not only active streams.\n5. Each replay worker reads the old `AttachRecord::Request` and calls `attach_as(Control, same origin)`. Same-identity takeover silently calls `become_controller`, so it can supersede the legitimate newly reconstructed/current worker without a `Displaced` signal. Replayed EOF then calls `detach_session`, clearing the controller the stale worker just stole. The legitimate active serve loop stays open and believes it is Controller, but its sink is no longer selected, so the operator sees a frozen PTY.\n6. Detach + `spt rc` creates a fresh stream whose worker runs after the stale replay storm and takes control again, explaining immediate recovery without a PTY restart.\n7. Adjacent recovery defect: reconstruction classifies a stream by `peek_first_line(stream_id)` from sequence 0. `StreamLog` is a bounded 4096-transport-chunk ring and does not preserve protocol openers. An older/high-input active attach stream can lose its Request; replay then starts at Input/Resize, mid-record, or a gap/error. `claimed.insert(stream_id)` happens before spawn and is never cleared or retried on `Unknown`/`Failed`, so that active stream is permanently abandoned too.\n\nWhy 4/5, not all: the primary outcome depends on each endpoint's historical finished attach streams and worker scheduling; the adjacent outcome depends on retained history/activity. Four endpoints had a stale replay takeover and/or unrecoverable active opener, while the unaffected endpoint had neither winning condition in that refresh. On the later update, lia also hit one of them. Endpoint identity itself is not causal.\n\nField evidence on HFENDULEAM:\n- Brain generations 2 and 3 both log `BRAIN_RESUMED: re-established 7 session cursor(s)` and `BRAIN_NET_CONSUMERS_UP`.\n- During those generations the new dispatcher logs failures for persistent and historical streams: `DISPATCH:741:Failed(\"No process is on the other end of the pipe. (os error 233)\")`, `DISPATCH:3:Failed(\"failed to fill whole buffer\")`, `DISPATCH:4496:Failed(...)`, and `DISPATCH:4176:Failed(...)`. The very low and current-looking stream IDs in the same fresh generation are consistent with indiscriminate historical replay; the failures prove one-shot redispatch workers were abandoned. They are supporting evidence, not a one-log-line-per-frozen-endpoint mapping.\n- Detach + `spt rc` reattach restores each session without restarting its hosted PTY, proving the PTY itself did not freeze.\n\nExact code seams:\n- `crates/spt-daemon/src/applyhost.rs`: refresh/apply requests the same supervisor brain cycle.\n- `crates/spt-daemon/src/dispatch.rs:194-232`: fresh dispatcher; one-shot `claimed` insertion before spawn.\n- `dispatch.rs:255-305`: opener replay/classification from seq 0, then fresh family worker.\n- `dispatch.rs:335+` → `attach::serve_attach`: target attach reconstruction.\n- `crates/spt-daemon/src/nethost.rs`: `DEFAULT_STREAM_RING_CHUNKS = 4096`; bounded `StreamLog` append/eviction.\n- `NetShared.streams` has no finished-row removal path; `StreamLog::finish` marks EOF but leaves the row discoverable by every future dispatcher generation.\n- Existing `attach_survives_target_brain_restart_exactly_once` manually re-serves a known short stream, so it bypasses production rediscovery/classification and never rolls the opener.\n\nRecommended source fix:\nA. Remove/retire finished stream rows from redispatch eligibility while preserving only the lifecycle state genuinely needed after EOF. A fresh dispatcher must never re-serve a terminal Attach request.\nB. Make active-stream dispatch identity restart-durable and independent of evictable transport history. Store an immutable bounded opener/classification fact with each broker-held inbound stream (or pin the complete first NDJSON record outside the rolling data ring) until stream close. Recover family + cursor from that metadata, not ring seq 0.\nC. Make dispatcher claims retryable: remove/requeue a claim after transient worker setup failure. Do not hot-loop terminal outcomes; explicitly distinguish active/retryable, served, and finished streams.\nD. Add production-path regressions:\n   1. Create a finished historical Attach plus a current active Attach for the same endpoint/origin, restart only target brain/dispatcher, and prove the historical stream neither takes nor clears the current controller; current input/output remains exactly once without detach.\n   2. Push >4096 transport chunks on an active Attach so seq0/opener is evicted, restart dispatcher, and prove durable classification resumes the same operator stream.\n   3. Inject one transient worker-start failure and prove claim retry recovers without duplicate controller/output.\n\nThis is the source-level RCA. No live refresh/update was rerun during diagnosis; the live incident logs plus code path were used to avoid freezing active sessions again."}],"details":{"displayContent":{"text":"HERTZ RCA — `spt daemon refresh` / `spt update` freezes existing remote session PTYs\n\nVerdict: target-brain refresh exposes a production redispatch bug. Hosted PTYs and broker-held QUIC streams survive, but active remote attach handlers are brain-owned and must be reconstructed. The fresh dispatcher indiscriminately replays every historical peer stream still in the broker table—including finished attach streams—and can let stale workers steal and then clear current controllers. A second adjacent defect prevents recovery when an active stream's opener has rolled out of its bounded history.\n\nCausal chain:\n1. `spt daemon refresh` raises the supervisor brain-restart signal. Broker and hosted PTY child/master resources remain alive; only the brain process and its dispatcher/serve workers die. `spt update` uses the same brain-cycle path after apply.\n2. Every active cross-node `spt rc` has a peer-initiated broker-held net stream. Its target-side `serve_attach` worker owns a broker connection labelled `role=brain stream-subscriber stream=X controller session=Y`; therefore that semantic relay dies with the old brain even though the PTY and QUIC stream remain.\n3. The new brain intentionally resumes only hosted session cursors at boot. After net startup it launches a fresh `dispatch::run_dispatch_loop`, whose empty per-process `claimed` set enumerates every broker-held peer stream.\n4. Finished streams are never removed from `NetShared.streams`; `StreamLog::finish` only marks them finished. A new dispatcher therefore replays all historical attach streams, not only active streams.\n5. Each replay worker reads the old `AttachRecord::Request` and calls `attach_as(Control, same origin)`. Same-identity takeover silently calls `become_controller`, so it can supersede the legitimate newly reconstructed/current worker without a `Displaced` signal. Replayed EOF then calls `detach_session`, clearing the controller the stale worker just stole. The legitimate active serve loop stays open and believes it is Controller, but its sink is no longer selected, so the operator sees a frozen PTY.\n6. Detach + `spt rc` creates a fresh stream whose worker runs after the stale replay storm and takes control again, explaining immediate recovery without a PTY restart.\n7. Adjacent recovery defect: reconstruction classifies a stream by `peek_first_line(stream_id)` from sequence 0. `StreamLog` is a bounded 4096-transport-chunk ring and does not preserve protocol openers. An older/high-input active attach stream can lose its Request; replay then starts at Input/Resize, mid-record, or a gap/error. `claimed.insert(stream_id)` happens before spawn and is never cleared or retried on `Unknown`/`Failed`, so that active stream is permanently abandoned too.\n\nWhy 4/5, not all: the primary outcome depends on each endpoint's historical finished attach streams and worker scheduling; the adjacent outcome depends on retained history/activity. Four endpoints had a stale replay takeover and/or unrecoverable active opener, while the unaffected endpoint had neither winning condition in that refresh. On the later update, lia also hit one of them. Endpoint identity itself is not causal.\n\nField evidence on HFENDULEAM:\n- Brain generations 2 and 3 both log `BRAIN_RESUMED: re-established 7 session cursor(s)` and `BRAIN_NET_CONSUMERS_UP`.\n- During those generations the new dispatcher logs failures for persistent and historical streams: `DISPATCH:741:Failed(\"No process is on the other end of the pipe. (os error 233)\")`, `DISPATCH:3:Failed(\"failed to fill whole buffer\")`, `DISPATCH:4496:Failed(...)`, and `DISPATCH:4176:Failed(...)`. The very low and current-looking stream IDs in the same fresh generation are consistent with indiscriminate historical replay; the failures prove one-shot redispatch workers were abandoned. They are supporting evidence, not a one-log-line-per-frozen-endpoint mapping.\n- Detach + `spt rc` reattach restores each session without restarting its hosted PTY, proving the PTY itself did not freeze.\n\nExact code seams:\n- `crates/spt-daemon/src/applyhost.rs`: refresh/apply requests the same supervisor brain cycle.\n- `crates/spt-daemon/src/dispatch.rs:194-232`: fresh dispatcher; one-shot `claimed` insertion before spawn.\n- `dispatch.rs:255-305`: opener replay/classification from seq 0, then fresh family worker.\n- `dispatch.rs:335+` → `attach::serve_attach`: target attach reconstruction.\n- `crates/spt-daemon/src/nethost.rs`: `DEFAULT_STREAM_RING_CHUNKS = 4096`; bounded `StreamLog` append/eviction.\n- `NetShared.streams` has no finished-row removal path; `StreamLog::finish` marks EOF but leaves the row discoverable by every future dispatcher generation.\n- Existing `attach_survives_target_brain_restart_exactly_once` manually re-serves a known short stream, so it bypasses production rediscovery/classification and never rolls the opener.\n\nRecommended source fix:\nA. Remove/retire finished stream rows from redispatch eligibility while preserving only the lifecycle state genuinely needed after EOF. A fresh dispatcher must never re-serve a terminal Attach request.\nB. Make active-stream dispatch identity restart-durable and independent of evictable transport history. Store an immutable bounded opener/classification fact with each broker-held inbound stream (or pin the complete first NDJSON record outside the rolling data ring) until stream close. Recover family + cursor from that metadata, not ring seq 0.\nC. Make dispatcher claims retryable: remove/requeue a claim after transient worker setup failure. Do not hot-loop terminal outcomes; explicitly distinguish active/retryable, served, and finished streams.\nD. Add production-path regressions:\n   1. Create a finished historical Attach plus a current active Attach for the same endpoint/origin, restart only target brain/dispatcher, and prove the historical stream neither takes nor clears the current controller; current input/output remains exactly once without detach.\n   2. Push >4096 transport chunks on an active Attach so seq0/opener is evicted, restart dispatcher, and prove durable classification resumes the same operator stream.\n   3. Inject one transient worker-start failure and prove claim retry recovers without duplicate controller/output.\n\nThis is the source-level RCA. No live refresh/update was rerun during diagnosis; the live incident logs plus code path were used to avoid freezing active sessions again.","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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.claude\\reports\\2026-07-16-redispatch-truth\\hertz-refresh-rca.txt"}}},"isError":false,"timestamp":1784271605191}}
{"type":"message","id":"26d3987c","parentId":"c648736a","timestamp":"2026-07-17T07:00:14.156Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Locating pump module and reading push semantics**"},{"type":"toolCall","id":"call_hoJQGeFAXo7MNRosQR3LyTCe|fc_089095e18118ee7d016a59d2fe1278819086385d8244643b91","name":"read","arguments":{"i":"Mapping pump module","path":"crates/spt-daemon/src/pump","selector":null}},{"type":"toolCall","id":"call_uBrSuvboLQCQZxbrEhAhTshT|fc_089095e18118ee7d016a59d2fe128c8190946d1176b8053b3f","name":"read","arguments":{"i":"Reading registry pump worker","path":"crates/spt-daemon/src/pump/registry.rs","selector":"1-220"}},{"type":"toolCall","id":"call_Dkgbk1wc1HLXeOd0vEJ1IRNU|fc_089095e18118ee7d016a59d2fe12908190bedc80bd164cea7a","name":"read","arguments":{"i":"Reading push feed transport","path":"crates/spt-daemon/src/pump/mod.rs","selector":"700-850,1040-1140"}},{"type":"toolCall","id":"call_hLv0EoFPkg74MHGZ3PRlClGo|fc_089095e18118ee7d016a59d2fe12988190949527e5b8347305","name":"grep","arguments":{"i":"Tracing pump connection reuse","pattern":"struct PeerIo|trait PumpIo|push_feed|net_dial|conn_id|disconnect|peer_step|run_peer_pump|cold_start","path":"crates/spt-daemon/src/pump","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_yDSmoWDUzxSJAX8k41WP4zy3|fc_089095e18118ee7d016a59d2fe12a08190aadafe2fe357bfac","name":"grep","arguments":{"i":"Checking feed completion retirement","pattern":"push_feed|finish=true|retire|stream_infos|registry feed","path":"crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs; crates/spt-daemon/tests/mesh_recovery.rs","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":22082,"output":311,"cacheRead":181760,"cacheWrite":0,"totalTokens":204153,"reasoningTokens":14,"cost":{"input":0.11041000000000001,"output":0.00933,"cacheRead":0.09088,"cacheWrite":0,"total":0.21062000000000003}},"stopReason":"toolUse","timestamp":1784271605214,"responseId":"resp_089095e18118ee7d016a59d2f5872c8190b0d22aab7ca0dabc","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d2f869188190858bf912ea3fec61","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdL-3jsur0PAPCYDuHCOecWiQQ98qu5RCMUTvy2pqTs_Imk9Q9Es_PMNrBU-I_NqEDbfsxTJwVRlNsTnZ_F2YLYt5j6QIdEQ9kNXiI7wI07iolqmi2WufIw3J2h-Jy59dmSVDQB1Pk7-Jwod9m9XTQlQBLTJcJ_AvAxTsk2sxrCl7Xe9Yj3VPtJ3S_TyF0cGheC2Nrhh9LXOU5V0_9dWk6sSh8jbFUt597-1WKYFfJ2nBMRK2TEcSJF8jJG3K1fXgfr_9Ht0CY5OL9_QmFs5PkmDtFvZpG4-LwCb46_6_fRYWm_uQbfwV8AGOCKWLLF-gLMdhIp_tTqIMYg1rBX-IfRfN9LdRAEl-0dD7Wo20y9WanNaFdck2KzH8B1U2rUjs1FCxI6FYfu8VAhvf9oUmdAkZiPKw6bdtvwci5801Qs6OguMgQgy8AZehhGyuE_u0JXy2z80J33ACq9QnKNOVVQqITzeCizIK-15yVs_7TbtyPo-0vTD-rrzayDHXZ5lSyWPfWudYlvdntHW1IJo4rAd0PvEZgunmDaILzdB2Ngwu--YvnKVqbZpJsey8GJguJwOoQ5b7Op5003LrYEPXm-YkZ3y4m732YHznvM3OuLCuxLJH0sJpdEZml3nDUBpLDHDT12JFNdhOKUQum7KDY0xM7ekxpa28WhNYDt9cswZwMWv_NRoXcDUOALbRhui_Etugjb3TN8CTFhITT9Kghx0QV3SpMwQWyb3DqFitK7YGzwrCaCVJDfUXDAWXEfyF_vyUS0OVQY0bVF4OappCYAVhBMvtuhdyDbpo-wDPxq9ljlbr69oxSmY4unmARf0wZHfyD56VBdkOa2Oqh9Rdil4Gkqx-WfMMGUX1K9ys50DEREfWUyaixtouAkuVmqvTnFcd2SLR4HE6rQuLu6RKdB3NZPwwxzQCDT9sATAhE_hVjM5kQRi9LrTxUxNAWS1tiGliRlJGMrdFN1yWu6OFjOibQpfYtBxb-lKq3bWJxzkTZ8=","summary":[{"type":"summary_text","text":"**Locating pump module and reading push semantics**"}]},{"id":"fc_089095e18118ee7d016a59d2fe1278819086385d8244643b91","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping pump module\",\"path\":\"crates/spt-daemon/src/pump\",\"selector\":null}","call_id":"call_hoJQGeFAXo7MNRosQR3LyTCe","name":"read"},{"id":"fc_089095e18118ee7d016a59d2fe128c8190946d1176b8053b3f","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading registry pump worker\",\"path\":\"crates/spt-daemon/src/pump/registry.rs\",\"selector\":\"1-220\"}","call_id":"call_uBrSuvboLQCQZxbrEhAhTshT","name":"read"},{"id":"fc_089095e18118ee7d016a59d2fe12908190bedc80bd164cea7a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading push feed transport\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"700-850,1040-1140\"}","call_id":"call_Dkgbk1wc1HLXeOd0vEJ1IRNU","name":"read"},{"id":"fc_089095e18118ee7d016a59d2fe12988190949527e5b8347305","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing pump connection reuse\",\"pattern\":\"struct PeerIo|trait PumpIo|push_feed|net_dial|conn_id|disconnect|peer_step|run_peer_pump|cold_start\",\"path\":\"crates/spt-daemon/src/pump\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_hLv0EoFPkg74MHGZ3PRlClGo","name":"grep"},{"id":"fc_089095e18118ee7d016a59d2fe12a08190aadafe2fe357bfac","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking feed completion retirement\",\"pattern\":\"push_feed|finish=true|retire|stream_infos|registry feed\",\"path\":\"crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs; crates/spt-daemon/tests/mesh_recovery.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_yDSmoWDUzxSJAX8k41WP4zy3","name":"grep"}]},"duration":8940.205400004983,"ttft":3215.884800001979,"contextSnapshot":{"promptTokens":203842,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hoJQGeFAXo7MNRosQR3LyTCe|fc_089095e18118ee7d016a59d2fe1278819086385d8244643b91","toolName":"read","startedAt":"2026-07-17T07:00:14.157Z","args":{"path":"crates/spt-daemon/src/pump"},"intent":"Mapping pump module"},"id":"74b3e4fd","parentId":"26d3987c","timestamp":"2026-07-17T07:00:14.157Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_uBrSuvboLQCQZxbrEhAhTshT|fc_089095e18118ee7d016a59d2fe128c8190946d1176b8053b3f","toolName":"read","startedAt":"2026-07-17T07:00:14.157Z","args":{"path":"crates/spt-daemon/src/pump/registry.rs"},"intent":"Reading registry pump worker"},"id":"3e89e106","parentId":"74b3e4fd","timestamp":"2026-07-17T07:00:14.157Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Dkgbk1wc1HLXeOd0vEJ1IRNU|fc_089095e18118ee7d016a59d2fe12908190bedc80bd164cea7a","toolName":"read","startedAt":"2026-07-17T07:00:14.157Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Reading push feed transport"},"id":"e8abf628","parentId":"3e89e106","timestamp":"2026-07-17T07:00:14.157Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hLv0EoFPkg74MHGZ3PRlClGo|fc_089095e18118ee7d016a59d2fe12988190949527e5b8347305","toolName":"grep","startedAt":"2026-07-17T07:00:14.157Z","args":{"path":"crates/spt-daemon/src/pump"},"intent":"Tracing pump connection reuse"},"id":"f0fc02bd","parentId":"e8abf628","timestamp":"2026-07-17T07:00:14.157Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yDSmoWDUzxSJAX8k41WP4zy3|fc_089095e18118ee7d016a59d2fe12a08190aadafe2fe357bfac","toolName":"grep","startedAt":"2026-07-17T07:00:14.157Z","args":{"path":"crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs; crates/spt-daemon/tests/mesh_recovery.rs"},"intent":"Checking feed completion retirement"},"id":"9b2b2e63","parentId":"f0fc02bd","timestamp":"2026-07-17T07:00:14.157Z"}
{"type":"message","id":"3bbd5d7a","parentId":"9b2b2e63","timestamp":"2026-07-17T07:00:14.162Z","message":{"role":"toolResult","toolCallId":"call_hoJQGeFAXo7MNRosQR3LyTCe|fc_089095e18118ee7d016a59d2fe1278819086385d8244643b91","toolName":"read","content":[{"type":"text","text":".\n  - mod.rs       91.5KB    6h ago\n  - health.rs    14.5KB    6h ago\n  - update.rs    7.9KB     1w ago\n  - sync.rs      4.4KB     1w ago\n  - registry.rs  14.6KB    1w ago\n  - notif.rs     1.9KB     1w ago"}],"details":{"isDirectory":true,"resolvedPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump","meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump"}}},"isError":false,"timestamp":1784271614162}}
{"type":"message","id":"058fc976","parentId":"3bbd5d7a","timestamp":"2026-07-17T07:00:14.184Z","message":{"role":"toolResult","toolCallId":"call_Dkgbk1wc1HLXeOd0vEJ1IRNU|fc_089095e18118ee7d016a59d2fe12908190bedc80bd164cea7a","toolName":"read","content":[{"type":"text","text":"pub fn run_peer_pump(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        // broker stopped answering the stream (dead carrier) → bubble → supervised\n        // restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still\n        // restarts the pump; a dead PEER never does).\n        // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n        let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;\n        loop {\n            let read_deadline = if pending.is_empty() {\n                Instant::now() // no outstanding submits — sweep only what is queued\n            } else {\n                round_deadline\n            };\n            match classify_drain_read(events.read_event_until(Some(read_deadline))) {\n                DrainStep::Apply(ev) => handle_presence_event(\n                    ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n                    &mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,\n                )?,\n                DrainStep::Skip => continue, // the event carrier only carries presence\n                DrainStep::RoundDone => break, // quiet (TimedOut) — drain done\n                DrainStep::Restart(e) => return Err(e), // dead broker/carrier → restart\n            }\n        }\n\n        // Persist the round's reachability truth for the status surfaces\n        // (REQ-PUMP-STAGE-TRUTH: the acceptance surface of this contract).\n        health.save_to(&health_path);\n\n        // Mark-after-round with a POST-round instant (ADR-0018 §V4: the\n        // stagger — ran = ATTEMPTED, not succeeded; the next due is measured\n        // from here, not from the round's start).\n        mark_ran(&mut last, &due_flags, Instant::now());\n    }\n    Ok(())\n}\n\n/// Compute each worker's due flag for this tick (the pure scheduler step):\n/// poll EVERY worker's wake first — even a not-otherwise-due leg, so a marker\n/// is never stranded — then the [`due`] kernel over its RAM-only last-ran\n/// instant. Returns the per-worker flags (parallel to `workers`) and whether\n/// ANY leg is due (an all-idle tick sleeps without loading a round).\nfn compute_due(\n    workers: &mut [&mut dyn PumpWorker],\n    last: &[Option<Instant>],\n    now: Instant,\n) -> (Vec<bool>, bool) {\n    let mut flags = vec![false; workers.len()];\n    let mut any = false;\n    for i in 0..workers.len() {\n        let wake = workers[i].poll_wake();\n        flags[i] = due(last[i], wake, now, workers[i].cadence());\n        any |= flags[i];\n    }\n    (flags, any)\n}\n\n/// Run each due worker's node-local `pre_round` ONCE for the round, before any\n/// peer_step (the registry sweep + advertise; the other legs default no-op).\nfn pre_rounds(workers: &mut [&mut dyn PumpWorker], due_flags: &[bool], ctx: &RoundCtx) {\n    for i in 0..workers.len() {\n        if due_flags[i] {\n            workers[i].pre_round(ctx);\n        }\n    }\n}\n\n/// Run `step` for each due worker index in fan-out order, aborting on the\n/// FIRST error (abort-on-first-fail — the caller drops the peer's conn and\n/// redials next tick). The peer-step IO itself lives in `step`; this is the\n/// pure ordering/abort kernel, so the choreography is a unit fact.\nfn run_due(due_flags: &[bool], mut step: impl FnMut(usize) -> io::Result<()>) -> io::Result<()> {\n    for (i, &is_due) in due_flags.iter().enumerate() {\n        if is_due {\n            step(i)?;\n        }\n    }\n    Ok(())\n}\n\n/// Per-peer fan-out outcome (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION) — SUPERSEDES\n/// W1's whole-round `peer_outcome` poison at the round level while KEEPING its\n/// carrier-desync→restart path. `Ok` keeps the conn. An ORDINARY error (including\n/// a connect-then-silent peer's reply-read `TimedOut`, already reclassified OUT of\n/// `TimedOut` by `Brain::read_peer_reply_until`) is a PER-PEER failure: drop THAT\n/// conn + back the peer off (Q3 — a mid-round `peer_step` fail isolates exactly\n/// like a `DIAL_FAILED`, never round-wide), the round continues, the heartbeat\n/// advances. A genuine broker-IPC-CARRIER desync — a raw `TimedOut` (the carrier\n/// ops `net_open_stream` / `subscribe` / `send` keep it) — STILL bubbles `Err` to\n/// a SUPERVISED RESTART: a dead BROKER conn restarts the pump; a dead PEER never\n/// does (the decouple W2 delivers).\n// [impl->REQ-PUMP-PEER-ISOLATION]\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\nfn peer_leg_outcome(\n    result: io::Result<()>,\n    peer_hex: &str,\n    conns: &mut HashMap<String, u64>,\n    sched: &mut HashMap<String, PeerSched>,\n    now: Instant,\n) -> io::Result<()> {\n    match result {\n        Ok(()) => Ok(()),\n        Err(e) if e.kind() == io::ErrorKind::TimedOut => Err(e), // carrier desync → restart\n        Err(e) => {\n            eprintln!(\"PUMP_PEER_FAIL:{peer_hex}:{e}\");\n            conns.remove(peer_hex); // ordinary per-peer failure → drop conn\n            let prev = sched.get(peer_hex).copied();\n            sched.insert(peer_hex.to_string(), next_peer_backoff(prev, now)); // + back off (Q3)\n            Ok(())\n        }\n    }\n}\n\n/// Run every due worker's `peer_step` for a CONNECTED peer across all subnets it\n/// targets this round (one conn serves them all), applying [`peer_leg_outcome`]\n/// after each — an ordinary failure drops the conn + backs the peer off and stops\n/// this peer's remaining legs; a carrier desync bubbles to a supervised restart.\n/// Each worker mints its open-ops only through the shell-owned [`PeerIo`].\n// [impl->REQ-PUMP-PEER-ISOLATION]\n#[allow(clippy::too_many_arguments)]\nfn run_peer_subnets(\n    brain: &mut Brain,\n    ops: &mut EpochSource,\n    workers: &mut [&mut dyn PumpWorker],\n    due_flags: &[bool],\n    conns: &mut HashMap<String, u64>,\n    sched: &mut HashMap<String, PeerSched>,\n    conn_id: u64,\n    subs: &[String],\n    peer_hex: &str,\n    ctx: &RoundCtx,\n    now: Instant,\n) -> io::Result<()> {\n    for sub_name in subs {\n        let outcome = run_due(due_flags, |i| {\n            let mut io = PeerIo {\n                brain: &mut *brain,\n                conn_id,\n                ops: &mut *ops,\n            };\n            workers[i].peer_step(&mut io, sub_name, peer_hex, ctx)\n        });\n        peer_leg_outcome(outcome, peer_hex, conns, sched, now)?;\n        if !conns.contains_key(peer_hex) {\n            break; // an ordinary failure dropped the conn — redial a later round\n        }\n    }\n    Ok(())\n}\n\n/// Apply one drained presence event to the pump's per-peer state (PUMP-TRUTH W2).\n/// `CONNECTED` → cache the conn, RESET the peer's backoff, seed the observed addr\n/// (REQ-CONV-1 write-back rides the async connect), and run the peer's due legs in\n/// THIS round (advertise the round it connects — no head-of-line block by the dead\n…\nfn resolve_submit_addr(\n    resolver: &PeerResolver,\n    peer_hex: &str,\n    peer_addrs: &std::path::Path,\n    roster: &spt_store::roster::RosterStore,\n) -> Option<serde_json::Value> {\n    let cache = PeerAddrStore::load_from(peer_addrs);\n    if let Some(seeded) = cache.valid_route(peer_hex).cloned() {\n        return Some(seeded); // leg 1: healthy validated cache row\n    }\n    if let Some(rostered) = roster_route(roster, peer_hex) {\n        return Some(rostered); // leg 2: validated roster address\n    }\n    resolver(peer_hex) // leg 3: id-only discovery\n}\n\n/// Startup reconcile + invariant migration over `peer-addrs.json` (ADR-0039\n/// Decisions 3+4): (1) validated roster addresses reconcile in — absent rows\n/// fill, suspect/invalid rows are replaced ([`reconcile_peeraddrs`]); (2) any\n/// row STILL violating `outer key == address.id` after the roster pass (no\n/// roster repair available) is dropped, loudly — repaired-or-rejected, never\n/// silently kept, never used as a route (gate ruling 4), and never a\n/// bare-delete of the whole file (valid rows + the id-only fallback stay).\n// [impl->REQ-PEER-ROUTE-CHAIN]\n// [impl->REQ-PEERADDR-INVARIANT]\nfn startup_reconcile_peeraddrs(paths: &PumpPaths, self_hex: &str) {\n    let roster_path = paths\n        .subnets\n        .parent()\n        .map(|p| p.join(\"roster.json\"))\n        .unwrap_or_else(|| PathBuf::from(\"roster.json\"));\n    let roster = spt_store::roster::RosterStore::load_from(&roster_path);\n    let mut pa = PeerAddrStore::load_from(&paths.peer_addrs);\n    let mut changed = crate::seedproofx::reconcile_peeraddrs(&mut pa, self_hex, &roster.members);\n    let invalid: Vec<String> = pa\n        .addrs\n        .iter()\n        .filter(|(k, v)| spt_store::peeraddrs::addr_peer_id(v) != Some(k.as_str()))\n        .map(|(k, _)| k.clone())\n        .collect();\n    for k in invalid {\n        eprintln!(\n            \"PEERADDR_INVARIANT_REPAIR:{k}: dropped a cached row whose address.id does not match its key (no roster repair available) [REQ-PEERADDR-INVARIANT]\"\n        );\n        changed |= pa.forget(&k);\n    }\n    if changed {\n        let _ = pa.save_to(&paths.peer_addrs);\n    }\n}\n\n/// The roster leg of the route chain: any subnet's `RosterEntry` for\n/// `peer_hex` whose advertised address passes id-match validation (gate\n/// ruling 4 — an address claiming a different identity resolves NOTHING).\n// [impl->REQ-PEER-ROUTE-CHAIN]\nfn roster_route(\n    roster: &spt_store::roster::RosterStore,\n    peer_hex: &str,\n) -> Option<serde_json::Value> {\n    roster\n        .members\n        .iter()\n        .filter(|m| m.pubkey_hex == peer_hex)\n        .filter_map(|m| m.address.as_ref())\n        .find(|a| spt_store::peeraddrs::addr_peer_id(a) == Some(peer_hex))\n        .cloned()\n}\n\n/// Monotonic milliseconds since the process's first stamp — the `mono_ms`\n/// half of the stamped failure lines (REQ-PUMP-STAGE-TRUTH): wall time can\n/// step; this cannot, so intra-incident ordering is trustworthy.\nfn mono_ms() -> u64 {\n    use std::sync::OnceLock;\n    static BASE: OnceLock<Instant> = OnceLock::new();\n    BASE.get_or_init(Instant::now).elapsed().as_millis() as u64\n}\n\n/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\n/// and finish it (the receiver's dispatcher classifies + applies). One\n/// stream per feed: streams are single-purpose by the demux contract.\nfn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n    let opened = brain.net_open_stream(conn_id, None)?;\n    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n    Ok(())\n}\n\n/// Connect a fresh brain IPC client in PUMP mode (retrying through broker\n/// startup): the carrier is split at construction with the per-call deadline\n/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the\n/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}"}],"details":{"displayContent":{"text":"pub fn run_peer_pump(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        // broker stopped answering the stream (dead carrier) → bubble → supervised\n        // restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still\n        // restarts the pump; a dead PEER never does).\n        // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n        let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;\n        loop {\n            let read_deadline = if pending.is_empty() {\n                Instant::now() // no outstanding submits — sweep only what is queued\n            } else {\n                round_deadline\n            };\n            match classify_drain_read(events.read_event_until(Some(read_deadline))) {\n                DrainStep::Apply(ev) => handle_presence_event(\n                    ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n                    &mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,\n                )?,\n                DrainStep::Skip => continue, // the event carrier only carries presence\n                DrainStep::RoundDone => break, // quiet (TimedOut) — drain done\n                DrainStep::Restart(e) => return Err(e), // dead broker/carrier → restart\n            }\n        }\n\n        // Persist the round's reachability truth for the status surfaces\n        // (REQ-PUMP-STAGE-TRUTH: the acceptance surface of this contract).\n        health.save_to(&health_path);\n\n        // Mark-after-round with a POST-round instant (ADR-0018 §V4: the\n        // stagger — ran = ATTEMPTED, not succeeded; the next due is measured\n        // from here, not from the round's start).\n        mark_ran(&mut last, &due_flags, Instant::now());\n    }\n    Ok(())\n}\n\n/// Compute each worker's due flag for this tick (the pure scheduler step):\n/// poll EVERY worker's wake first — even a not-otherwise-due leg, so a marker\n/// is never stranded — then the [`due`] kernel over its RAM-only last-ran\n/// instant. Returns the per-worker flags (parallel to `workers`) and whether\n/// ANY leg is due (an all-idle tick sleeps without loading a round).\nfn compute_due(\n    workers: &mut [&mut dyn PumpWorker],\n    last: &[Option<Instant>],\n    now: Instant,\n) -> (Vec<bool>, bool) {\n    let mut flags = vec![false; workers.len()];\n    let mut any = false;\n    for i in 0..workers.len() {\n        let wake = workers[i].poll_wake();\n        flags[i] = due(last[i], wake, now, workers[i].cadence());\n        any |= flags[i];\n    }\n    (flags, any)\n}\n\n/// Run each due worker's node-local `pre_round` ONCE for the round, before any\n/// peer_step (the registry sweep + advertise; the other legs default no-op).\nfn pre_rounds(workers: &mut [&mut dyn PumpWorker], due_flags: &[bool], ctx: &RoundCtx) {\n    for i in 0..workers.len() {\n        if due_flags[i] {\n            workers[i].pre_round(ctx);\n        }\n    }\n}\n\n/// Run `step` for each due worker index in fan-out order, aborting on the\n/// FIRST error (abort-on-first-fail — the caller drops the peer's conn and\n/// redials next tick). The peer-step IO itself lives in `step`; this is the\n/// pure ordering/abort kernel, so the choreography is a unit fact.\nfn run_due(due_flags: &[bool], mut step: impl FnMut(usize) -> io::Result<()>) -> io::Result<()> {\n    for (i, &is_due) in due_flags.iter().enumerate() {\n        if is_due {\n            step(i)?;\n        }\n    }\n    Ok(())\n}\n\n/// Per-peer fan-out outcome (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION) — SUPERSEDES\n/// W1's whole-round `peer_outcome` poison at the round level while KEEPING its\n/// carrier-desync→restart path. `Ok` keeps the conn. An ORDINARY error (including\n/// a connect-then-silent peer's reply-read `TimedOut`, already reclassified OUT of\n/// `TimedOut` by `Brain::read_peer_reply_until`) is a PER-PEER failure: drop THAT\n/// conn + back the peer off (Q3 — a mid-round `peer_step` fail isolates exactly\n/// like a `DIAL_FAILED`, never round-wide), the round continues, the heartbeat\n/// advances. A genuine broker-IPC-CARRIER desync — a raw `TimedOut` (the carrier\n/// ops `net_open_stream` / `subscribe` / `send` keep it) — STILL bubbles `Err` to\n/// a SUPERVISED RESTART: a dead BROKER conn restarts the pump; a dead PEER never\n/// does (the decouple W2 delivers).\n// [impl->REQ-PUMP-PEER-ISOLATION]\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\nfn peer_leg_outcome(\n    result: io::Result<()>,\n    peer_hex: &str,\n    conns: &mut HashMap<String, u64>,\n    sched: &mut HashMap<String, PeerSched>,\n    now: Instant,\n) -> io::Result<()> {\n    match result {\n        Ok(()) => Ok(()),\n        Err(e) if e.kind() == io::ErrorKind::TimedOut => Err(e), // carrier desync → restart\n        Err(e) => {\n            eprintln!(\"PUMP_PEER_FAIL:{peer_hex}:{e}\");\n            conns.remove(peer_hex); // ordinary per-peer failure → drop conn\n            let prev = sched.get(peer_hex).copied();\n            sched.insert(peer_hex.to_string(), next_peer_backoff(prev, now)); // + back off (Q3)\n            Ok(())\n        }\n    }\n}\n\n/// Run every due worker's `peer_step` for a CONNECTED peer across all subnets it\n/// targets this round (one conn serves them all), applying [`peer_leg_outcome`]\n/// after each — an ordinary failure drops the conn + backs the peer off and stops\n/// this peer's remaining legs; a carrier desync bubbles to a supervised restart.\n/// Each worker mints its open-ops only through the shell-owned [`PeerIo`].\n// [impl->REQ-PUMP-PEER-ISOLATION]\n#[allow(clippy::too_many_arguments)]\nfn run_peer_subnets(\n    brain: &mut Brain,\n    ops: &mut EpochSource,\n    workers: &mut [&mut dyn PumpWorker],\n    due_flags: &[bool],\n    conns: &mut HashMap<String, u64>,\n    sched: &mut HashMap<String, PeerSched>,\n    conn_id: u64,\n    subs: &[String],\n    peer_hex: &str,\n    ctx: &RoundCtx,\n    now: Instant,\n) -> io::Result<()> {\n    for sub_name in subs {\n        let outcome = run_due(due_flags, |i| {\n            let mut io = PeerIo {\n                brain: &mut *brain,\n                conn_id,\n                ops: &mut *ops,\n            };\n            workers[i].peer_step(&mut io, sub_name, peer_hex, ctx)\n        });\n        peer_leg_outcome(outcome, peer_hex, conns, sched, now)?;\n        if !conns.contains_key(peer_hex) {\n            break; // an ordinary failure dropped the conn — redial a later round\n        }\n    }\n    Ok(())\n}\n\n/// Apply one drained presence event to the pump's per-peer state (PUMP-TRUTH W2).\n/// `CONNECTED` → cache the conn, RESET the peer's backoff, seed the observed addr\n/// (REQ-CONV-1 write-back rides the async connect), and run the peer's due legs in\n/// THIS round (advertise the round it connects — no head-of-line block by the dead\n…\nfn resolve_submit_addr(\n    resolver: &PeerResolver,\n    peer_hex: &str,\n    peer_addrs: &std::path::Path,\n    roster: &spt_store::roster::RosterStore,\n) -> Option<serde_json::Value> {\n    let cache = PeerAddrStore::load_from(peer_addrs);\n    if let Some(seeded) = cache.valid_route(peer_hex).cloned() {\n        return Some(seeded); // leg 1: healthy validated cache row\n    }\n    if let Some(rostered) = roster_route(roster, peer_hex) {\n        return Some(rostered); // leg 2: validated roster address\n    }\n    resolver(peer_hex) // leg 3: id-only discovery\n}\n\n/// Startup reconcile + invariant migration over `peer-addrs.json` (ADR-0039\n/// Decisions 3+4): (1) validated roster addresses reconcile in — absent rows\n/// fill, suspect/invalid rows are replaced ([`reconcile_peeraddrs`]); (2) any\n/// row STILL violating `outer key == address.id` after the roster pass (no\n/// roster repair available) is dropped, loudly — repaired-or-rejected, never\n/// silently kept, never used as a route (gate ruling 4), and never a\n/// bare-delete of the whole file (valid rows + the id-only fallback stay).\n// [impl->REQ-PEER-ROUTE-CHAIN]\n// [impl->REQ-PEERADDR-INVARIANT]\nfn startup_reconcile_peeraddrs(paths: &PumpPaths, self_hex: &str) {\n    let roster_path = paths\n        .subnets\n        .parent()\n        .map(|p| p.join(\"roster.json\"))\n        .unwrap_or_else(|| PathBuf::from(\"roster.json\"));\n    let roster = spt_store::roster::RosterStore::load_from(&roster_path);\n    let mut pa = PeerAddrStore::load_from(&paths.peer_addrs);\n    let mut changed = crate::seedproofx::reconcile_peeraddrs(&mut pa, self_hex, &roster.members);\n    let invalid: Vec<String> = pa\n        .addrs\n        .iter()\n        .filter(|(k, v)| spt_store::peeraddrs::addr_peer_id(v) != Some(k.as_str()))\n        .map(|(k, _)| k.clone())\n        .collect();\n    for k in invalid {\n        eprintln!(\n            \"PEERADDR_INVARIANT_REPAIR:{k}: dropped a cached row whose address.id does not match its key (no roster repair available) [REQ-PEERADDR-INVARIANT]\"\n        );\n        changed |= pa.forget(&k);\n    }\n    if changed {\n        let _ = pa.save_to(&paths.peer_addrs);\n    }\n}\n\n/// The roster leg of the route chain: any subnet's `RosterEntry` for\n/// `peer_hex` whose advertised address passes id-match validation (gate\n/// ruling 4 — an address claiming a different identity resolves NOTHING).\n// [impl->REQ-PEER-ROUTE-CHAIN]\nfn roster_route(\n    roster: &spt_store::roster::RosterStore,\n    peer_hex: &str,\n) -> Option<serde_json::Value> {\n    roster\n        .members\n        .iter()\n        .filter(|m| m.pubkey_hex == peer_hex)\n        .filter_map(|m| m.address.as_ref())\n        .find(|a| spt_store::peeraddrs::addr_peer_id(a) == Some(peer_hex))\n        .cloned()\n}\n\n/// Monotonic milliseconds since the process's first stamp — the `mono_ms`\n/// half of the stamped failure lines (REQ-PUMP-STAGE-TRUTH): wall time can\n/// step; this cannot, so intra-incident ordering is trustworthy.\nfn mono_ms() -> u64 {\n    use std::sync::OnceLock;\n    static BASE: OnceLock<Instant> = OnceLock::new();\n    BASE.get_or_init(Instant::now).elapsed().as_millis() as u64\n}\n\n/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\n/// and finish it (the receiver's dispatcher classifies + applies). One\n/// stream per feed: streams are single-purpose by the demux contract.\nfn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n    let opened = brain.net_open_stream(conn_id, None)?;\n    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n    Ok(())\n}\n\n/// Connect a fresh brain IPC client in PUMP mode (retrying through broker\n/// startup): the carrier is split at construction with the per-call deadline\n/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the\n/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}","startLine":519,"lineNumbers":[519,null,527,null,594,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,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,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\mod.rs"}}},"isError":false,"timestamp":1784271614183}}
{"type":"message","id":"ca002e75","parentId":"058fc976","timestamp":"2026-07-17T07:00:14.186Z","message":{"role":"toolResult","toolCallId":"call_hLv0EoFPkg74MHGZ3PRlClGo|fc_089095e18118ee7d016a59d2fe12988190949527e5b8347305","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/pump/\n## health.rs\n 132|\n*133|    /// A peer disconnected (no failure — redial-eligible). Only the live\n 134|    /// count moves.\n*135|    pub fn note_disconnected(&mut self, live_peers: usize) {\n 136|        self.live_peers = live_peers;\n 137|    }\n 138|\n## mod.rs\n 7|//! whom* (every roster member of every non-detached member subnet); each worker\n*8|//! owns *what* (its node-local `pre_round` and its per-peer `peer_step`), a thin\n 9|//! call over an already-shipped, idempotent driver. The four legs, in fan-out\n 10|//! order, each in its own module:\n 11|//!\n...\n 34|//! choreography table below): [`compute_due`] (prime-all / wake-forces-one),\n*35|//! [`pre_rounds`] (once per due round, before any peer_step), [`run_due`]\n 36|//! (abort-on-first-fail), [`mark_ran`] (the §V4 stagger), [`fan_targets`]\n 37|//! (detached-skip). LLM-bearing work is never a [`PumpWorker`] (KNOWN-HAZARDS\n 38|//! 7.4) — see the trait's contract.\n...\n 78|use crate::msg::{\n*79|    NetPresenceEvent, PRESENCE_CONNECTED, PRESENCE_DIAL_FAILED, PRESENCE_DISCONNECTED,\n 80|};\n 81|use crate::registryhost::RegistryHost;\n 82|use crate::sync::{SyncPolicy, SyncPullReport};\n...\n 140|/// dial-eligible when `now >= next_due`. A `DIAL_FAILED` (or a mid-round\n*141|/// `peer_step` failure — Q3) doubles `backoff` (capped [`PEER_BACKOFF_CAP`]) and\n 142|/// pushes `next_due` out; a successful connect drops the entry (reset). Kept in a\n 143|/// `HashMap<peer_hex, PeerSched>` on the pump loop — RAM-only, re-primed on a\n 144|/// supervised restart like `WorkerLasts`.\n...\n 363|/// (per-subnet × per-roster-member). The pump owns *when* and *toward whom*;\n*364|/// the worker owns *what* — its node-local `pre_round` and its `peer_step`.\n 365|///\n 366|/// **KH 7.4 — never an LLM-bearing worker.** A pump worker is cadenced,\n 367|/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile\n...\n 382|\n*383|    /// Node-local work run ONCE per due round, before any `peer_step`.\n 384|    /// Default: nothing.\n 385|    fn pre_round(&mut self, _ctx: &RoundCtx) {}\n 386|\n...\n 389|    /// conn (redial next tick) — abort-on-first-fail, current behaviour.\n*390|    fn peer_step(\n 391|        &mut self,\n 392|        io: &mut PeerIo,\n 393|        subnet: &str,\n...\n 460|    /// A minimal empty round context for worker unit tests whose `pre_round` /\n*461|    /// `peer_step` ignore it (the registry leg keys off its own state, not the\n 462|    /// shared loads).\n 463|    fn for_test() -> RoundCtx {\n 464|        RoundCtx {\n...\n 480|/// epoch-lease double-mint and is forbidden.\n*481|struct PeerIo<'a> {\n 482|    /// The one brain IPC handle.\n 483|    brain: &'a mut Brain,\n 484|    /// The established peer conn id for this step.\n*485|    conn_id: u64,\n 486|    /// The pump's sole durable open-op counter. Workers mint open-ops only\n 487|    /// through [`PeerIo::open_op`] (the sync + update legs); a worker-owned\n 488|    /// `EpochSource` over this counter would be the KH epoch-lease double-mint.\n...\n 494|    /// push-feed discipline — `op_id: None`, finished on send).\n*495|    fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {\n*496|        push_feed(self.brain, self.conn_id, feed)\n 497|    }\n 498|\n 499|    /// Mint the next durable open-op from the pump's sole counter (exactly-once\n...\n 514|/// heartbeat → poll every worker's wake → the [`due`] kernel → each due\n*515|/// worker's `pre_round` (once, before any peer_step) → per-subnet × per-peer\n*516|/// (`ensure_conn` → each due worker's `peer_step` in fan-out order,\n 517|/// abort-on-first-fail) → mark-after-round with a POST-round instant. No leg\n 518|/// logic lives here — the shell owns only *when* and *toward whom*.\n*519|pub fn run_peer_pump(\n 520|    broker_name: &str,\n 521|    registry: Arc<RegistryHost>,\n 522|    paths: &PumpPaths,\n...\n 540|    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\n*541|    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\n 542|    // whose reply-reads consume every event kind off their carrier; sharing one\n 543|    // carrier would make a pull-read swallow a presence frame (advancing the\n 544|    // exactly-once cursor while dropping the outcome — the D4c break). Two\n...\n 564|    let mut health = PumpHealth::load_from(&health_path);\n*565|    health.note_disconnected(0); // no conns exist yet in this pump generation\n 566|    let mut ops = EpochSource::load_from(&paths.ops);\n 567|    // The live per-peer connection cache (updated by presence events) + the\n 568|    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\n...\n 620|        // Each due worker's node-local pre_round, once per round before ANY\n*621|        // peer_step (only the registry leg does work here — its sweep +\n 622|        // advertise; the rest default to a no-op).\n 623|        pre_rounds(&mut workers, &due_flags, &ctx);\n 624|\n...\n 643|        for (peer_hex, subs) in &peer_subnets {\n*644|            if let Some(&conn_id) = conns.get(peer_hex) {\n 645|                run_peer_subnets(\n 646|                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n*647|                    conn_id, subs, peer_hex, &ctx, round_start,\n 648|                )?;\n 649|            } else if peer_eligible(&sched, peer_hex, round_start) {\n 650|                if let Some(addr) =\n## notif.rs\n 2|//!\n*3|//! `peer_step` emits this node's notif feed for the subnet\n 4|//! ([`emit_notif_feed`]) and pushes it over the peer's feed stream; the\n 5|//! receiver's join dedups. No node-local `pre_round` and no wake edge — a\n 6|//! plain cadenced push. Construction-captured dep: the notif spool DB path.\n...\n 44|    // [impl->REQ-NOTIF-1]\n*45|    fn peer_step(\n 46|        &mut self,\n 47|        io: &mut PeerIo,\n 48|        subnet: &str,\n...\n 55|        if !feed.is_empty() {\n*56|            io.push_feed(&feed)?;\n 57|        }\n 58|        Ok(())\n 59|    }\n## registry.rs\n 7|//! rotations → re-advertise) ONCE per due round and holds the produced adverts\n*8|//! as worker state; `peer_step` pushes this node's own rows for the given\n 9|//! subnet over the peer's feed stream. Construction-captured deps: the shared\n 10|//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the\n 11|//! two registry cadence knobs.\n...\n 82|    evict_after: Duration,\n*83|    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\n 84|    adverts: Vec<RegistryFeedRecord>,\n 85|}\n 86|\n...\n 123|\n*124|    /// The node-local sweep, run ONCE per due round before any `peer_step`\n 125|    /// (§Frozen-7, order preserved verbatim):\n 126|    fn pre_round(&mut self, _ctx: &RoundCtx) {\n 127|        // Consume the re-pair eviction marker FIRST (M8 decision 13,\n...\n 153|        // Re-advertise this node's own rows; held as worker state for the\n*154|        // per-subnet `peer_step` filter below.\n 155|        self.adverts = self.registry.advertise_local(&self.owlery);\n 156|    }\n 157|\n...\n 160|    /// stream per feed.\n*161|    fn peer_step(\n 162|        &mut self,\n 163|        io: &mut PeerIo,\n 164|        subnet: &str,\n...\n 174|        if !lines.is_empty() {\n*175|            io.push_feed(&lines)?;\n 176|        }\n 177|        Ok(())\n 178|    }\n## sync.rs\n 3|//! `poll_wake` consumes the wake-edge freshness-pull marker (a dormant→active\n*4|//! transition forces a pull NOW); `peer_step` builds the want-set — the union\n 5|//! of locally-tracked allowed branches ([`select_refs`]) and registry-derived\n 6|//! `a-<id>` bootstrap refs (endpoints known in the shared subnet this node\n 7|//! never held locally), both gated by the same [`crate::sync::SyncPolicy`] the\n...\n 85|    // [impl->REQ-INST-5]\n*86|    fn peer_step(\n 87|        &mut self,\n 88|        io: &mut PeerIo,\n 89|        subnet: &str,\n...\n 106|            let open_op = io.open_op()?;\n*107|            let report = request_sync(&mut *io.brain, io.conn_id, &want, open_op, &cs, &self.scratch)?;\n 108|            (self.hooks.on_pull)(peer_hex, &report);\n 109|        }\n 110|        Ok(())\n## update.rs\n 2|//!\n*3|//! `peer_step` runs the REQ-UPD-1/4/6 leg under the PRODUCTION gate: the\n 4|//! VerifyPolicy floor arithmetic (keys + channel pin from `release-keys.json`,\n 5|//! version floor = the staged release as the rollback gate, wall clock for\n 6|//! expiry only), the [`request_update`] pull, and — on a fresh stage — the\n...\n 84|\n*85|    fn peer_step(\n 86|        &mut self,\n 87|        io: &mut PeerIo,\n 88|        subnet: &str,\n...\n 114|            &mut *io.brain,\n*115|            io.conn_id,\n 116|            open_op,\n 117|            &BrokerAbi::current(),\n 118|            &policy,"}],"details":{"scopePath":"crates/spt-daemon/src/pump","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":37,"fileCount":6,"files":["crates/spt-daemon/src/pump/health.rs","crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/notif.rs","crates/spt-daemon/src/pump/registry.rs","crates/spt-daemon/src/pump/sync.rs","crates/spt-daemon/src/pump/update.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/pump/health.rs","count":2},{"path":"crates/spt-daemon/src/pump/mod.rs","count":20},{"path":"crates/spt-daemon/src/pump/notif.rs","count":3},{"path":"crates/spt-daemon/src/pump/registry.rs","count":6},{"path":"crates/spt-daemon/src/pump/sync.rs","count":3},{"path":"crates/spt-daemon/src/pump/update.rs","count":3}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/src/pump/\n## health.rs\n 132│\n*133│    /// A peer disconnected (no failure — redial-eligible). Only the live\n 134│    /// count moves.\n*135│    pub fn note_disconnected(&mut self, live_peers: usize) {\n 136│        self.live_peers = live_peers;\n 137│    }\n 138│\n## mod.rs\n   7│//! whom* (every roster member of every non-detached member subnet); each worker\n  *8│//! owns *what* (its node-local `pre_round` and its per-peer `peer_step`), a thin\n   9│//! call over an already-shipped, idempotent driver. The four legs, in fan-out\n  10│//! order, each in its own module:\n  11│//!\n    │...\n  34│//! choreography table below): [`compute_due`] (prime-all / wake-forces-one),\n *35│//! [`pre_rounds`] (once per due round, before any peer_step), [`run_due`]\n  36│//! (abort-on-first-fail), [`mark_ran`] (the §V4 stagger), [`fan_targets`]\n  37│//! (detached-skip). LLM-bearing work is never a [`PumpWorker`] (KNOWN-HAZARDS\n  38│//! 7.4) — see the trait's contract.\n    │...\n  78│use crate::msg::{\n *79│    NetPresenceEvent, PRESENCE_CONNECTED, PRESENCE_DIAL_FAILED, PRESENCE_DISCONNECTED,\n  80│};\n  81│use crate::registryhost::RegistryHost;\n  82│use crate::sync::{SyncPolicy, SyncPullReport};\n    │...\n 140│/// dial-eligible when `now >= next_due`. A `DIAL_FAILED` (or a mid-round\n*141│/// `peer_step` failure — Q3) doubles `backoff` (capped [`PEER_BACKOFF_CAP`]) and\n 142│/// pushes `next_due` out; a successful connect drops the entry (reset). Kept in a\n 143│/// `HashMap<peer_hex, PeerSched>` on the pump loop — RAM-only, re-primed on a\n 144│/// supervised restart like `WorkerLasts`.\n    │...\n 363│/// (per-subnet × per-roster-member). The pump owns *when* and *toward whom*;\n*364│/// the worker owns *what* — its node-local `pre_round` and its `peer_step`.\n 365│///\n 366│/// **KH 7.4 — never an LLM-bearing worker.** A pump worker is cadenced,\n 367│/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile\n    │...\n 382│\n*383│    /// Node-local work run ONCE per due round, before any `peer_step`.\n 384│    /// Default: nothing.\n 385│    fn pre_round(&mut self, _ctx: &RoundCtx) {}\n 386│\n    │...\n 389│    /// conn (redial next tick) — abort-on-first-fail, current behaviour.\n*390│    fn peer_step(\n 391│        &mut self,\n 392│        io: &mut PeerIo,\n 393│        subnet: &str,\n    │...\n 460│    /// A minimal empty round context for worker unit tests whose `pre_round` /\n*461│    /// `peer_step` ignore it (the registry leg keys off its own state, not the\n 462│    /// shared loads).\n 463│    fn for_test() -> RoundCtx {\n 464│        RoundCtx {\n    │...\n 480│/// epoch-lease double-mint and is forbidden.\n*481│struct PeerIo<'a> {\n 482│    /// The one brain IPC handle.\n 483│    brain: &'a mut Brain,\n 484│    /// The established peer conn id for this step.\n*485│    conn_id: u64,\n 486│    /// The pump's sole durable open-op counter. Workers mint open-ops only\n 487│    /// through [`PeerIo::open_op`] (the sync + update legs); a worker-owned\n 488│    /// `EpochSource` over this counter would be the KH epoch-lease double-mint.\n    │...\n 494│    /// push-feed discipline — `op_id: None`, finished on send).\n*495│    fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {\n*496│        push_feed(self.brain, self.conn_id, feed)\n 497│    }\n 498│\n 499│    /// Mint the next durable open-op from the pump's sole counter (exactly-once\n    │...\n 514│/// heartbeat → poll every worker's wake → the [`due`] kernel → each due\n*515│/// worker's `pre_round` (once, before any peer_step) → per-subnet × per-peer\n*516│/// (`ensure_conn` → each due worker's `peer_step` in fan-out order,\n 517│/// abort-on-first-fail) → mark-after-round with a POST-round instant. No leg\n 518│/// logic lives here — the shell owns only *when* and *toward whom*.\n*519│pub fn run_peer_pump(\n 520│    broker_name: &str,\n 521│    registry: Arc<RegistryHost>,\n 522│    paths: &PumpPaths,\n    │...\n 540│    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\n*541│    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\n 542│    // whose reply-reads consume every event kind off their carrier; sharing one\n 543│    // carrier would make a pull-read swallow a presence frame (advancing the\n 544│    // exactly-once cursor while dropping the outcome — the D4c break). Two\n    │...\n 564│    let mut health = PumpHealth::load_from(&health_path);\n*565│    health.note_disconnected(0); // no conns exist yet in this pump generation\n 566│    let mut ops = EpochSource::load_from(&paths.ops);\n 567│    // The live per-peer connection cache (updated by presence events) + the\n 568│    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\n    │...\n 620│        // Each due worker's node-local pre_round, once per round before ANY\n*621│        // peer_step (only the registry leg does work here — its sweep +\n 622│        // advertise; the rest default to a no-op).\n 623│        pre_rounds(&mut workers, &due_flags, &ctx);\n 624│\n    │...\n 643│        for (peer_hex, subs) in &peer_subnets {\n*644│            if let Some(&conn_id) = conns.get(peer_hex) {\n 645│                run_peer_subnets(\n 646│                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n*647│                    conn_id, subs, peer_hex, &ctx, round_start,\n 648│                )?;\n 649│            } else if peer_eligible(&sched, peer_hex, round_start) {\n 650│                if let Some(addr) =\n## notif.rs\n  2│//!\n *3│//! `peer_step` emits this node's notif feed for the subnet\n  4│//! ([`emit_notif_feed`]) and pushes it over the peer's feed stream; the\n  5│//! receiver's join dedups. No node-local `pre_round` and no wake edge — a\n  6│//! plain cadenced push. Construction-captured dep: the notif spool DB path.\n   │...\n 44│    // [impl->REQ-NOTIF-1]\n*45│    fn peer_step(\n 46│        &mut self,\n 47│        io: &mut PeerIo,\n 48│        subnet: &str,\n   │...\n 55│        if !feed.is_empty() {\n*56│            io.push_feed(&feed)?;\n 57│        }\n 58│        Ok(())\n 59│    }\n## registry.rs\n   7│//! rotations → re-advertise) ONCE per due round and holds the produced adverts\n  *8│//! as worker state; `peer_step` pushes this node's own rows for the given\n   9│//! subnet over the peer's feed stream. Construction-captured deps: the shared\n  10│//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the\n  11│//! two registry cadence knobs.\n    │...\n  82│    evict_after: Duration,\n *83│    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\n  84│    adverts: Vec<RegistryFeedRecord>,\n  85│}\n  86│\n    │...\n 123│\n*124│    /// The node-local sweep, run ONCE per due round before any `peer_step`\n 125│    /// (§Frozen-7, order preserved verbatim):\n 126│    fn pre_round(&mut self, _ctx: &RoundCtx) {\n 127│        // Consume the re-pair eviction marker FIRST (M8 decision 13,\n    │...\n 153│        // Re-advertise this node's own rows; held as worker state for the\n*154│        // per-subnet `peer_step` filter below.\n 155│        self.adverts = self.registry.advertise_local(&self.owlery);\n 156│    }\n 157│\n    │...\n 160│    /// stream per feed.\n*161│    fn peer_step(\n 162│        &mut self,\n 163│        io: &mut PeerIo,\n 164│        subnet: &str,\n    │...\n 174│        if !lines.is_empty() {\n*175│            io.push_feed(&lines)?;\n 176│        }\n 177│        Ok(())\n 178│    }\n## sync.rs\n   3│//! `poll_wake` consumes the wake-edge freshness-pull marker (a dormant→active\n  *4│//! transition forces a pull NOW); `peer_step` builds the want-set — the union\n   5│//! of locally-tracked allowed branches ([`select_refs`]) and registry-derived\n   6│//! `a-<id>` bootstrap refs (endpoints known in the shared subnet this node\n   7│//! never held locally), both gated by the same [`crate::sync::SyncPolicy`] the\n    │...\n  85│    // [impl->REQ-INST-5]\n *86│    fn peer_step(\n  87│        &mut self,\n  88│        io: &mut PeerIo,\n  89│        subnet: &str,\n    │...\n 106│            let open_op = io.open_op()?;\n*107│            let report = request_sync(&mut *io.brain, io.conn_id, &want, open_op, &cs, &self.scratch)?;\n 108│            (self.hooks.on_pull)(peer_hex, &report);\n 109│        }\n 110│        Ok(())\n## update.rs\n   2│//!\n  *3│//! `peer_step` runs the REQ-UPD-1/4/6 leg under the PRODUCTION gate: the\n   4│//! VerifyPolicy floor arithmetic (keys + channel pin from `release-keys.json`,\n   5│//! version floor = the staged release as the rollback gate, wall clock for\n   6│//! expiry only), the [`request_update`] pull, and — on a fresh stage — the\n    │...\n  84│\n *85│    fn peer_step(\n  86│        &mut self,\n  87│        io: &mut PeerIo,\n  88│        subnet: &str,\n    │...\n 114│            &mut *io.brain,\n*115│            io.conn_id,\n 116│            open_op,\n 117│            &BrokerAbi::current(),\n 118│            &policy,"},"isError":false,"timestamp":1784271614186}}
{"type":"message","id":"0c891a1f","parentId":"ca002e75","timestamp":"2026-07-17T07:00:14.192Z","message":{"role":"toolResult","toolCallId":"call_yDSmoWDUzxSJAX8k41WP4zy3|fc_089095e18118ee7d016a59d2fe12a08190aadafe2fe357bfac","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## mesh.rs\n 119|\n*120|/// The inert/registry-only cadence: only registry feeds flow (notif/sync/\n 121|/// update are pushed far enough out to never fire), so the shared canonical\n 122|/// notif/context stores stay untouched across nodes.\n 123|fn cadence() -> PumpCadence {"}],"details":{"scopePath":"crates/spt-daemon/tests/pump.rs, crates/spt-daemon/tests/mesh.rs, crates/spt-daemon/tests/mesh_recovery.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":1,"fileCount":1,"files":["crates/spt-daemon/tests/mesh.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/mesh.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/tests/\n## mesh.rs\n 119│\n*120│/// The inert/registry-only cadence: only registry feeds flow (notif/sync/\n 121│/// update are pushed far enough out to never fire), so the shared canonical\n 122│/// notif/context stores stay untouched across nodes.\n 123│fn cadence() -> PumpCadence {"},"isError":false,"timestamp":1784271614192}}
{"type":"message","id":"44767e4e","parentId":"0c891a1f","timestamp":"2026-07-17T07:00:14.196Z","message":{"role":"toolResult","toolCallId":"call_uBrSuvboLQCQZxbrEhAhTshT|fc_089095e18118ee7d016a59d2fe128c8190946d1176b8053b3f","toolName":"read","content":[{"type":"text","text":"//! The registry pump worker (pump-seam P2) — the first leg cut behind the\n//! [`super::PumpWorker`] seam.\n//!\n//! `poll_wake` consumes the event-driven advertisement marker (force a round\n//! NOW on an endpoint online/offline transition); `pre_round` runs the\n//! node-local sweep (repair-evictions → silent-peer eviction → due seed\n//! rotations → re-advertise) ONCE per due round and holds the produced adverts\n//! as worker state; `peer_step` pushes this node's own rows for the given\n//! subnet over the peer's feed stream. Construction-captured deps: the shared\n//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the\n//! two registry cadence knobs.\n\nuse std::io;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse spt_net::net::replicate::RegistryFeedRecord;\nuse spt_store::subnet::SubnetStore;\n\nuse crate::registryhost::RegistryHost;\n\nuse super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n\n/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,\n/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the\n/// prior generation one-deep for the re-seed grace) and clear its pending entry.\n/// The new seed reaches benign offliners over the member control stream's\n/// re-seed leg on their next connect — never over gossip; the revokee's\n/// already-propagated tombstone denies it the grace. Pure-store work (no conn\n/// handle needed): the daemon re-reads `subnet.json` per connect, so the bump\n/// takes effect without a restart.\n// [impl->REQ-MESH-4]\nfn fire_due_rotations(subnets_path: &std::path::Path, rotations_path: &std::path::Path, now_ms: u64) {\n    let mut pending = spt_store::rotation::RotationPending::load_from(rotations_path);\n    let due = pending.due_subnets(now_ms);\n    if due.is_empty() {\n        return;\n    }\n    let mut subnets = SubnetStore::load_from(subnets_path);\n    let mut rotated = false;\n    for sub in &due {\n        match subnets.rotate_seed(sub) {\n            Ok(rec) => {\n                eprintln!(\"SEED_ROTATED:{sub}:epoch={} (revoke window closed)\", rec.epoch);\n                rotated = true;\n            }\n            // The subnet may have been left/renamed since the revoke — drop the\n            // stale entry rather than retrying it forever.\n            Err(e) => eprintln!(\"SEED_ROTATE_SKIP:{sub}: {e}\"),\n        }\n        pending.clear(sub);\n    }\n    if rotated {\n        if let Err(e) = subnets.save_to(subnets_path) {\n            eprintln!(\"SEED_ROTATE_FAIL: {e}\");\n            return; // keep the pending entry — retry next tick rather than lose it\n        }\n    }\n    let _ = pending.save_to(rotations_path);\n}\n\n/// The registry leg as a [`PumpWorker`]: own-row re-advertisement\n/// ([`RegistryHost::advertise_local`] → one feed stream per peer). The node\n/// re-advertises its own rows; peers reconverge within one cadence\n/// (replay-is-recovery). Own rows only — a relayed third-party row would carry\n/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\npub(super) struct RegistryWorker {\n    /// The shared registry host (sweep + advertise + the canonical epoch).\n    registry: Arc<RegistryHost>,\n    /// The perch tree scanned for advertisable rows.\n    owlery: PathBuf,\n    /// `subnet.json` — read by the due-rotation leg.\n    subnets: PathBuf,\n    /// `rotation-pending.json` — the timeboxed seed-rotation schedule.\n    rotations: PathBuf,\n    /// The event-driven advertisement marker (`poll_wake`).\n    advertise_marker: PathBuf,\n    /// This worker's scheduling period (`cadence.registry`).\n    cadence: Duration,\n    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10).\n    evict_after: Duration,\n    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\n    adverts: Vec<RegistryFeedRecord>,\n}\n\nimpl RegistryWorker {\n    /// Construction capture (§Frozen-4): the shared registry plus this worker's\n    /// `PathBuf` slices and cadence knobs out of the pump's wiring.\n    pub(super) fn new(\n        registry: Arc<RegistryHost>,\n        paths: &PumpPaths,\n        cadence: &PumpCadence,\n    ) -> RegistryWorker {\n        RegistryWorker {\n            registry,\n            owlery: paths.owlery.clone(),\n            subnets: paths.subnets.clone(),\n            rotations: paths.rotations.clone(),\n            advertise_marker: paths.advertise_marker.clone(),\n            cadence: cadence.registry,\n            evict_after: cadence.registry_evict_after,\n            adverts: Vec::new(),\n        }\n    }\n}\n\nimpl PumpWorker for RegistryWorker {\n    fn cadence(&self) -> Duration {\n        self.cadence\n    }\n\n    /// Event-driven advertisement (M8 decision 15, REQ-CONV-2): an endpoint\n    /// online/offline transition dropped the marker — consume it and run the\n    /// registry leg NOW. A wake of the pump loop, not a second advertisement\n    /// path: the forced round is the same advertise_local + push code, so the\n    /// epoch lease and visibility gates ride unchanged. The take IS the remove,\n    /// so one wake claims exactly one forced round.\n    // [impl->REQ-CONV-2]\n    fn poll_wake(&mut self) -> bool {\n        crate::registryhost::take_advertise_now(&self.advertise_marker)\n    }\n\n    /// The node-local sweep, run ONCE per due round before any `peer_step`\n    /// (§Frozen-7, order preserved verbatim):\n    fn pre_round(&mut self, _ctx: &RoundCtx) {\n        // Consume the re-pair eviction marker FIRST (M8 decision 13,\n        // REQ-SUBNET-7): a superseded identity's in-memory rows go before this\n        // tick's snapshot mirror can resurrect the pairing host's disk-side\n        // eviction.\n        // [impl->REQ-SUBNET-7]\n        self.registry.consume_repair_evictions();\n        // Sweep silent-node ghost rows BEFORE re-advertising (KNOWN-HAZARDS\n        // 4.10): a dead node identity's rows decay instead of poisoning bare-id\n        // resolution forever; a live peer re-stamps its heard entry with every\n        // admitted feed and is never touched.\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n        self.registry.evict_silent_peers(self.evict_after);\n        // Trigger (b) — per-row Offline-TTL (#2-secondary): the SAME sweep also\n        // evicts rows that have been non-routable (Offline) beyond the grace even\n        // while their author node is alive (a purge/erase Offline ghost that\n        // ghost-heal re-advertised once and whole-node eviction never reaps). Same\n        // registry_evict_after grace; own rows never decay; the receiver-observed\n        // offline_since is self-pruning so a whole-node eviction above drops its\n        // stamps for free.\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n        self.registry.evict_aged_offline_rows(self.evict_after);\n        // Mesh-D7 (REQ-MESH-4): fire any seed rotation whose revoke window has\n        // closed — after the eviction sweep, before re-advertising. The\n        // re-minted seed rides the member control stream on the next connect;\n        // the revokee's tombstone (already propagated) keeps it locked out.\n        fire_due_rotations(&self.subnets, &self.rotations, now_ms());\n        // Re-advertise this node's own rows; held as worker state for the\n        // per-subnet `peer_step` filter below.\n        self.adverts = self.registry.advertise_local(&self.owlery);\n    }\n\n    /// Push this node's own rows for `subnet` over the peer's feed stream\n    /// (per-subnet filter of the `pre_round` adverts). Fire-and-forget, one\n    /// stream per feed.\n    fn peer_step(\n        &mut self,\n        io: &mut PeerIo,\n        subnet: &str,\n        _peer_hex: &str,\n        _ctx: &RoundCtx,\n    ) -> io::Result<()> {\n        let lines: Vec<u8> = self\n            .adverts\n            .iter()\n            .filter(|u| u.subnet() == subnet)\n            .flat_map(|u| u.encode_line())\n            .collect();\n        if !lines.is_empty() {\n            io.push_feed(&lines)?;\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::test_home::with_home;\n    use spt_store::epoch::EpochSource;\n\n    // [unit->REQ-MESH-4] the pump fires a seed rotation only once its window has\n    // closed: a due subnet rotates (epoch bumps, prior seed retained one-deep)\n    // and its pending entry clears; a not-yet-due subnet is untouched and stays\n    // scheduled. This is the daemon-side half of the coalescing window.\n    #[test]\n    fn fire_due_rotations_rotates_due_and_leaves_pending() {\n        let dir = tempfile::tempdir().unwrap();\n        let subnets_path = dir.path().join(\"subnet.json\");\n        let rotations_path = dir.path().join(\"rotation-pending.json\");\n\n        let mut subnets = SubnetStore::default();\n        let before_home = subnets.create_subnet(\"home\").unwrap().clone();\n        subnets.create_subnet(\"work\").unwrap();\n        subnets.save_to(&subnets_path).unwrap();\n\n        let mut pending = spt_store::rotation::RotationPending::default();\n        pending.coalesce(\"home\", &[\"aa\".to_string()], 1_000); // due at now=1_000\n        pending.coalesce(\"work\", &[\"bb\".to_string()], 5_000); // not yet due\n        pending.save_to(&rotations_path).unwrap();\n\n        fire_due_rotations(&subnets_path, &rotations_path, 1_000);\n\n        let after = SubnetStore::load_from(&subnets_path);\n        let home = after.find(\"home\").unwrap();\n        assert_eq!(home.epoch, before_home.epoch + 1, \"due subnet rotated\");\n        assert_eq!(\n            home.prev_seed_hex.as_deref(),\n            Some(before_home.seed_hex.as_str()),\n            \"prior seed retained for the grace\"\n        );\n        assert_eq!(after.find(\"work\").unwrap().epoch, 1, \"not-due subnet untouched\");\n\n        let pend = spt_store::rotation::RotationPending::load_from(&rotations_path);\n        assert!(!pend.subnets.contains_key(\"home\"), \"fired entry cleared\");\n        assert!(pend.subnets.contains_key(\"work\"), \"pending entry kept\");\n    }\n\n…\n}\n\n[Showing lines 1-223 of 334. Use :224 to continue]"}],"details":{"truncation":{"content":"//! The registry pump worker (pump-seam P2) — the first leg cut behind the\r\n//! [`super::PumpWorker`] seam.\r\n//!\r\n//! `poll_wake` consumes the event-driven advertisement marker (force a round\r\n//! NOW on an endpoint online/offline transition); `pre_round` runs the\r\n//! node-local sweep (repair-evictions → silent-peer eviction → due seed\r\n//! rotations → re-advertise) ONCE per due round and holds the produced adverts\r\n//! as worker state; `peer_step` pushes this node's own rows for the given\r\n//! subnet over the peer's feed stream. Construction-captured deps: the shared\r\n//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the\r\n//! two registry cadence knobs.\r\n\r\nuse std::io;\r\nuse std::path::PathBuf;\r\nuse std::sync::Arc;\r\nuse std::time::Duration;\r\n\r\nuse spt_net::net::replicate::RegistryFeedRecord;\r\nuse spt_store::subnet::SubnetStore;\r\n\r\nuse crate::registryhost::RegistryHost;\r\n\r\nuse super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\r\n\r\n/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,\r\n/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the\r\n/// prior generation one-deep for the re-seed grace) and clear its pending entry.\r\n/// The new seed reaches benign offliners over the member control stream's\r\n/// re-seed leg on their next connect — never over gossip; the revokee's\r\n/// already-propagated tombstone denies it the grace. Pure-store work (no conn\r\n/// handle needed): the daemon re-reads `subnet.json` per connect, so the bump\r\n/// takes effect without a restart.\r\n// [impl->REQ-MESH-4]\r\nfn fire_due_rotations(subnets_path: &std::path::Path, rotations_path: &std::path::Path, now_ms: u64) {\r\n    let mut pending = spt_store::rotation::RotationPending::load_from(rotations_path);\r\n    let due = pending.due_subnets(now_ms);\r\n    if due.is_empty() {\r\n        return;\r\n    }\r\n    let mut subnets = SubnetStore::load_from(subnets_path);\r\n    let mut rotated = false;\r\n    for sub in &due {\r\n        match subnets.rotate_seed(sub) {\r\n            Ok(rec) => {\r\n                eprintln!(\"SEED_ROTATED:{sub}:epoch={} (revoke window closed)\", rec.epoch);\r\n                rotated = true;\r\n            }\r\n            // The subnet may have been left/renamed since the revoke — drop the\r\n            // stale entry rather than retrying it forever.\r\n            Err(e) => eprintln!(\"SEED_ROTATE_SKIP:{sub}: {e}\"),\r\n        }\r\n        pending.clear(sub);\r\n    }\r\n    if rotated {\r\n        if let Err(e) = subnets.save_to(subnets_path) {\r\n            eprintln!(\"SEED_ROTATE_FAIL: {e}\");\r\n            return; // keep the pending entry — retry next tick rather than lose it\r\n        }\r\n    }\r\n    let _ = pending.save_to(rotations_path);\r\n}\r\n\r\n/// The registry leg as a [`PumpWorker`]: own-row re-advertisement\r\n/// ([`RegistryHost::advertise_local`] → one feed stream per peer). The node\r\n/// re-advertises its own rows; peers reconverge within one cadence\r\n/// (replay-is-recovery). Own rows only — a relayed third-party row would carry\r\n/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\r\npub(super) struct RegistryWorker {\r\n    /// The shared registry host (sweep + advertise + the canonical epoch).\r\n    registry: Arc<RegistryHost>,\r\n    /// The perch tree scanned for advertisable rows.\r\n    owlery: PathBuf,\r\n    /// `subnet.json` — read by the due-rotation leg.\r\n    subnets: PathBuf,\r\n    /// `rotation-pending.json` — the timeboxed seed-rotation schedule.\r\n    rotations: PathBuf,\r\n    /// The event-driven advertisement marker (`poll_wake`).\r\n    advertise_marker: PathBuf,\r\n    /// This worker's scheduling period (`cadence.registry`).\r\n    cadence: Duration,\r\n    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10).\r\n    evict_after: Duration,\r\n    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\r\n    adverts: Vec<RegistryFeedRecord>,\r\n}\r\n\r\nimpl RegistryWorker {\r\n    /// Construction capture (§Frozen-4): the shared registry plus this worker's\r\n    /// `PathBuf` slices and cadence knobs out of the pump's wiring.\r\n    pub(super) fn new(\r\n        registry: Arc<RegistryHost>,\r\n        paths: &PumpPaths,\r\n        cadence: &PumpCadence,\r\n    ) -> RegistryWorker {\r\n        RegistryWorker {\r\n            registry,\r\n            owlery: paths.owlery.clone(),\r\n            subnets: paths.subnets.clone(),\r\n            rotations: paths.rotations.clone(),\r\n            advertise_marker: paths.advertise_marker.clone(),\r\n            cadence: cadence.registry,\r\n            evict_after: cadence.registry_evict_after,\r\n            adverts: Vec::new(),\r\n        }\r\n    }\r\n}\r\n\r\nimpl PumpWorker for RegistryWorker {\r\n    fn cadence(&self) -> Duration {\r\n        self.cadence\r\n    }\r\n\r\n    /// Event-driven advertisement (M8 decision 15, REQ-CONV-2): an endpoint\r\n    /// online/offline transition dropped the marker — consume it and run the\r\n    /// registry leg NOW. A wake of the pump loop, not a second advertisement\r\n    /// path: the forced round is the same advertise_local + push code, so the\r\n    /// epoch lease and visibility gates ride unchanged. The take IS the remove,\r\n    /// so one wake claims exactly one forced round.\r\n    // [impl->REQ-CONV-2]\r\n    fn poll_wake(&mut self) -> bool {\r\n        crate::registryhost::take_advertise_now(&self.advertise_marker)\r\n    }\r\n\r\n    /// The node-local sweep, run ONCE per due round before any `peer_step`\r\n    /// (§Frozen-7, order preserved verbatim):\r\n    fn pre_round(&mut self, _ctx: &RoundCtx) {\r\n        // Consume the re-pair eviction marker FIRST (M8 decision 13,\r\n        // REQ-SUBNET-7): a superseded identity's in-memory rows go before this\r\n        // tick's snapshot mirror can resurrect the pairing host's disk-side\r\n        // eviction.\r\n        // [impl->REQ-SUBNET-7]\r\n        self.registry.consume_repair_evictions();\r\n        // Sweep silent-node ghost rows BEFORE re-advertising (KNOWN-HAZARDS\r\n        // 4.10): a dead node identity's rows decay instead of poisoning bare-id\r\n        // resolution forever; a live peer re-stamps its heard entry with every\r\n        // admitted feed and is never touched.\r\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n        self.registry.evict_silent_peers(self.evict_after);\r\n        // Trigger (b) — per-row Offline-TTL (#2-secondary): the SAME sweep also\r\n        // evicts rows that have been non-routable (Offline) beyond the grace even\r\n        // while their author node is alive (a purge/erase Offline ghost that\r\n        // ghost-heal re-advertised once and whole-node eviction never reaps). Same\r\n        // registry_evict_after grace; own rows never decay; the receiver-observed\r\n        // offline_since is self-pruning so a whole-node eviction above drops its\r\n        // stamps for free.\r\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n        self.registry.evict_aged_offline_rows(self.evict_after);\r\n        // Mesh-D7 (REQ-MESH-4): fire any seed rotation whose revoke window has\r\n        // closed — after the eviction sweep, before re-advertising. The\r\n        // re-minted seed rides the member control stream on the next connect;\r\n        // the revokee's tombstone (already propagated) keeps it locked out.\r\n        fire_due_rotations(&self.subnets, &self.rotations, now_ms());\r\n        // Re-advertise this node's own rows; held as worker state for the\r\n        // per-subnet `peer_step` filter below.\r\n        self.adverts = self.registry.advertise_local(&self.owlery);\r\n    }\r\n\r\n    /// Push this node's own rows for `subnet` over the peer's feed stream\r\n    /// (per-subnet filter of the `pre_round` adverts). Fire-and-forget, one\r\n    /// stream per feed.\r\n    fn peer_step(\r\n        &mut self,\r\n        io: &mut PeerIo,\r\n        subnet: &str,\r\n        _peer_hex: &str,\r\n        _ctx: &RoundCtx,\r\n    ) -> io::Result<()> {\r\n        let lines: Vec<u8> = self\r\n            .adverts\r\n            .iter()\r\n            .filter(|u| u.subnet() == subnet)\r\n            .flat_map(|u| u.encode_line())\r\n            .collect();\r\n        if !lines.is_empty() {\r\n            io.push_feed(&lines)?;\r\n        }\r\n        Ok(())\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n    use crate::test_home::with_home;\r\n    use spt_store::epoch::EpochSource;\r\n\r\n    // [unit->REQ-MESH-4] the pump fires a seed rotation only once its window has\r\n    // closed: a due subnet rotates (epoch bumps, prior seed retained one-deep)\r\n    // and its pending entry clears; a not-yet-due subnet is untouched and stays\r\n    // scheduled. This is the daemon-side half of the coalescing window.\r\n    #[test]\r\n    fn fire_due_rotations_rotates_due_and_leaves_pending() {\r\n        let dir = tempfile::tempdir().unwrap();\r\n        let subnets_path = dir.path().join(\"subnet.json\");\r\n        let rotations_path = dir.path().join(\"rotation-pending.json\");\r\n\r\n        let mut subnets = SubnetStore::default();\r\n        let before_home = subnets.create_subnet(\"home\").unwrap().clone();\r\n        subnets.create_subnet(\"work\").unwrap();\r\n        subnets.save_to(&subnets_path).unwrap();\r\n\r\n        let mut pending = spt_store::rotation::RotationPending::default();\r\n        pending.coalesce(\"home\", &[\"aa\".to_string()], 1_000); // due at now=1_000\r\n        pending.coalesce(\"work\", &[\"bb\".to_string()], 5_000); // not yet due\r\n        pending.save_to(&rotations_path).unwrap();\r\n\r\n        fire_due_rotations(&subnets_path, &rotations_path, 1_000);\r\n\r\n        let after = SubnetStore::load_from(&subnets_path);\r\n        let home = after.find(\"home\").unwrap();\r\n        assert_eq!(home.epoch, before_home.epoch + 1, \"due subnet rotated\");\r\n        assert_eq!(\r\n            home.prev_seed_hex.as_deref(),\r\n            Some(before_home.seed_hex.as_str()),\r\n            \"prior seed retained for the grace\"\r\n        );\r\n        assert_eq!(after.find(\"work\").unwrap().epoch, 1, \"not-due subnet untouched\");\r\n\r\n        let pend = spt_store::rotation::RotationPending::load_from(&rotations_path);\r\n        assert!(!pend.subnets.contains_key(\"home\"), \"fired entry cleared\");\r\n        assert!(pend.subnets.contains_key(\"work\"), \"pending entry kept\");\r\n    }\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":334,"totalBytes":10147,"outputLines":223,"outputBytes":10147,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! The registry pump worker (pump-seam P2) — the first leg cut behind the\r\n//! [`super::PumpWorker`] seam.\r\n//!\r\n//! `poll_wake` consumes the event-driven advertisement marker (force a round\r\n//! NOW on an endpoint online/offline transition); `pre_round` runs the\r\n//! node-local sweep (repair-evictions → silent-peer eviction → due seed\r\n//! rotations → re-advertise) ONCE per due round and holds the produced adverts\r\n//! as worker state; `peer_step` pushes this node's own rows for the given\r\n//! subnet over the peer's feed stream. Construction-captured deps: the shared\r\n//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the\r\n//! two registry cadence knobs.\r\n\r\nuse std::io;\r\nuse std::path::PathBuf;\r\nuse std::sync::Arc;\r\nuse std::time::Duration;\r\n\r\nuse spt_net::net::replicate::RegistryFeedRecord;\r\nuse spt_store::subnet::SubnetStore;\r\n\r\nuse crate::registryhost::RegistryHost;\r\n\r\nuse super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\r\n\r\n/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,\r\n/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the\r\n/// prior generation one-deep for the re-seed grace) and clear its pending entry.\r\n/// The new seed reaches benign offliners over the member control stream's\r\n/// re-seed leg on their next connect — never over gossip; the revokee's\r\n/// already-propagated tombstone denies it the grace. Pure-store work (no conn\r\n/// handle needed): the daemon re-reads `subnet.json` per connect, so the bump\r\n/// takes effect without a restart.\r\n// [impl->REQ-MESH-4]\r\nfn fire_due_rotations(subnets_path: &std::path::Path, rotations_path: &std::path::Path, now_ms: u64) {\r\n    let mut pending = spt_store::rotation::RotationPending::load_from(rotations_path);\r\n    let due = pending.due_subnets(now_ms);\r\n    if due.is_empty() {\r\n        return;\r\n    }\r\n    let mut subnets = SubnetStore::load_from(subnets_path);\r\n    let mut rotated = false;\r\n    for sub in &due {\r\n        match subnets.rotate_seed(sub) {\r\n            Ok(rec) => {\r\n                eprintln!(\"SEED_ROTATED:{sub}:epoch={} (revoke window closed)\", rec.epoch);\r\n                rotated = true;\r\n            }\r\n            // The subnet may have been left/renamed since the revoke — drop the\r\n            // stale entry rather than retrying it forever.\r\n            Err(e) => eprintln!(\"SEED_ROTATE_SKIP:{sub}: {e}\"),\r\n        }\r\n        pending.clear(sub);\r\n    }\r\n    if rotated {\r\n        if let Err(e) = subnets.save_to(subnets_path) {\r\n            eprintln!(\"SEED_ROTATE_FAIL: {e}\");\r\n            return; // keep the pending entry — retry next tick rather than lose it\r\n        }\r\n    }\r\n    let _ = pending.save_to(rotations_path);\r\n}\r\n\r\n/// The registry leg as a [`PumpWorker`]: own-row re-advertisement\r\n/// ([`RegistryHost::advertise_local`] → one feed stream per peer). The node\r\n/// re-advertises its own rows; peers reconverge within one cadence\r\n/// (replay-is-recovery). Own rows only — a relayed third-party row would carry\r\n/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\r\npub(super) struct RegistryWorker {\r\n    /// The shared registry host (sweep + advertise + the canonical epoch).\r\n    registry: Arc<RegistryHost>,\r\n    /// The perch tree scanned for advertisable rows.\r\n    owlery: PathBuf,\r\n    /// `subnet.json` — read by the due-rotation leg.\r\n    subnets: PathBuf,\r\n    /// `rotation-pending.json` — the timeboxed seed-rotation schedule.\r\n    rotations: PathBuf,\r\n    /// The event-driven advertisement marker (`poll_wake`).\r\n    advertise_marker: PathBuf,\r\n    /// This worker's scheduling period (`cadence.registry`).\r\n    cadence: Duration,\r\n    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10).\r\n    evict_after: Duration,\r\n    /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.\r\n    adverts: Vec<RegistryFeedRecord>,\r\n}\r\n\r\nimpl RegistryWorker {\r\n    /// Construction capture (§Frozen-4): the shared registry plus this worker's\r\n    /// `PathBuf` slices and cadence knobs out of the pump's wiring.\r\n    pub(super) fn new(\r\n        registry: Arc<RegistryHost>,\r\n        paths: &PumpPaths,\r\n        cadence: &PumpCadence,\r\n    ) -> RegistryWorker {\r\n        RegistryWorker {\r\n            registry,\r\n            owlery: paths.owlery.clone(),\r\n            subnets: paths.subnets.clone(),\r\n            rotations: paths.rotations.clone(),\r\n            advertise_marker: paths.advertise_marker.clone(),\r\n            cadence: cadence.registry,\r\n            evict_after: cadence.registry_evict_after,\r\n            adverts: Vec::new(),\r\n        }\r\n    }\r\n}\r\n\r\nimpl PumpWorker for RegistryWorker {\r\n    fn cadence(&self) -> Duration {\r\n        self.cadence\r\n    }\r\n\r\n    /// Event-driven advertisement (M8 decision 15, REQ-CONV-2): an endpoint\r\n    /// online/offline transition dropped the marker — consume it and run the\r\n    /// registry leg NOW. A wake of the pump loop, not a second advertisement\r\n    /// path: the forced round is the same advertise_local + push code, so the\r\n    /// epoch lease and visibility gates ride unchanged. The take IS the remove,\r\n    /// so one wake claims exactly one forced round.\r\n    // [impl->REQ-CONV-2]\r\n    fn poll_wake(&mut self) -> bool {\r\n        crate::registryhost::take_advertise_now(&self.advertise_marker)\r\n    }\r\n\r\n    /// The node-local sweep, run ONCE per due round before any `peer_step`\r\n    /// (§Frozen-7, order preserved verbatim):\r\n    fn pre_round(&mut self, _ctx: &RoundCtx) {\r\n        // Consume the re-pair eviction marker FIRST (M8 decision 13,\r\n        // REQ-SUBNET-7): a superseded identity's in-memory rows go before this\r\n        // tick's snapshot mirror can resurrect the pairing host's disk-side\r\n        // eviction.\r\n        // [impl->REQ-SUBNET-7]\r\n        self.registry.consume_repair_evictions();\r\n        // Sweep silent-node ghost rows BEFORE re-advertising (KNOWN-HAZARDS\r\n        // 4.10): a dead node identity's rows decay instead of poisoning bare-id\r\n        // resolution forever; a live peer re-stamps its heard entry with every\r\n        // admitted feed and is never touched.\r\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n        self.registry.evict_silent_peers(self.evict_after);\r\n        // Trigger (b) — per-row Offline-TTL (#2-secondary): the SAME sweep also\r\n        // evicts rows that have been non-routable (Offline) beyond the grace even\r\n        // while their author node is alive (a purge/erase Offline ghost that\r\n        // ghost-heal re-advertised once and whole-node eviction never reaps). Same\r\n        // registry_evict_after grace; own rows never decay; the receiver-observed\r\n        // offline_since is self-pruning so a whole-node eviction above drops its\r\n        // stamps for free.\r\n        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n        self.registry.evict_aged_offline_rows(self.evict_after);\r\n        // Mesh-D7 (REQ-MESH-4): fire any seed rotation whose revoke window has\r\n        // closed — after the eviction sweep, before re-advertising. The\r\n        // re-minted seed rides the member control stream on the next connect;\r\n        // the revokee's tombstone (already propagated) keeps it locked out.\r\n        fire_due_rotations(&self.subnets, &self.rotations, now_ms());\r\n        // Re-advertise this node's own rows; held as worker state for the\r\n        // per-subnet `peer_step` filter below.\r\n        self.adverts = self.registry.advertise_local(&self.owlery);\r\n    }\r\n\r\n    /// Push this node's own rows for `subnet` over the peer's feed stream\r\n    /// (per-subnet filter of the `pre_round` adverts). Fire-and-forget, one\r\n    /// stream per feed.\r\n    fn peer_step(\r\n        &mut self,\r\n        io: &mut PeerIo,\r\n        subnet: &str,\r\n        _peer_hex: &str,\r\n        _ctx: &RoundCtx,\r\n    ) -> io::Result<()> {\r\n        let lines: Vec<u8> = self\r\n            .adverts\r\n            .iter()\r\n            .filter(|u| u.subnet() == subnet)\r\n            .flat_map(|u| u.encode_line())\r\n            .collect();\r\n        if !lines.is_empty() {\r\n            io.push_feed(&lines)?;\r\n        }\r\n        Ok(())\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n    use crate::test_home::with_home;\r\n    use spt_store::epoch::EpochSource;\r\n\r\n    // [unit->REQ-MESH-4] the pump fires a seed rotation only once its window has\r\n    // closed: a due subnet rotates (epoch bumps, prior seed retained one-deep)\r\n    // and its pending entry clears; a not-yet-due subnet is untouched and stays\r\n    // scheduled. This is the daemon-side half of the coalescing window.\r\n    #[test]\r\n    fn fire_due_rotations_rotates_due_and_leaves_pending() {\r\n        let dir = tempfile::tempdir().unwrap();\r\n        let subnets_path = dir.path().join(\"subnet.json\");\r\n        let rotations_path = dir.path().join(\"rotation-pending.json\");\r\n\r\n        let mut subnets = SubnetStore::default();\r\n        let before_home = subnets.create_subnet(\"home\").unwrap().clone();\r\n        subnets.create_subnet(\"work\").unwrap();\r\n        subnets.save_to(&subnets_path).unwrap();\r\n\r\n        let mut pending = spt_store::rotation::RotationPending::default();\r\n        pending.coalesce(\"home\", &[\"aa\".to_string()], 1_000); // due at now=1_000\r\n        pending.coalesce(\"work\", &[\"bb\".to_string()], 5_000); // not yet due\r\n        pending.save_to(&rotations_path).unwrap();\r\n\r\n        fire_due_rotations(&subnets_path, &rotations_path, 1_000);\r\n\r\n        let after = SubnetStore::load_from(&subnets_path);\r\n        let home = after.find(\"home\").unwrap();\r\n        assert_eq!(home.epoch, before_home.epoch + 1, \"due subnet rotated\");\r\n        assert_eq!(\r\n            home.prev_seed_hex.as_deref(),\r\n            Some(before_home.seed_hex.as_str()),\r\n            \"prior seed retained for the grace\"\r\n        );\r\n        assert_eq!(after.find(\"work\").unwrap().epoch, 1, \"not-due subnet untouched\");\r\n\r\n        let pend = spt_store::rotation::RotationPending::load_from(&rotations_path);\r\n        assert!(!pend.subnets.contains_key(\"home\"), \"fired entry cleared\");\r\n        assert!(pend.subnets.contains_key(\"work\"), \"pending entry kept\");\r\n    }\r\n\r\n…\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,null,333]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\registry.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":334,"totalBytes":10147,"outputLines":223,"outputBytes":10147,"shownRange":{"start":1,"end":223},"nextOffset":224}}},"isError":false,"timestamp":1784271614195}}
{"type":"message","id":"5f438a05","parentId":"44767e4e","timestamp":"2026-07-17T07:01:00.592Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing feed stream lifecycle and EOF handling**\n\n**Investigating registry feed blocking causes**\n\n**Assessing Brain read blocking by pipe buffer**"},{"type":"thinking","thinking":"**Identifying SubscriberSeat replay bug**\n\n**Analyzing broker connection churn and write failures**\n\n**Examining BrokerConn drop retention issue**"},{"type":"thinking","thinking":"**Assessing broker worker retention risks**\n\n**Investigating Git commit discrepancies**"},{"type":"toolCall","id":"call_iDraMjrXU6iw7bq6kKArz4eF|fc_089095e18118ee7d016a59d32c7f8881909174a1304000ea15","name":"read","arguments":{"i":"Checking current checkout","path":".git/HEAD","selector":"raw"}},{"type":"toolCall","id":"call_88mbR1VXNyy8mpiOf7zdKecH|fc_089095e18118ee7d016a59d32c7fa0819080307e73e4e6094a","name":"read","arguments":{"i":"Checking v036 tag metadata","path":".git/packed-refs","selector":"raw"}},{"type":"toolCall","id":"call_wmvN2R8x90RHdsKKPnJCVY2d|fc_089095e18118ee7d016a59d32c7fa88190ba9bcb9643a9c80a","name":"grep","arguments":{"i":"Finding registry feed scaling","pattern":"advertise_local|save_snapshot|save_to|apply_feed_flips|for .*updates|for .*labels|heard|registry snapshot","path":"crates/spt-daemon/src/registryhost.rs","selector":"200-360","case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_Wh6GwByFaBaT5PDyqCwOQ2F6|fc_089095e18118ee7d016a59d32c7fb48190868c4bcbc6a3f29c","name":"read","arguments":{"i":"Reading registry apply path","path":"crates/spt-daemon/src/registryhost.rs","selector":"200-360"}},{"type":"toolCall","id":"call_yJn9eN3363O0L9yQ6EC5eqkt|fc_089095e18118ee7d016a59d32c7fbc8190ba8256e2fcceb531","name":"read","arguments":{"i":"Reading pump brain send behavior","path":"crates/spt-daemon/src/brain.rs","selector":"1395-1435"}},{"type":"toolCall","id":"call_kdnyImfStY0iFNd0dzbclHDW|fc_089095e18118ee7d016a59d32c7fc4819088c7e23d45688783","name":"grep","arguments":{"i":"Finding Registry feed counts","pattern":"stream-sub-attach.*Registry|family=Registry|stream=524|stream=1161|stream=1347|stream=3053|stream=3810|DISPATCH_EV","path":".claude; *.log; **/*.log","selector":null,"case":false,"gitignore":false,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":9040,"output":1646,"cacheRead":203264,"cacheWrite":0,"totalTokens":213950,"reasoningTokens":1326,"cost":{"input":0.045200000000000004,"output":0.04938,"cacheRead":0.101632,"cacheWrite":0,"total":0.196212}},"stopReason":"toolUse","timestamp":1784271614211,"responseId":"resp_089095e18118ee7d016a59d2fe933881908da3b45e41d01c0b","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d30235148190ad8712f96f85862a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdMTOMYlw1-msS9_k7C2Seede_vAz5pufr77idKrqEWhrVO_g_A7A_xGvpbmps_gQvmq5M7XNa8AKDZ9KW08azWJPH09esZPOOv7AIUxxupIY6gXHXLYcD-nKeVhPTXlzcXrt2h2w6HifDpZw12CFMIpJYQdlMc8pbwv9vbZJ8IiMJjz8gDGfNHutSPUBpGF1KpX7c368yfhG1p3ky4jlKRIkl9qFZXLDlGhljV6mXrHKGvCC7m07FXgNOV55ymjuXh7M_jNwKhA6OWnkNW0UBtOuS9TJiSjp1V5kiaESlDeK7ot_Fy2Kwn7itxj4t8N_qNEwyJrHs5GN1dz1NzELi9M2apD4vx-xc3qFR91Q6F1IT0nXzReAvcN9xbpkS5OtX5j3XFzlnjVVepSBVjCzJZ5IE5OE8KPH4bL0zKAXHyenBgxVoPKCYgjr7COt1K5IzIyAp7Ldj230Nwa105Sntk3UpTMbkrU80_EhR3w_BA17ab_jc8gi_YtZYeOSt6z0Qjp2EWqjs0gf86ImTwSj-jVDqTIoe5-rifJ6-PTwyA5kdcs7vYB3Mg5iu_RTuUZBUyKlTteQumwIXKr-NS8GtOdSDCRBqJUx_k6xpH7tFMKyK4GAd4ZMGNuz9_SJHlP0K8mvWorzkyeBN7zGcbJOPqDPJquaO01CiULnXoG0KB-g8uiVX1LyLQwFajm0xcj1al8ePAKo2L5Imwjt5vCn70WMR_t9wmOTG5c2DozTwjKNUQEgZl0kd1kxADBeKokSmjLc1oJ8shBCFZYaEUsbbE6RM6T7OtMhzPtE3VwM2tdxusfUfmlYcD8bfTRdabSRX0K7WYQQWvrcUgfax6PEPRd347HnR5NaQA8K1WQtNrpTQoRFw6T6iVPFnV2BG0aWSOvulxtLYFiZyeW8YPTUvEth_NOJXqz24Ng7rsM5MjCnHWxXT-P-WobBRmTfJ8yFv_inx2ceT6Cj921IAqnoYuNGQNCPMAljNg7fKnYlwBAlo-go8qic3Rf7rwKGiRV7O9eFQYx-5ykn-qgIOhOIF6AMenRZmVmi8Zv7NSLKkY50W3Li-YI00btvvImxmJAw2qxI3xqSVLv-YUiZuQRnaB0pqNNIRJYT-WBNZ864VzacQoiYEql0qxM6PK6tUni64DIe4paM1jmtsnInVn_8H5pzexTU0WhyKEKxlKDTCC80FxOmQbo-spGqTEGwXUnMVUMLqgJhjN_07hyYjsOhJr2WCJ1b_NuDWR_qTpYeRSpeBFOmhvrPkQwNwasCXmluT17LTs53xac3g90-_5_CmSYOg0RG3ccV9DsaUnxtxPV6VNBzgSshVq1lO3igdVU1wf1laVBITqbMLFVktTxR4m7kt6RZ5LB0S-c-c2DkdsP-uszW2neuCR6EnPelmh2Cg6Pz4itE-K3_TybvaOPQP7-OmVedWxTcf8ZhVEhG9p64e2sAO_nqp0OSiDhs0XGg53bgFAO9-b8wWUU893fGSmt97A1s_eFTQhnmU3GF5LTjNCmOciEGgnnHuTfDWpzjUF-GwfGZ6CZo0WgScnirJZ9fsXsj8vJ51fjWnpVsIzHRQzzza5zgtIcISYr5jsgDXn83EukJMSRqTqBRAu8bYqqhz5aYDq7fwRHBjj0V2gdAC1poJHpx1lmDVhgQaDOzCmvJ12Eb3RG3gJoOUbN_DWPVPTYywSfJ6B8V2b8Zb8Mi6oCn1RtkNQhkGEFkcadlj97ht73eSrlR9vz6WL4u1GxncBHUMDCQ25j0LlXdSJaLFDFQ1LnCfEsk81SMG0EV_iWRVELGqS6blfK_eTZUqhoVWMK50wgP1MVEV0s1TdGgUPVt5XBT1QZvFRFgtWo-NiGgKMHBw9YvluECvCgh4jWBVAjbj8PewAjm8fh2DK3Oov0pCEiaGH-XKwHzHjxVvMLHrwZvwDrNkUwqwUj4kJ_U2zsRT7WuN9lPvtRq4LeEEpAoaEf9pqX9SUAW855x6q13R5gfuVt3hNjQd_PTxdgYJnNRvdK3A8OqDSilSVzyzgFTDNh2tCJ-jEHJAxfcImf6LNOFuQRWFWilb-Q1L9pU678WIRK5Sl00fEnX0uAzSc5P44vdEAt-FUxSOmDZb1xd7nWW_hJVSN1-JZua1BH_qDniJWIimCYY0UdWyjihQgftHvbD5ByQwmVQwBgQh5mTQYWztClH24KQy0-UpuMeWmdRs_euaUDVhDpYsFxRVObcE7fyPdK-F0Ea-zoUbWXT1TyRZIMKFO6iH5NfEkljRHT5p5rbRIkj878HN64oT5v373jbNpJNXGLD_1_eQzdenZBLmiEz3eR2NE_tZQs7JZWb-23GEBcUELEDcDGh7kknEolp5ObvrB8D05UdBxE9YppCj_Z9A7m5kxu6WiysO2Gm41iJE34q4Tvg2UN4XupNnrKlvG1p7N1CmoCr8EO_uAh6j2MRByRy8qD2yVtpBHqVvd8fGomz9N9R7MBwY8U2zuye4oy09_ck-_VokGWiPeKCht33z6a3vg6dz3NsI3bYFYtzoUz6TdZkOuUo1c8xj9_QO11lMJR5jnXYkOswH2SkNQ4MHcHLgqy8Yb5ZDSylNFmtw1CnaHKJ8PriQmziMEZlNK_Cr4lj8MpkmQMOze3Q4M_A6jpyaofr1o478uMJzQq4rbsioUaW1S_kewVfaxXWEzc8fyTZaUDNLKkWJM6bBkddtq3I2Vaeg_579la4qvq-eq4lR2axfbwVkqtQ-psBjekDjwmTiHSbWhX9d4EsOQEv-nQ9w4jzRIjFqH-p4XVn0BCIrnyKk-x1wmFJcUJ58nbbFiYe-1FmXAlVdqn2kIEsR_px_NM1mJgjX2Iqvwu5BpBVwpbDHNE7LxDFnTLN1WLy9y6-gkyfhcw_kxvBQjkQzQJtGHgLvk0RBriD00ou29seSXdNoPnM8YLUBxkmBNyvxDIKm_b2pZ0eD3i3d67h4TnlUL2FxT0Cu6Nc7Hj_q2L42WVNwYucZ6ki4LdnwKl9uNkzgWaNJ1gYEkrYnRAuULD5My-jJiThGS4JHmFYr6tcutsMdBqYXSrL1405fWxdw-s8aDtpXiIJQm69nJ4P1Jh-yLFe-wI7JPjufbNSwsQzn5yfgw9bZRkOvggblgpxFhyLpaijrFywiWeJdARUGoPMhV0QZ1dT6SGIngK9-3m3wfwSjMCMXoQuz2oJzj03vxXjZTO1tp6UAYA8vF70Qg1UBBV7MQzVMJtftqUHQXpUHkDYpsb8AUeVxKgRtdmn744uMVUd9Tz-zL-jWiSZcGlx2LVqO55YwkymX3mz4S4UcMMFdlKD9cSTCumPpctWa7zV4dqdRzyvMXqcd4YXyagzXKt7dTTpx2g5yHSJKx6BnDMRrBCS_O6aK5PzVRnw74em9XuZiW7Xph-1TSoU1hD5tK3kVNOgYmCM07zNJPWrfL6iqvDb1jBJz-7rgSdBPc4v8SXTWt2rIduKrdAo0pYV9RHqVhWZvxG35QyDEuJkUs_b6noR7hp30Ib5DHl6pfjWOstDcnqa60_Lsm2mDdHcWguMYu4MKCWRVmWq7jS3CNPD-ksuNfiSzcXew4f5ARRp5sMNnpUl5NSAHwtiF8FYDgWVTAp6yxaAKAyHQy1KCRxAoPlYbKwu537c85wS7soA1jBWigBP77KdWA7TeCEAInktAEVL_itK4yriccCse9yEOwvCnAAhUMRW_IKvxVjwcVb3-OH0Cxf0UoOcOi5rQSD_IaUNMi3X-W7-eG0bRAO7qdLNikyw0WySVVmHcYo6pYhwfnbJKYQNbOajz273B__1ZoIQhQgRLJq40zixZIftCuDthBEZ6WoOqi2PO-p0dMK3yH1GJ3gnU7ipTfHLQOmZLWkKmt_rPNZ59JZLaCTvZz5Xi2yq69KVVNa7FXjbYi1eLFPcc82Z4Qr19MJrTzOGaX3wrFJK-exuWDTBnklD4zc20HJ6qc0RbTvL32PTPib7z-4dU4E4czOVn7ox87gFMrFoMJ0t_O_sv4vDiIyJCe-jso5Ir-ZC4QsLZNbB2foMqP21Tprx3C355DltQf74a-_Hp2nDMYLSJKkaPCtwpDyYP7qD-ft--7hgp8CTysCU0V2D-H1OSAeixMOgDBRK2F5nh0ifqwbd6sfbUrQnIFWfgN2ICh1V9CqceGGjoFit2RQ-p70GUKgyJlI0wjeo3Xhq6aWgPJ-sbe77WUi-VuW2WVKfVWl81b6HBbIDcdgRAKtB0y6fk-72LxaDCoGEeWHlDHcidnKdnLcyMyrD4SFFmQ=","summary":[{"type":"summary_text","text":"**Analyzing feed stream lifecycle and EOF handling**"},{"type":"summary_text","text":"**Investigating registry feed blocking causes**"},{"type":"summary_text","text":"**Assessing Brain read blocking by pipe buffer**"}]},{"id":"rs_089095e18118ee7d016a59d31392c88190bf5581c4053fa319","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdMhrSMbSjra5qbDzqKqTRVUYdzxdR--mheExoEL5J1iApJfRBmvNRR1pct-aqu1e8Mkrk9eUi8tyYZatGHpq9DS3g4-bPfeahH8VHLo0gAn_cAJM1cjqUv5z1jKZrfDmZcetIMIormfItpYGGBUXfDpl9TjtBlLgVepf3wb9TI9xL3wlHl7fM_kJGwRKz-9hGy-mLKGDwSsco9FjtloY90iw0qO8dV1ChHZ3VJHnDRmk5Wi_AwGePZKmIu5thvvoDNCteJ57yz_L9kYkgRmq_lbEgQ_wwCYrWSpALDdOkn5DXC4cY_rI2_LYbRfUhFU8qbZqIqsqiBpapi_T_NdDM9u7hmGxPCrgMc9oFo-boZ5ixXRElhI0Jl3VCnEl2V6x8nTpEgWZ4g_E4iUGh7RCM3Qf_vnUrS9Ui1DrVWPZSvY0IkcfzFRAjiM1WLfd-LBqt7OsbibgCdM_4DBW3vO_4Tv1_jN-fZTKbglNTqewEuKVNtCvLb--PjqAoSoI4-x14dCwyNeqRpS20GgufUMnd1TgCA8pxI4hyIB5y_H5oze1dOWdcoVX2XQDEqyln6tjUX0MdZKLQNo5O0eYK3BGPTC0rE__I_J0wLGAamQgcy7RDA2WIjpnGdJEx-YugpCbtNrXTtoew1TzRA2Mjz5Ha2KQP9xinhIxi0ragsZod-_UZ9tbG0xbxLYv61yFPVtbY5r_Za2ZYVwe4k2-PFVbT_Ik5nPE1kz3uejVcRNFi3n9i60fNSnmUkf_HdcgLh0wgp2a9tN3wnHFwdsQKEtbkCpObd_Wb4qq5RubssORjqupZ9o4kPnyVZqDOUqTn3jYwiBNJB3iYimo_m7ufwawYT4pKDry6PJhqV_tIp_8BuSfapc9Nsw7XzOOTJJovc9YEnAqrvWabfU1XYVppv5JcIpQE9orch9c-qpIIWg_Id5sELLlD15HPF0eMeFl3ivs4Ijk9YEr2YjmSc3EZ5arDmdFEhOcNlbWAUShzVpPUiaGVC6zHsIgy4jzhqae5TNrY5PuA0lRORy5YOjpns0qMA6ZbM0JKP2yUy3qL0IvNAxIHksHRNSPN2rxalh1FUaP1CGRoIbFnTW0ak7vkZdarmVghhn4V4gspnhqHeUftpfVF6uz1Yce6ef2LY2-30tHLZwVRrIjTRz1o6IvAibKImbDAa9oNb9YbNq8lsmGbvZaLC6wcx5AsuknNvBc8E6hIoQQZGIcL3roQelF7OgTGOwAotatpWyw1lTnnwjOlXTPgbUFOMDRoPMLgPAwmy8HiDnUQ7VYtLnzO6OLpXjAYd0W_ocC0ou1sdfxYnfDigmeDmNuJMEZebhkWWZAaDDhfs76Ef0ge6e00DlnI7N3kubh8MnnMTdmJ_-6kp2FH6HQUK0KxJ5-aL8yl7wGqniA-RezCFW5IwzOPV9ThkFm70NM3KD_m8C3YZg9-mj8yRa29m6_w3Qi84d9o8Cmk6OzrJIaQAxtFOkSrWOg5gx-a2KrKwzyegk6Bc3bKtQLi4FVrM-JkAzXqg05zwJw-TSgcV4hp4OWa1e7WA2aBt62l_sqo6u6X2ZbpS0v0xQRfe4CfVnCaQEaltHu6-z9PopCO_v2A05-R1WYKIFPO9dYdhwjrRp1oq-z-7x9aHapUqyGoEYs88tk2y76yVsbUPB4y343EsYF3dfM2Le_94OBZqvgolAmX8_1Np4mgN77ulM8okM4Op6ENok7IJf_-RBzWsyQxUdLPDPuARmE45Yg5sAbqwCnZBWfHS1tHPfvcfNJQHOMD61KiQB_7UVJO_2j8r6kvfiS1sNigvzsc1wSAca1VoPe6LhLh4DqqnjhhOaKj76vPUAhZ4i4XAB57UDlOU0qqjh5uE0C_IQ6HmFawHxISQTFJsvrI3DXxc6G6CH6aE39Kr0O_PKKLXduh7Q33VDjf_O8a_Lf8kOHgNj1YiCM-oImI2spRDeCKJpqiU5p76oPiwMag8wE2F4hShKEALYyPatukPzfjWLajaDFHjPbVY3egLLBCptnUtMMyq8zYg5ktDoJn5yUEoRkctdRnuAVZTywpomBKlhuFAAW6cAS7Z-GLt0a5oUEgGBJkKxPerSRwkTi4Cs6FZFq4XIEt-Gsqp6aR6CEt6k7ztQCzgsmtt8Ac3uUgtkFZxXnUU0TaWYk0M_YwjdOGNb9RZI7-P2ThG2wouahbngdtfwOMCojXUAYnGEZNswBHchs9tvI7WIWrpUBNtHwbE5K3BRl2bHdciMGi8aIRwEuTe7gYLr-sAGxoGpzuHkK8gqGA3-QaeQIh65Jz7dzrn59Kno6PnRE6ua96k-XNkdCG2rOQJZQE7lea9rDDW1M8Qq69eOSn807zPOKvSsTxu08Ln48eXy4MYT8rVeQmFBdZ6Rkc_OqYv8VGAGmPqwCxu6ybckKwjGs6pwL4vdsCgKB51legEGJoD5yb2kfB6fl5LZpEK9G8EEd9FfI8UZXOoo9_IIEGqatw-5dyfS0Cs9ZCj37bKnXHOBhWSg7Wlug8LJ7g123VlqqqH6_dlP-su5W_fdJ9X8uagreMcFvxVzJ2_8slUpdLX8rg3v5J0EZd5bSc37cKhoQzfwqo6mn6BBUN0Ub3yLZBk7UBlGrX3wR4FeHs26piCK_pCQ8DwF85hyPRxo1rODJNlb0fEKbHPpsYuCkz1yWr_EwyGJtCWQRuZmm0TnHxQyhvV9TJ8eBndoTO2vGePTw9kTxLEYC0TBKKT_uYqx63DOWclLge8K4iyrPWQT4PeIBADeb8PiIooue2xyac9gQgAmWapTzhPUSFkGnOHnFhWvUkRJ1tghLq7Or1YdBqcAL443yF0hclE0i132KxfN3lDTsRGVWFz0HZWJDPBuJfIddm3ugZ3jndS-jxneifAx8jFYGenek56NNNoxCKg1F3OFDGAv4CRsVIo3tm80Qo768mlZYgEW-wmizgabXhEeqDBhWGYTPTQSItENAS6rMnnCSSe-bEkwoucqyVeOrIs3LjUi2Umm4aWFGLqd6pcnTIiYyPZ-P96wXLHvcGNL61IL94vD_P5zX3T5FK_IeHsfEadB6XWeATvOjzduRB161K1BBEjxDgA_BqgTw3BguRWdJBop_I-hhYOKxZywpmqx8gETWcD9wKM6SdqPlrrK-kBV-9K7o_XOAspH_j9KW9BV9mzRjjoxkhaA2kWXCXTDSjYoFvyYiJSPtVLkcAqRLB1RVE8-u0bQBxRNp7nzStWZlLL56GDutc5NW7wa2p2RsiIh9NCpwXmQ16EaxY_vZraSQADI_L4SAncV_ybCmwGdwI-SwtR0YCHJlkwQWCXi48d8_hEOd6M3_KBk1zf9u1F2rrDZMmKeCEJmZpBVUscZ6h7YhYX22FwFdKPyVWwg7SIVlKD-9ciSSUWlQ-XFhe074SbGjAZSogq8OQbSChAWkJZs6t0m5BBVpOaHHfzh3vupjETgeF7RHtGW2n_XDf7Kb2Av9fDvRaZPbTeXzvNpj0FWpQCB7ZW3LjHyAeMzWwpyxNvqar2KExcC2220KGcj2LkvvB-pBWos7Z2CTzN3xWhy_AdUfB1497i9Ns8IFwuXjvgHbjcdwNJFD7tMbdwl8sW9Xk_c2Joi7Sg3AMYKinvmWmpIin01g5VtZsho67tm1UZ94Q6exhB8qOlkgAjl8COJ8QcZ3iNch5m8fC2wWIP7HZLJDxgadERQXDILPXL4NWW_cL1DqrpR23ev0-GoKVNqLQTRHIIfBW5tQDAFEVsFh8GBkMXZSr3cLLxl3ake394XO6XGGJhwvbIarbZTdKXm8dhp_kGghHSEzW05GdwET5cw4FSvQHk6FdBZXi_tK8Y7VLIjJN13a2G1mjBbja8Oo_lxSkGV6xwBAb7QzPHHXaVQB-CkqEeGIIHUzGPvlRTPlU1d73nFNTNWeEmVzPsZfZlwgzXMQSwVW60ZFEmSv5gbnztCIlWxdambbVcF63SnGvaxLaXaSfhf6bdppUjzJA1T1tDz66rhmQTmmDBfdK8AMwoHt35SrAU5IAgj0NJwZ5umRe3wYz6ThK6fRTPlxR_bMpx8XTjzKTgor9wtaNhvm4cMngx7mAD0gKVutQ47SFFzCYlM5OKm4zbrRKpLl7Mp--pTXpePAH8eCBCjA-5UC5z9rkWidv5s7fP8GZflRovHKMokJr2cXEmUdwlyPKetPememX4euZyYq4y7c8DFbtcSfm_HjpJzMNqjv5epBkLY0dRVLAGUR-m4rVin0P996PsiggnqlmyvZiJ_bjSkoMOIMstsTbKodtb78tH7ch2ga99x","summary":[{"type":"summary_text","text":"**Identifying SubscriberSeat replay bug**"},{"type":"summary_text","text":"**Analyzing broker connection churn and write failures**"},{"type":"summary_text","text":"**Examining BrokerConn drop retention issue**"}]},{"id":"rs_089095e18118ee7d016a59d3217158819095ccee2bafea33cc","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdMs9sh0sMAKkpD7_Q8crod-HiKxEC64ri6WfX_34Cy-HjltQz047DEXU7DeqKIQnkq1N6pyJAWOC_YpB1-jX63dLd19ld3BbfhdFDXnbIBmOW_c7VW4_hkMES89CrSqVilqKVmAB6RWssYAh-2YipfFlrbBTbM3vgsZ3pJRHXh8UPUYVsiF0_WkCmVSCRTHPDOiB0dw5qaN22P8uQPjItFNxFmyqkCrEuEIec-m2VumGcdFB6naBRUZdn5mLJCxKaJpgCYfAqs-qYTeuG83DYsWvfk1EkIVOz0XSSZ-kZvyAc73UCRT9-qUv7seGOYbt8600KCbquAD9IVVQRdfLZDUHqVYuEIqwvAdCUYQmjIo0f2sc_nINkXkEu0q9Dng5jlszTcIfcb5VEJ--x8-Xw76G_AXGK53L54a8RavS64bpQbXYXOkYYbRVMDxl20muCLJngr2VFtUd7fjbyw8g5f2Nn4q7U3N1jA7qW1rc6cUqNIVd27xSMvRVnW57BKrOh5ptpD0DTdZiSM0xmPuftAMPjl8nPDN-FVmu-OdboqfZk6Jo45xCGWI1Eqxtcv5CIs2XbhTXzlqfc1J3RmOWIHfufgWdKTilDI_umgoHrG8iWxAV9hKhlsSYJIIKf9VIqGwZb7qTYU125BGvucIyyfIQtAkhxidirITj2WuWRJXMMu7xbdPgVbdC5ATCEA6lR2e7x7HO1BFQqvq6h0bBJGc2QPpceaFu3p5r4tMAv9zXDq0nK_pttcEDCuNcEct8S7FTf3uFqW1egAZ6k3tQldT7nvaMPF9KCzPza-pdyJepzwKI1GBiq3SaYO5DDimoRTU6powqQtN-R69HmpcUTa9sv1CvOoAlJNE24b6lccm716srzb9bDS_gWpgK8LREeEppBDcyzIKDNL5AwG1Fdo7bi8ZBnhq7f5eujtlkGMdlo3IHZGFL6yNlwt3s0AxFwnQOZHRETqo1mdLxpzhy61UnVi82A4Srr6rOniiJaj45Fi_XskDwTXWdloMP_7z2XjILqtGSbeIHCKfV-pe19jjy0BHZEF9qsRVOtuwKf2mKqyaamjarNc-74tcHPHvbVgmHzw7T1iNqruS9w408JqMjcnVmwQohflt9mRVU2h_sJqL7I0Okw_QavkwcqWHxqJY8qqfJTGhBgm3bFCOHFoaQ4Jk1FDs4g2XLxJjIGF88JCmcBhP02PkkKkhtRqTd3zpMmIQ5vydixZE_JDitJaXKm8T3jFY4ukrDwED9D4DjwFnYG6x44OEnNoXr6QO0aBDUjpPovVOU8geJbCriT-EYKa1Sg9XihXHXzOK4OaGcgIhTwJcS3RE3ekotDkDNUOC5o8Cj_W18N3nMt1er_rTfxXUPr0YMII6ayGluJ1qHJJfoHTkWXROpl0_ORMXRtw0gi9WmYsqvnPBqqEcDx0AWbK0KKjpscyqKLdWEDGAAHClgZ52X9gmP7dg4vjuZWbnOFTmat7YDxrv7Iaq2YeHbvLcimH2lwEfnRKg-FJZKP186umIp0v7RQP0sTQELjAhOVnsHmbvTr1L3RdVuvfPyWfF2lI-VFBBqSYgm3nyTiht34E3MCg_pFebQxhb_8NQ2K48MHlVRO70R6LSWRSTcniuzICWN8Dsrxnk3Ua9SYoBVjf9MelILUewwKLgOCrJHYaOkkVsAHRFTgl3BFThwD1eMU8bTuWYk_6shqQrI2UIeE32i6qb8ssH8yYfnzxtkEqXgHufwKBvBhxEW_jzgSFjjPNx31ZrZT5HHIKVT_S5lYLxHJKZ0uq-RmWhyEbSu4oUpt9UxVpeLr7Q1n1TucFSJ_vSgkvU20xMjLneffI8h1KjcpqwdkcBexOa8YngodwM9EhccnG3pXjqGsDdzyBDCEYIT-hXFFhsB5CziSGU6Bc6pPUWSV-aNyBYoxtV6jRxezSCwvrOkkGrjOk3-jq4NgQDlBODk0ulkb2vVk5rfAeRCAVUlus4V5oc0k-q_u8f9ly-CsYypEH-E7IdE_mvqmqg4juiDUIYq3GPZ6TwFEWShnC8fDQ-wIY7SP5hx9WdEplyba2WL9-nMk_zhNlh8dwDw8WI9tPnBRAEOYcC0twUAIqg8rIV5j-SczC9-CF9kQoxEledtl5-LcNfRvvK0q5jVMz3q0tOI1ncHVU90VyTsXZmrBYsODS4X7IGXGcWN2ujXAblQpj9FzflQXYJdaALuJW6SyNVTHGA1C4lW11UkUMzn1j4F8fmpm-UxAUoAXBf16-eIMz_kRrry9zO55UmKGXFODBDg3sVEOk9dYzOs0c4oYuofpfMhRcIOzoqEYdy7dz6W0pmnqQmrHSsnxPkjIknQRqEUw8WHVTYvU02NmERKLHcMU8GSW-xIRHs5RJjlh3Fd1R9tF6TAZaBT93ruCzBE4P4sekrBWS9-BGJbzr2OWeHcrmMuzFbcXmfl0GpW7GTN8KIeg9IRNVC0i6E5Eulq7MGUtPJygi8Qp1osRqRFUD43XZOr8jPJbIF46Fddm4rbPcU1xH05WCfPW_Q7Km3cGiLCHlKrqTfp7qzZvjeNIHR6ZfdIbOoPdvULUmaQxsP8diKOsFr_TOy1hZY0N4N2L_fLVz8ZLj32DYGu37CXAYYGNsaEybB","summary":[{"type":"summary_text","text":"**Assessing broker worker retention risks**"},{"type":"summary_text","text":"**Investigating Git commit discrepancies**"}]},{"id":"fc_089095e18118ee7d016a59d32c7f8881909174a1304000ea15","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking current checkout\",\"path\":\".git/HEAD\",\"selector\":\"raw\"}","call_id":"call_iDraMjrXU6iw7bq6kKArz4eF","name":"read"},{"id":"fc_089095e18118ee7d016a59d32c7fa0819080307e73e4e6094a","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking v036 tag metadata\",\"path\":\".git/packed-refs\",\"selector\":\"raw\"}","call_id":"call_88mbR1VXNyy8mpiOf7zdKecH","name":"read"},{"id":"fc_089095e18118ee7d016a59d32c7fa88190ba9bcb9643a9c80a","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding registry feed scaling\",\"pattern\":\"advertise_local|save_snapshot|save_to|apply_feed_flips|for .*updates|for .*labels|heard|registry snapshot\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"200-360\",\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_wmvN2R8x90RHdsKKPnJCVY2d","name":"grep"},{"id":"fc_089095e18118ee7d016a59d32c7fb48190868c4bcbc6a3f29c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading registry apply path\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"200-360\"}","call_id":"call_Wh6GwByFaBaT5PDyqCwOQ2F6","name":"read"},{"id":"fc_089095e18118ee7d016a59d32c7fbc8190ba8256e2fcceb531","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump brain send behavior\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"1395-1435\"}","call_id":"call_yJn9eN3363O0L9yQ6EC5eqkt","name":"read"},{"id":"fc_089095e18118ee7d016a59d32c7fc4819088c7e23d45688783","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding Registry feed counts\",\"pattern\":\"stream-sub-attach.*Registry|family=Registry|stream=524|stream=1161|stream=1347|stream=3053|stream=3810|DISPATCH_EV\",\"path\":\".claude; *.log; **/*.log\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_kdnyImfStY0iFNd0dzbclHDW","name":"grep"}]},"duration":46380.25100000203,"ttft":4006.3724000006914,"contextSnapshot":{"promptTokens":212304,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_iDraMjrXU6iw7bq6kKArz4eF|fc_089095e18118ee7d016a59d32c7f8881909174a1304000ea15","toolName":"read","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":".git/HEAD"},"intent":"Checking current checkout"},"id":"809ec5fe","parentId":"5f438a05","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_88mbR1VXNyy8mpiOf7zdKecH|fc_089095e18118ee7d016a59d32c7fa0819080307e73e4e6094a","toolName":"read","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":".git/packed-refs"},"intent":"Checking v036 tag metadata"},"id":"13fe2cea","parentId":"809ec5fe","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_wmvN2R8x90RHdsKKPnJCVY2d|fc_089095e18118ee7d016a59d32c7fa88190ba9bcb9643a9c80a","toolName":"grep","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Finding registry feed scaling"},"id":"d212683b","parentId":"13fe2cea","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Wh6GwByFaBaT5PDyqCwOQ2F6|fc_089095e18118ee7d016a59d32c7fb48190868c4bcbc6a3f29c","toolName":"read","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Reading registry apply path"},"id":"a98b12fa","parentId":"d212683b","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yJn9eN3363O0L9yQ6EC5eqkt|fc_089095e18118ee7d016a59d32c7fbc8190ba8256e2fcceb531","toolName":"read","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading pump brain send behavior"},"id":"cae38e73","parentId":"a98b12fa","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_kdnyImfStY0iFNd0dzbclHDW|fc_089095e18118ee7d016a59d32c7fc4819088c7e23d45688783","toolName":"grep","startedAt":"2026-07-17T07:01:00.593Z","args":{"path":".claude; *.log; **/*.log"},"intent":"Finding Registry feed counts"},"id":"4e9687f0","parentId":"cae38e73","timestamp":"2026-07-17T07:01:00.593Z"}
{"type":"message","id":"1b98d731","parentId":"4e9687f0","timestamp":"2026-07-17T07:01:00.603Z","message":{"role":"toolResult","toolCallId":"call_iDraMjrXU6iw7bq6kKArz4eF|fc_089095e18118ee7d016a59d32c7f8881909174a1304000ea15","toolName":"read","content":[{"type":"text","text":"ref: refs/heads/docs/v0360-notes-plain-language\n"}],"details":{"displayContent":{"text":"ref: refs/heads/docs/v0360-notes-plain-language\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\HEAD"}}},"isError":false,"timestamp":1784271660603}}
{"type":"message","id":"4998ec68","parentId":"1b98d731","timestamp":"2026-07-17T07:01:00.628Z","message":{"role":"toolResult","toolCallId":"call_Wh6GwByFaBaT5PDyqCwOQ2F6|fc_089095e18118ee7d016a59d32c7fb48190868c4bcbc6a3f29c","toolName":"read","content":[{"type":"text","text":"    /// otherwise issue the same id from independent counters. (Field bug: the\n    /// pump minted consent notif_ids from the stream-open op counter\n    /// `pump-ops.json` — a different file — so on a node whose two counters\n    /// were both low the consent notif and a presence/notify notif collided on\n    /// `node:1`. The rig's presence-redirect rung caught it: `UNIQUE\n    /// constraint failed: notifs.notif_id`.) Locks the shared cursor so the\n    /// dispatcher's registry arm and the pump never double-mint.\n    // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]\n    pub fn with_epoch<R>(&self, f: impl FnOnce(&mut EpochSource) -> R) -> R {\n        f(&mut self.epoch.lock().unwrap())\n    }\n\n    /// Apply one inbound feed under the gate. `origin_node` MUST be the\n    /// handshake-proven stream-table origin, never payload bytes.\n    // [impl->REQ-INST-7]\n    // [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n    pub fn apply_feed(\n        &self,\n        origin_node: &str,\n        updates: &[RegistryUpdate],\n        policy: &RegistryGatePolicy,\n    ) -> Vec<RegistryApplyVerdict> {\n        self.apply_feed_flips(origin_node, updates, policy).0\n    }\n\n    /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention\n    /// flips** (D9-2, REQ-INST-3): the endpoint ids whose admitted update was\n    /// an observed *transition to `Active` on a peer node* — the wire signal\n    /// that another instance of the id just became the driven one (its\n    /// daemon advertises transitions epoch-bumped), which rests a local\n    /// instance of the same id warm (CONTEXT: driving `ling@laptop` makes\n    /// `ling@desktop` dormant).\n    ///\n    /// Flip = `Updated` merge where the stored row's status was NOT `Active`\n    /// and the incoming one is. Deliberately **never `Inserted`**: this\n    /// registry is in-memory, so a daemon restart re-inserts every peer row\n    /// at its *current* status — counting inserts would knock local\n    /// instances dormant on every restart; and never the periodic\n    /// re-advertisement (`Updated` with prev already `Active` is not a\n    /// flip). The lease has already dropped stale/equal epochs before any\n    /// of this runs.\n    // [impl->REQ-INST-3]\n    pub fn apply_feed_flips(\n        &self,\n        origin_node: &str,\n        updates: &[RegistryUpdate],\n        policy: &RegistryGatePolicy,\n    ) -> (Vec<RegistryApplyVerdict>, Vec<String>) {\n        let mut regs = self.regs.lock().unwrap();\n        let mut flips: Vec<String> = Vec::new();\n        let mut admitted_any = false;\n        let verdicts = updates\n            .iter()\n            .map(|u| {\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\n                    return RegistryApplyVerdict::NotMember;\n                }\n                // [impl->REQ-MESH-5]\n                if !policy.roster.is_member(&u.subnet, origin_node) {\n                    return RegistryApplyVerdict::UntrustedOrigin;\n                }\n                admitted_any = true;\n                let reg = regs.entry(u.subnet.clone()).or_default();\n                let prev = reg\n                    .instances(&u.endpoint_id)\n                    .iter()\n                    .find(|i| i.node == u.instance.node)\n                    .map(|i| i.status);\n                // The epoch lease (4.8) decides; an own-row echo from a peer\n                // is dropped Stale by construction (our epoch is newer or\n                // equal — never authored elsewhere).\n                let outcome = reg.merge_instance(&u.endpoint_id, u.instance.clone());\n                if outcome == MergeOutcome::Updated\n                    && u.instance.status == Status::Active\n                    && prev != Some(Status::Active)\n                    && u.instance.node != self.node_hex\n                    && !flips.contains(&u.endpoint_id)\n                {\n                    flips.push(u.endpoint_id.clone());\n                }\n                RegistryApplyVerdict::Merged(outcome)\n            })\n            .collect();\n        drop(regs);\n        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin\n        // node is up right now — even a Stale merge verdict is a liveness\n        // signal (the lease judged the row, not the sender). Refused feeds\n        // stamp nothing (an untrusted sender earns no liveness credit).\n        // [impl->REQ-SUBNET-1]\n        if admitted_any {\n            self.heard\n                .lock()\n                .unwrap()\n                .insert(origin_node.to_string(), wall_ms());\n            self.stamp_admit();\n        }\n        self.write_snapshots();\n        (verdicts, flips)\n    }\n\n    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME\n    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an\n    /// untrusted origin is refused; an admitted update merges via\n    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the\n    /// origin's gossip-recency (an admitted label feed proves the peer is up,\n    /// exactly like an instance feed). The endpoint-less carrier: this is how a\n    /// peer with zero endpoints gets its hostname into our `--nodes` view.\n    // [impl->REQ-SUBNET-3]\n    pub fn apply_node_labels(\n        &self,\n        origin_node: &str,\n        updates: &[NodeLabelUpdate],\n        policy: &RegistryGatePolicy,\n    ) -> Vec<RegistryApplyVerdict> {\n        let mut regs = self.regs.lock().unwrap();\n        let mut admitted_any = false;\n        let verdicts = updates\n            .iter()\n            .map(|u| {\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\n                    return RegistryApplyVerdict::NotMember;\n                }\n                // [impl->REQ-MESH-5]\n                if !policy.roster.is_member(&u.subnet, origin_node) {\n                    return RegistryApplyVerdict::UntrustedOrigin;\n                }\n                admitted_any = true;\n                let reg = regs.entry(u.subnet.clone()).or_default();\n                RegistryApplyVerdict::Merged(reg.merge_node_label(\n                    &u.node,\n                    u.label.clone(),\n                    u.epoch,\n                ))\n            })\n            .collect();\n        drop(regs);\n        if admitted_any {\n            self.heard\n                .lock()\n                .unwrap()\n                .insert(origin_node.to_string(), wall_ms());\n            self.stamp_admit();\n        }\n        self.write_snapshots();\n        verdicts\n    }\n\n    /// Record an admitted inbound feed (any origin) for the health surface.\n    // [impl->REQ-PUMP-STAGE-TRUTH]\n    fn stamp_admit(&self) {\n        self.last_admit_ms\n            .store(wall_ms(), std::sync::atomic::Ordering::Relaxed);\n    }\n\n    /// Wall-clock ms of the last admitted inbound registry/label feed, if any\n    /// this process lifetime (ADR-0039 Decision 5 status field).\n    // [impl->REQ-PUMP-STAGE-TRUTH]\n    pub fn last_admit_ms(&self) -> Option<u64> {\n        match self\n            .last_admit_ms\n            .load(std::sync::atomic::Ordering::Relaxed)\n        {\n            0 => None,\n            ms => Some(ms),\n        }\n    }\n\n[Showing lines 199-363 of 2238. Use :364 to continue]"}],"details":{"truncation":{"content":"    /// otherwise issue the same id from independent counters. (Field bug: the\r\n    /// pump minted consent notif_ids from the stream-open op counter\r\n    /// `pump-ops.json` — a different file — so on a node whose two counters\r\n    /// were both low the consent notif and a presence/notify notif collided on\r\n    /// `node:1`. The rig's presence-redirect rung caught it: `UNIQUE\r\n    /// constraint failed: notifs.notif_id`.) Locks the shared cursor so the\r\n    /// dispatcher's registry arm and the pump never double-mint.\r\n    // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]\r\n    pub fn with_epoch<R>(&self, f: impl FnOnce(&mut EpochSource) -> R) -> R {\r\n        f(&mut self.epoch.lock().unwrap())\r\n    }\r\n\r\n    /// Apply one inbound feed under the gate. `origin_node` MUST be the\r\n    /// handshake-proven stream-table origin, never payload bytes.\r\n    // [impl->REQ-INST-7]\r\n    // [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n    pub fn apply_feed(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[RegistryUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> Vec<RegistryApplyVerdict> {\r\n        self.apply_feed_flips(origin_node, updates, policy).0\r\n    }\r\n\r\n    /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention\r\n    /// flips** (D9-2, REQ-INST-3): the endpoint ids whose admitted update was\r\n    /// an observed *transition to `Active` on a peer node* — the wire signal\r\n    /// that another instance of the id just became the driven one (its\r\n    /// daemon advertises transitions epoch-bumped), which rests a local\r\n    /// instance of the same id warm (CONTEXT: driving `ling@laptop` makes\r\n    /// `ling@desktop` dormant).\r\n    ///\r\n    /// Flip = `Updated` merge where the stored row's status was NOT `Active`\r\n    /// and the incoming one is. Deliberately **never `Inserted`**: this\r\n    /// registry is in-memory, so a daemon restart re-inserts every peer row\r\n    /// at its *current* status — counting inserts would knock local\r\n    /// instances dormant on every restart; and never the periodic\r\n    /// re-advertisement (`Updated` with prev already `Active` is not a\r\n    /// flip). The lease has already dropped stale/equal epochs before any\r\n    /// of this runs.\r\n    // [impl->REQ-INST-3]\r\n    pub fn apply_feed_flips(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[RegistryUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> (Vec<RegistryApplyVerdict>, Vec<String>) {\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut flips: Vec<String> = Vec::new();\r\n        let mut admitted_any = false;\r\n        let verdicts = updates\r\n            .iter()\r\n            .map(|u| {\r\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\r\n                    return RegistryApplyVerdict::NotMember;\r\n                }\r\n                // [impl->REQ-MESH-5]\r\n                if !policy.roster.is_member(&u.subnet, origin_node) {\r\n                    return RegistryApplyVerdict::UntrustedOrigin;\r\n                }\r\n                admitted_any = true;\r\n                let reg = regs.entry(u.subnet.clone()).or_default();\r\n                let prev = reg\r\n                    .instances(&u.endpoint_id)\r\n                    .iter()\r\n                    .find(|i| i.node == u.instance.node)\r\n                    .map(|i| i.status);\r\n                // The epoch lease (4.8) decides; an own-row echo from a peer\r\n                // is dropped Stale by construction (our epoch is newer or\r\n                // equal — never authored elsewhere).\r\n                let outcome = reg.merge_instance(&u.endpoint_id, u.instance.clone());\r\n                if outcome == MergeOutcome::Updated\r\n                    && u.instance.status == Status::Active\r\n                    && prev != Some(Status::Active)\r\n                    && u.instance.node != self.node_hex\r\n                    && !flips.contains(&u.endpoint_id)\r\n                {\r\n                    flips.push(u.endpoint_id.clone());\r\n                }\r\n                RegistryApplyVerdict::Merged(outcome)\r\n            })\r\n            .collect();\r\n        drop(regs);\r\n        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin\r\n        // node is up right now — even a Stale merge verdict is a liveness\r\n        // signal (the lease judged the row, not the sender). Refused feeds\r\n        // stamp nothing (an untrusted sender earns no liveness credit).\r\n        // [impl->REQ-SUBNET-1]\r\n        if admitted_any {\r\n            self.heard\r\n                .lock()\r\n                .unwrap()\r\n                .insert(origin_node.to_string(), wall_ms());\r\n            self.stamp_admit();\r\n        }\r\n        self.write_snapshots();\r\n        (verdicts, flips)\r\n    }\r\n\r\n    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME\r\n    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an\r\n    /// untrusted origin is refused; an admitted update merges via\r\n    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the\r\n    /// origin's gossip-recency (an admitted label feed proves the peer is up,\r\n    /// exactly like an instance feed). The endpoint-less carrier: this is how a\r\n    /// peer with zero endpoints gets its hostname into our `--nodes` view.\r\n    // [impl->REQ-SUBNET-3]\r\n    pub fn apply_node_labels(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[NodeLabelUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> Vec<RegistryApplyVerdict> {\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut admitted_any = false;\r\n        let verdicts = updates\r\n            .iter()\r\n            .map(|u| {\r\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\r\n                    return RegistryApplyVerdict::NotMember;\r\n                }\r\n                // [impl->REQ-MESH-5]\r\n                if !policy.roster.is_member(&u.subnet, origin_node) {\r\n                    return RegistryApplyVerdict::UntrustedOrigin;\r\n                }\r\n                admitted_any = true;\r\n                let reg = regs.entry(u.subnet.clone()).or_default();\r\n                RegistryApplyVerdict::Merged(reg.merge_node_label(\r\n                    &u.node,\r\n                    u.label.clone(),\r\n                    u.epoch,\r\n                ))\r\n            })\r\n            .collect();\r\n        drop(regs);\r\n        if admitted_any {\r\n            self.heard\r\n                .lock()\r\n                .unwrap()\r\n                .insert(origin_node.to_string(), wall_ms());\r\n            self.stamp_admit();\r\n        }\r\n        self.write_snapshots();\r\n        verdicts\r\n    }\r\n\r\n    /// Record an admitted inbound feed (any origin) for the health surface.\r\n    // [impl->REQ-PUMP-STAGE-TRUTH]\r\n    fn stamp_admit(&self) {\r\n        self.last_admit_ms\r\n            .store(wall_ms(), std::sync::atomic::Ordering::Relaxed);\r\n    }\r\n\r\n    /// Wall-clock ms of the last admitted inbound registry/label feed, if any\r\n    /// this process lifetime (ADR-0039 Decision 5 status field).\r\n    // [impl->REQ-PUMP-STAGE-TRUTH]\r\n    pub fn last_admit_ms(&self) -> Option<u64> {\r\n        match self\r\n            .last_admit_ms\r\n            .load(std::sync::atomic::Ordering::Relaxed)\r\n        {\r\n            0 => None,\r\n            ms => Some(ms),\r\n        }\r","truncated":true,"truncatedBy":"lines","totalLines":2040,"totalBytes":7286,"outputLines":165,"outputBytes":7286,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    /// otherwise issue the same id from independent counters. (Field bug: the\r\n    /// pump minted consent notif_ids from the stream-open op counter\r\n    /// `pump-ops.json` — a different file — so on a node whose two counters\r\n    /// were both low the consent notif and a presence/notify notif collided on\r\n    /// `node:1`. The rig's presence-redirect rung caught it: `UNIQUE\r\n    /// constraint failed: notifs.notif_id`.) Locks the shared cursor so the\r\n    /// dispatcher's registry arm and the pump never double-mint.\r\n    // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]\r\n    pub fn with_epoch<R>(&self, f: impl FnOnce(&mut EpochSource) -> R) -> R {\r\n        f(&mut self.epoch.lock().unwrap())\r\n    }\r\n\r\n    /// Apply one inbound feed under the gate. `origin_node` MUST be the\r\n    /// handshake-proven stream-table origin, never payload bytes.\r\n    // [impl->REQ-INST-7]\r\n    // [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n    pub fn apply_feed(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[RegistryUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> Vec<RegistryApplyVerdict> {\r\n        self.apply_feed_flips(origin_node, updates, policy).0\r\n    }\r\n\r\n    /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention\r\n    /// flips** (D9-2, REQ-INST-3): the endpoint ids whose admitted update was\r\n    /// an observed *transition to `Active` on a peer node* — the wire signal\r\n    /// that another instance of the id just became the driven one (its\r\n    /// daemon advertises transitions epoch-bumped), which rests a local\r\n    /// instance of the same id warm (CONTEXT: driving `ling@laptop` makes\r\n    /// `ling@desktop` dormant).\r\n    ///\r\n    /// Flip = `Updated` merge where the stored row's status was NOT `Active`\r\n    /// and the incoming one is. Deliberately **never `Inserted`**: this\r\n    /// registry is in-memory, so a daemon restart re-inserts every peer row\r\n    /// at its *current* status — counting inserts would knock local\r\n    /// instances dormant on every restart; and never the periodic\r\n    /// re-advertisement (`Updated` with prev already `Active` is not a\r\n    /// flip). The lease has already dropped stale/equal epochs before any\r\n    /// of this runs.\r\n    // [impl->REQ-INST-3]\r\n    pub fn apply_feed_flips(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[RegistryUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> (Vec<RegistryApplyVerdict>, Vec<String>) {\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut flips: Vec<String> = Vec::new();\r\n        let mut admitted_any = false;\r\n        let verdicts = updates\r\n            .iter()\r\n            .map(|u| {\r\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\r\n                    return RegistryApplyVerdict::NotMember;\r\n                }\r\n                // [impl->REQ-MESH-5]\r\n                if !policy.roster.is_member(&u.subnet, origin_node) {\r\n                    return RegistryApplyVerdict::UntrustedOrigin;\r\n                }\r\n                admitted_any = true;\r\n                let reg = regs.entry(u.subnet.clone()).or_default();\r\n                let prev = reg\r\n                    .instances(&u.endpoint_id)\r\n                    .iter()\r\n                    .find(|i| i.node == u.instance.node)\r\n                    .map(|i| i.status);\r\n                // The epoch lease (4.8) decides; an own-row echo from a peer\r\n                // is dropped Stale by construction (our epoch is newer or\r\n                // equal — never authored elsewhere).\r\n                let outcome = reg.merge_instance(&u.endpoint_id, u.instance.clone());\r\n                if outcome == MergeOutcome::Updated\r\n                    && u.instance.status == Status::Active\r\n                    && prev != Some(Status::Active)\r\n                    && u.instance.node != self.node_hex\r\n                    && !flips.contains(&u.endpoint_id)\r\n                {\r\n                    flips.push(u.endpoint_id.clone());\r\n                }\r\n                RegistryApplyVerdict::Merged(outcome)\r\n            })\r\n            .collect();\r\n        drop(regs);\r\n        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin\r\n        // node is up right now — even a Stale merge verdict is a liveness\r\n        // signal (the lease judged the row, not the sender). Refused feeds\r\n        // stamp nothing (an untrusted sender earns no liveness credit).\r\n        // [impl->REQ-SUBNET-1]\r\n        if admitted_any {\r\n            self.heard\r\n                .lock()\r\n                .unwrap()\r\n                .insert(origin_node.to_string(), wall_ms());\r\n            self.stamp_admit();\r\n        }\r\n        self.write_snapshots();\r\n        (verdicts, flips)\r\n    }\r\n\r\n    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME\r\n    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an\r\n    /// untrusted origin is refused; an admitted update merges via\r\n    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the\r\n    /// origin's gossip-recency (an admitted label feed proves the peer is up,\r\n    /// exactly like an instance feed). The endpoint-less carrier: this is how a\r\n    /// peer with zero endpoints gets its hostname into our `--nodes` view.\r\n    // [impl->REQ-SUBNET-3]\r\n    pub fn apply_node_labels(\r\n        &self,\r\n        origin_node: &str,\r\n        updates: &[NodeLabelUpdate],\r\n        policy: &RegistryGatePolicy,\r\n    ) -> Vec<RegistryApplyVerdict> {\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut admitted_any = false;\r\n        let verdicts = updates\r\n            .iter()\r\n            .map(|u| {\r\n                if !policy.subnets.iter().any(|s| s == &u.subnet) {\r\n                    return RegistryApplyVerdict::NotMember;\r\n                }\r\n                // [impl->REQ-MESH-5]\r\n                if !policy.roster.is_member(&u.subnet, origin_node) {\r\n                    return RegistryApplyVerdict::UntrustedOrigin;\r\n                }\r\n                admitted_any = true;\r\n                let reg = regs.entry(u.subnet.clone()).or_default();\r\n                RegistryApplyVerdict::Merged(reg.merge_node_label(\r\n                    &u.node,\r\n                    u.label.clone(),\r\n                    u.epoch,\r\n                ))\r\n            })\r\n            .collect();\r\n        drop(regs);\r\n        if admitted_any {\r\n            self.heard\r\n                .lock()\r\n                .unwrap()\r\n                .insert(origin_node.to_string(), wall_ms());\r\n            self.stamp_admit();\r\n        }\r\n        self.write_snapshots();\r\n        verdicts\r\n    }\r\n\r\n    /// Record an admitted inbound feed (any origin) for the health surface.\r\n    // [impl->REQ-PUMP-STAGE-TRUTH]\r\n    fn stamp_admit(&self) {\r\n        self.last_admit_ms\r\n            .store(wall_ms(), std::sync::atomic::Ordering::Relaxed);\r\n    }\r\n\r\n    /// Wall-clock ms of the last admitted inbound registry/label feed, if any\r\n    /// this process lifetime (ADR-0039 Decision 5 status field).\r\n    // [impl->REQ-PUMP-STAGE-TRUTH]\r\n    pub fn last_admit_ms(&self) -> Option<u64> {\r\n        match self\r\n            .last_admit_ms\r\n            .load(std::sync::atomic::Ordering::Relaxed)\r\n        {\r\n            0 => None,\r\n            ms => Some(ms),\r\n        }\r\n    }","startLine":199,"lineNumbers":[199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2238,"totalBytes":7286,"outputLines":165,"outputBytes":7286,"shownRange":{"start":199,"end":363},"nextOffset":364}}},"isError":false,"timestamp":1784271660628}}
{"type":"message","id":"de09d818","parentId":"4998ec68","timestamp":"2026-07-17T07:01:00.647Z","message":{"role":"toolResult","toolCallId":"call_yJn9eN3363O0L9yQ6EC5eqkt|fc_089095e18118ee7d016a59d32c7fbc8190ba8256e2fcceb531","toolName":"read","content":[{"type":"text","text":"    pub fn net_open_stream(\n…\n    ) -> io::Result<NetStreamOpened> {\n…\n        loop {\n…\n        }\n    }\n\n    /// Write bytes to a held stream over the broker (D4b). With `op_id` the\n    /// write is exactly-once and this blocks for the `net-sent` ack (returned);\n    /// without one it is a direct fire-and-forget write (`None` returned).\n    /// `finish` closes the send side after the write.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_stream_send(\n        &mut self,\n        stream_id: u64,\n        bytes: &[u8],\n        op: Option<MintedOp>,\n        finish: bool,\n    ) -> io::Result<Option<NetSent>> {\n        self.send(\n            KIND_NET_STREAM_SEND,\n            serde_json::to_value(NetStreamSendReq {\n                stream_id,\n                data_b64: encode_bytes(bytes),\n                op_id: op.map(|o| o.seq),\n                minter: op.map(|o| o.minter),\n                finish,\n            })\n            .expect(\"NetStreamSendReq serializes\"),\n        )?;\n        if op.is_none() {\n            return Ok(None);\n        }\n        let want_seq = op.map(|o| o.seq);\n        let deadline = self.call_deadline();\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n…\n        }\n    }\n\n[Showing lines 1394-1438 of 2247. Use :1439 to continue]"}],"details":{"truncation":{"content":"        }\n    }\n\n    /// Write bytes to a held stream over the broker (D4b). With `op_id` the\n    /// write is exactly-once and this blocks for the `net-sent` ack (returned);\n    /// without one it is a direct fire-and-forget write (`None` returned).\n    /// `finish` closes the send side after the write.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_stream_send(\n        &mut self,\n        stream_id: u64,\n        bytes: &[u8],\n        op: Option<MintedOp>,\n        finish: bool,\n    ) -> io::Result<Option<NetSent>> {\n        self.send(\n            KIND_NET_STREAM_SEND,\n            serde_json::to_value(NetStreamSendReq {\n                stream_id,\n                data_b64: encode_bytes(bytes),\n                op_id: op.map(|o| o.seq),\n                minter: op.map(|o| o.minter),\n                finish,\n            })\n            .expect(\"NetStreamSendReq serializes\"),\n        )?;\n        if op.is_none() {\n            return Ok(None);\n        }\n        let want_seq = op.map(|o| o.seq);\n        let deadline = self.call_deadline();\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {","truncated":true,"truncatedBy":"lines","totalLines":854,"totalBytes":1674,"outputLines":45,"outputBytes":1674,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn net_open_stream(\n…\n    ) -> io::Result<NetStreamOpened> {\n…\n        loop {\n…\n        }\n    }\n\n    /// Write bytes to a held stream over the broker (D4b). With `op_id` the\n    /// write is exactly-once and this blocks for the `net-sent` ack (returned);\n    /// without one it is a direct fire-and-forget write (`None` returned).\n    /// `finish` closes the send side after the write.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_stream_send(\n        &mut self,\n        stream_id: u64,\n        bytes: &[u8],\n        op: Option<MintedOp>,\n        finish: bool,\n    ) -> io::Result<Option<NetSent>> {\n        self.send(\n            KIND_NET_STREAM_SEND,\n            serde_json::to_value(NetStreamSendReq {\n                stream_id,\n                data_b64: encode_bytes(bytes),\n                op_id: op.map(|o| o.seq),\n                minter: op.map(|o| o.minter),\n                finish,\n            })\n            .expect(\"NetStreamSendReq serializes\"),\n        )?;\n        if op.is_none() {\n            return Ok(None);\n        }\n        let want_seq = op.map(|o| o.seq);\n        let deadline = self.call_deadline();\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n…\n        }\n    }","startLine":1373,"lineNumbers":[1373,null,1377,null,1388,null,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,null,1444,1445]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2247,"totalBytes":1674,"outputLines":45,"outputBytes":1674,"shownRange":{"start":1394,"end":1438},"nextOffset":1439}}},"isError":false,"timestamp":1784271660647}}
{"type":"message","id":"18da1dee","parentId":"de09d818","timestamp":"2026-07-17T07:01:00.652Z","message":{"role":"toolResult","toolCallId":"call_wmvN2R8x90RHdsKKPnJCVY2d|fc_089095e18118ee7d016a59d32c7fa88190ba9bcb9643a9c80a","toolName":"grep","content":[{"type":"text","text":" 220|    ) -> Vec<RegistryApplyVerdict> {\n*221|        self.apply_feed_flips(origin_node, updates, policy).0\n 222|    }\n 223|\n 224|    /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention\n...\n 240|    // [impl->REQ-INST-3]\n*241|    pub fn apply_feed_flips(\n 242|        &self,\n 243|        origin_node: &str,\n 244|        updates: &[RegistryUpdate],\n...\n 288|        if admitted_any {\n*289|            self.heard\n 290|                .lock()\n 291|                .unwrap()\n 292|                .insert(origin_node.to_string(), wall_ms());\n...\n 299|    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME\n*300|    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an\n 301|    /// untrusted origin is refused; an admitted update merges via\n 302|    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the\n 303|    /// origin's gossip-recency (an admitted label feed proves the peer is up,\n...\n 335|        if admitted_any {\n*336|            self.heard\n 337|                .lock()\n 338|                .unwrap()\n 339|                .insert(origin_node.to_string(), wall_ms());"}],"details":{"scopePath":"crates/spt-daemon/src/registryhost.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/registryhost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":5,"fileCount":1,"files":["crates/spt-daemon/src/registryhost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/registryhost.rs","count":5}],"truncated":false,"displayContent":" 220│    ) -> Vec<RegistryApplyVerdict> {\n*221│        self.apply_feed_flips(origin_node, updates, policy).0\n 222│    }\n 223│\n 224│    /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention\n    │...\n 240│    // [impl->REQ-INST-3]\n*241│    pub fn apply_feed_flips(\n 242│        &self,\n 243│        origin_node: &str,\n 244│        updates: &[RegistryUpdate],\n    │...\n 288│        if admitted_any {\n*289│            self.heard\n 290│                .lock()\n 291│                .unwrap()\n 292│                .insert(origin_node.to_string(), wall_ms());\n    │...\n 299│    /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME\n*300│    /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an\n 301│    /// untrusted origin is refused; an admitted update merges via\n 302│    /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the\n 303│    /// origin's gossip-recency (an admitted label feed proves the peer is up,\n    │...\n 335│        if admitted_any {\n*336│            self.heard\n 337│                .lock()\n 338│                .unwrap()\n 339│                .insert(origin_node.to_string(), wall_ms());"},"isError":false,"timestamp":1784271660652}}
{"type":"message","id":"8713baad","parentId":"18da1dee","timestamp":"2026-07-17T07:01:00.653Z","message":{"role":"toolResult","toolCallId":"call_88mbR1VXNyy8mpiOf7zdKecH|fc_089095e18118ee7d016a59d32c7fa0819080307e73e4e6094a","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":1784271660653}}
{"type":"message","id":"15140d28","parentId":"8713baad","timestamp":"2026-07-17T07:01:02.783Z","message":{"role":"toolResult","toolCallId":"call_kdnyImfStY0iFNd0dzbclHDW|fc_089095e18118ee7d016a59d32c7fc4819088c7e23d45688783","toolName":"grep","content":[{"type":"text","text":"# .claude/\n## todlando-commune.md\n 27|- nethost: SubscriberSeat (bounded queue 1024 + writer thread, halt-at-first-write-failure, poisoned/done flags) in StreamLog + PresenceLog; begin_attach snapshot + replay-enqueue OUTSIDE mutex; drop_seat (poison cancels StreamLease); send_stream refuses canceled lease; POISON-only reinstall refusal (healthy displacement stays legal — blanket refusal TIMEOUTed applyhost, learned hard).\n*28|- dispatch: is_unsupported_verb narrows Err(_) fallback; classify_failure + CircuitBreaker (2s doubling cap 30s, global, reset on Served); outcome_transition SeatBlocked = no-budget-burn breaker-paced retry; MAX_DISPATCH_WORKERS=8 pool; dispatch_event keyed telemetry (gen/stream/family/attempt/conn) + WorkerReport{outcome,family,endpoint} + line_endpoint; pre-serve terminal exclusion ('no such stream' → Served terminal).\n 29|- gen tokens (fix 6): AttachRecord.gen (client mints now_ms) → SubscribeReq/UnsubscribeReq.gen → ControllerSink.attach_gen; same-identity re-take REFUSED when gen!=0 && incumbent!=0 && gen<incumbent (equal = reconstruction OK, 0 = legacy/N-1); detach_if_gen stale never clears controller; serve_attach serve_gen → detach_session_gen.\n 30|</project-context>\n 31|\n...\n 35|(2) Read memory redispatch-stall-progress.md (BUILD CHECKPOINT + REMAINING TODO) — it is the authoritative state.\n*36|(3) Continue REMAINING TODO in order: fix-8 sessions clone-then-drop audit (broker.rs serve() end ~3211 + info.json-under-sessions-lock sites) → units (seat halt/remove per site; overflow-detach+cursor; breaker trip/gate/reset; is_unsupported_verb + classify_failure; dispatch_event key completeness; stale-gen refuse ×2) → ints tests/redispatch_stall.rs T1-T7 (pump.rs/mesh_recovery.rs harness; proving() mixed-pair blackhole = wedge; T7 real-traffic N-1) → HEAVY ×3 strings → preflight (redispatch...\n 37|(4) Box discipline: check CI/gate ownership before local suites; spt commands may auto-start the stopped daemon — fine.\n 38|!!wake!!\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":".claude, *.log, **/*.log","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":[".claude/todlando-commune.md"],"fileMatches":[{"path":".claude/todlando-commune.md","count":2}],"truncated":true,"displayContent":"# .claude/\n## todlando-commune.md\n 27│- nethost: SubscriberSeat (bounded queue 1024 + writer thread, halt-at-first-write-failure, poisoned/done flags) in StreamLog + PresenceLog; begin_attach snapshot + replay-enqueue OUTSIDE mutex; drop_seat (poison cancels StreamLease); send_stream refuses canceled lease; POISON-only reinstall refusal (healthy displacement stays legal — blanket refusal TIMEOUTed applyhost, learned hard).\n*28│- dispatch: is_unsupported_verb narrows Err(_) fallback; classify_failure + CircuitBreaker (2s doubling cap 30s, global, reset on Served); outcome_transition SeatBlocked = no-budget-burn breaker-paced retry; MAX_DISPATCH_WORKERS=8 pool; dispatch_event keyed telemetry (gen/stream/family/attempt/conn) + WorkerReport{outcome,family,endpoint} + line_endpoint; pre-serve terminal exclusion ('no such stream' → Served terminal).\n 29│- gen tokens (fix 6): AttachRecord.gen (client mints now_ms) → SubscribeReq/UnsubscribeReq.gen → ControllerSink.attach_gen; same-identity re-take REFUSED when gen!=0 && incumbent!=0 && gen<incumbent (equal = reconstruction OK, 0 = legacy/N-1); detach_if_gen stale never clears controller; serve_attach serve_gen → detach_session_gen.\n 30│</project-context>\n 31│\n   │...\n 35│(2) Read memory redispatch-stall-progress.md (BUILD CHECKPOINT + REMAINING TODO) — it is the authoritative state.\n*36│(3) Continue REMAINING TODO in order: fix-8 sessions clone-then-drop audit (broker.rs serve() end ~3211 + info.json-under-sessions-lock sites) → units (seat halt/remove per site; overflow-detach+cursor; breaker trip/gate/reset; is_unsupported_verb + classify_failure; dispatch_event key completeness; stale-gen refuse ×2) → ints tests/redispatch_stall.rs T1-T7 (pump.rs/mesh_recovery.rs harness; proving() mixed-pair blackhole = wedge; T7 real-traffic N-1) → HEAVY ×3 strings → preflight (redispatch...\n 37│(4) Box discipline: check CI/gate ownership before local suites; spt commands may auto-start the stopped daemon — fine.\n 38│!!wake!!","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784271662783}}
{"type":"message","id":"38b1bf52","parentId":"15140d28","timestamp":"2026-07-17T07:02:04.053Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying v0.36 tag and subscriber seat cleanup**\n\n**Identifying sender stream entry retention issue**\n\n**Linking stream scanning to high CPU and IPC load**"},{"type":"thinking","thinking":"**Clarifying I/O counter interpretations**\n\n**Identifying stream lifecycle leak in registry feed**\n\n**Linking stream growth to dispatcher clogging**"},{"type":"thinking","thinking":"**Analyzing broker thread behavior and connections**\n\n**Investigating thread count discrepancy and connection leaks**\n\n**Planning stream retirement and connection cleanup fix**"},{"type":"thinking","thinking":"**Assessing command retirement timing risks**\n\n**Examining stream ownership in register_stream**"},{"type":"toolCall","id":"call_f1gaGGaeLvuuY7gT4ga5Lpwq|fc_089095e18118ee7d016a59d36bf52881908cafe5f15fb21db9","name":"read","arguments":{"i":"Reading stream entry registration","path":"crates/spt-daemon/src/nethost.rs","selector":"760-850,980-1060,1600-1720"}},{"type":"toolCall","id":"call_FeJTs5WaJnmcxanoqAMAQKLd|fc_089095e18118ee7d016a59d36bf53c8190b8e24fc923bd646a","name":"grep","arguments":{"i":"Finding physical stream counts","pattern":"stream_count|streams\\.len|test_.*stream|stream_infos\\(\\).*len|retired","path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_Iy6sa7FBUnFoUoEOj8jKdB4m|fc_089095e18118ee7d016a59d36bf5448190a1ff5f862dd02d27","name":"grep","arguments":{"i":"Finding push feed API tests","pattern":"push_feed\\(|one stream per feed|fire-and-forget|finished on send","path":"crates/spt-daemon/src/pump; crates/spt-daemon/tests","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_kTp57LXQ3soIwIUsxEecP2JZ|fc_089095e18118ee7d016a59d36bf54c8190ad7edb00f5e6be89","name":"grep","arguments":{"i":"Finding stream removal semantics","pattern":"streams\\.remove|retain\\(\\|_, e\\| e\\.conn_id|retire_stream|retired\\.swap|finished.*remove","path":"crates/spt-daemon/src/nethost.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":9513,"output":2036,"cacheRead":211456,"cacheWrite":0,"totalTokens":223005,"reasoningTokens":1719,"cost":{"input":0.047565,"output":0.06108,"cacheRead":0.10572799999999999,"cacheWrite":0,"total":0.21437299999999998}},"stopReason":"toolUse","timestamp":1784271662828,"responseId":"resp_089095e18118ee7d016a59d32f0ae48190bd58f304f60ffc9a","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d332ddc48190b60408022db84e32","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdNDeX0PBKbVhquT_UYgN68uPpbxzkpviTzd-ommiWO70WbumLnnYURqBNjsJoB4Qo_ZUwOiAHqunlfNyOG-Hk5twLGciZlg8yx_teFuqFCYiWxk3wfQcVm0b6YXZ096GPkPo3HNreLbnPv5V3icjs_-lqGhr2BONkjGY_-x5yDYOJnl-qwOpxZ93btqg9mEEbpFhmHeZ65Wd67Jo6dHAiojSpUzroJgT5Z9cAru_Z-459Dx8h1PRgrH3Zo0VF4m97BvLuMCbG-D8W4GFIxfYqml9lYNpyc6Br4Cx3QOLaL5R_JlFILh9CPX7HTIalMZyKTKs6shXFhKZ5frYEFyMAcRDQL25ZEyGvrr81QQH6A0vi7a4KL4gnwuLbxfGVt7vGw5IG97sVZi74JEKl0XnnOIjWiQdZpn6Wg_P4dIUGqQ9fFTeVOepyEyTwfmOzp_2jBfiGW0zf23ZH89PD7lpkji3PCL7I5wLstbz3MdZVpFflSjKJJf4nx2evS2HL57zaLWcgPCUL_XQGgZdZmzGgIEPrMCIhFzWReHy-CxLhIGcVsY-Eahr_ibsyFfjh3-BtSqwsi9PRF2WchRZCRFBvvwkm0xniCkMtCVMKPmavl4ip2fDYzIUnISEKjJ7Yq3kvkeOrdjas0xyPZIpt8Twuts0W2z9zIUFYv9LDj6xb9CjzdIuY3ezPfyr2JRwhPMy9W5IiexEq1uf6l9YgG2B6c-284C2EsYzNuNdFvyot_qeholNp0l9Hyg9I7Yk2lQHc-ToU0oWmg0ty2xsuZ_UJSE-b-UUu2B4yPvp8PigbJTPhkwlgul4t9uoXM6zXwdUkjAOKrm7DGrYfCyzHnQO7K8KwbHnSokZz9pkp4wuXWYU2tDktben8l0-7H17WjxjEPM_U3al4tmSpd9ikXwbVmGUE-5LhoyB1t0tpqCu8_sIaRpfngN-y-sum4uO9i50xrX7Jn3EZThUbumAOkrj4kbAhxVy5EAwC8t5t0yX8teeB4YPfdljYtVnAQomOR4oHwjsMZbiBJxLgZrgNs9kHLXR9K55LFHO-R-0_5f6nU2MHYXokR37LWKqegbPJyDMmi7cYn_2pkBFJk7reuBO7ZtW_EunoR_bETMpZQqvwTyK698cd79oXox1cbxOU3qJb0g0dXdPbQUi1mLpJdzwgcNmAIQKBfMcib_cl5-dB7T8bqJNMihO5RUNTT_acL6cb4yABZuApLRYKoAdt24Kv42hhtDjC2SxouQUagfF9mOONVYx1HYPH9omS-iUfsQ6Ck7-lHEUP0ZrCjpxMKWEMCKP7GABKth4XG_Qg0BuboPiI9BjIe-U-7m2L9j6pNQtKq01oGlrsp9PwXZRT3y1fc4f6IvWE8ZWt7mu_07L4dCCiXO7NzqxuZl91EFQSQLVHR9fC_JTropfn0032GL3oRbrV7m0978yWPI9nI3wis-5o-IENgUDyu9ebXhLHbZcu5bnJFUTxFbGCU0IjDbJXB3JAdWoN1BxmO_2wzLofZa5mXkfJmSICpZaud_Sd99RDfIumAiE8m5qvPNnRQ1J5ENE-6T_QxGkOzlBf51MCm1RsNpeQlLnt2JVYtJegtkk-nftuW-2pNwVltRTO4JIGWlTi7S1rPoK_MmsmixWKPNYm4DvgHBHR8XJr9Py86ORz-jJFotokxNMOn2LEnyz3pqeyZoM_aVfS1wixijlB1D3PZfBu3JcexGKZnpxbg-biyWLhCEFnRF2S_NppsQE-nijgUEz3mYGRf6rfkW-NXNy4noelTbjFky3bxrD46Ocq-HotDl5SmvomCDntIOlS4ihCMW6BgNaEfbi1dDE9qdQzThvH0Q4miS1f4beWuqhLaTrUYVT1yMmbWPpxlyigzU1wlslLXYkrlS70tz4p1Urrqb-T8E1cvqonljeGpCOeoCpbCFnsxCWq7Rgk_dZBWZWRoKt6IKxC-5I4-aGl6GHHC985cuQLCsOefelI82JY70OgYvLXzhUJqEwv-lREKi6qo20Gm0x7XVsfGTEshnjQq5ZfmRofKpmcs1hSBsPTnPohSVXdRtkvSOtS83Iu9a1whvkcCxRrg7rl4LD4ukII8YeHjWfz8Pd0LIoTEW094dvnb99E5d9rNgkbzzG3D98xFBv7IPoF3g9W0ZvCcqlEPO0_C2EZYJuoiwnf5T2za2HrNFajMz7-zRaeeTgzA1BubSKP-JrxqSSLafCwesFTt32WQr4BjtIaJCWJaFwre4rjOFgMYL-PTHpVXMfiidhvVcfD0Ice4KxujoAggGld5NaM8rG3V77H9Oi8aQjzBBFEMeAuL8GdSQ4idDv-jv59x1mZbYwevhWf3nNbhLSwRefsGwwYmKWEQA5JXS4sWHAv6-w660hZb86KacE4tQJFP6dEMjGhVIc4-2FALE-1m_tn4VyvgMRow1OoJvO5UNTHJg8XRbh9AuTQUZLCpEEYvJZcd_fHwopqn4Z1aDzk8hs-c7oMZNYuxxes3UKKKmXQIGjltIOfY2rCGqn3jTeEduNU8aiqKhleoaj8LKB9rKLNgaQ5VN22Jkdac0gIGvimZ47gFnKxMYs7srfPgpQj-MrKVaWNpy18L9TMul0Td_qJzXm-WWrSMIaVlr8zjFpOJJ_rkOzdEU7DsdftIb8nj0doAtkfLhUmmKhosimyp4Fij3SBI_CP1zQIGZVrYOfbKRfPxNBYx7KKgY_crUpHA-Z5wY068yuWBGnM0qX41iuQ5OL-JfbiB3yDq2_8KQmPgMj1dAWoqVwqAenjka6e5EbFKWxRHX85EUumF7xIdu_hp3_93gQ-hT8qyJz6UomSPo_PPl_AYfbdzkjpRyXEW0fikrdVFhepKdOA5gU1Em8YFQXhC2UCNNjQqXR-m-Cy9_rO5jRNVxa3fTYcU-Ty6hnNvFYxQQo0PRFvhfYn4LQkge1U5MckQDfAxpnDmEXgajv8FB4PWHmeqPXmm7F7bMtJaXqCiVg-wzoY8VbhSFZ5EXrdrdBW5zJi24EzNgMoUdXPcrAA2nIeiziZ7RX-K8ejOIjOmPH7juOwdSFHJqrkm5t4t3eO8Tl6obCMfOMBLeEVYru4gwROdTo__jpCTsjRSeTca0Engt7POZm2eoAVpzB09SZsAoufhbBCSHnttQ8SnX9j_TOpSjFhmoXXk_y2zo3RREWYq1t4kGSeUDAQBzuCxTqOIveU2hZ3sBSdRXr8buhuYt6JOtWM0B1DJVA7hX0scZ5srDdhVJChEnnr_bHickAvMulYxW6115uovKQ0dWIbI9QjW1JyC6m2jyogHigj3NwAmDCw0EB5-T3CdemUSLM4JhkVh3VFRwSvT26Y1XTJaqYtgQ4o1PJvmUUlgRptFHz-P6bpAhdHh4On90T01XlDu0_pDO4y3VzWfAncXj9CRajh_ddrRGQwYzyTtfwcXZitOhad2DotYB6EQLg2hAHoN_LcC6NBRzLtp2EMjvyv4Z4CxyXxlPzPkNCDO1FUzxdW9HxdPtIUsWIX5vN6ET6xUeo08BmxkwyYwd2KdyI3pma-P634a1eNUVuO793X-pSN_uRiYT01miNO5fhF3GxATnsI-CxfkhmRbqxymZUMuQ6D8vcNkbZlqXYAJxmTx7wbfCQu-vZipTCSciHU0ZXrOla4gWaHcIRXwPE6LWZdMkWnEULAsvJRL_uR2Y6ZOzl1X6ogHYTtAv-2a8qQXUDGbyoQUmmT1zzibTENZODqQCxbiKXzjufPN4EnHPoRbbXHL7SN7pgW561D9LcQ7mGYH72uC0qx1Tget-1APL91rspVNDWEa4U5tn6bNbYFyb2BD35w02U64oBNIMTiEPYv5e0GrOGDek4RJUmhhrF0k2nG3GACAkGH3775nyxu8pa-SnNtTo3ozGWEX63SV2oMDCmwqTQJRHxwD5xi-BnAZaXlGnA_RPEEUl6pSl-Mbmw85_V3q_I4msJsytdKQPCpV9N-QD4IQRyXgfTQqJ-b25HO4gSqkGfHG6WkW12NWQi9w4XJ1tSLYqI3OiAPy7wjw2ljqF_qO1-DdznVIejL3snjQs_DLplEbdsdZS9w6MHjEg3rrv01fTyxGw2CXWnV3Gj6cygNlND5AhBLKKUmHJQh36IQYTHElS2gNwuKgvfLduGtvsKFqnTn2gcum3Kay1rMxqfwFz6dIo_Xn4hKpaJgJQvTKLVA==","summary":[{"type":"summary_text","text":"**Verifying v0.36 tag and subscriber seat cleanup**"},{"type":"summary_text","text":"**Identifying sender stream entry retention issue**"},{"type":"summary_text","text":"**Linking stream scanning to high CPU and IPC load**"}]},{"id":"rs_089095e18118ee7d016a59d34355608190906dd4223c0eecb6","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdNQn0voLtsEKeOS3hV31k2rZwFwjByZYHJEHCZU6axKARYcOX85RAJgz96mAxgpsoNIU9fQTTcQY-YIok1CsqrafddAL6ZKvI05aLa2DJvZIBuFLr6W6DaGvatuWtAVGAAKvouXP12vr-o3UvwpxNtijeqB6shYOxo7HjbWwXjTlO4DeAm_wYTgYLi8tqluJSbBH3rKBTVI0ZfPvZQ_CBsCVhHAyPmgEqi-2ay49sxR7gHdI_F-DEzmm_T762V1s7ShVsTDlSAY7Q_uoxQ3QURE1cyhqb35yYrt73S8YASlkRpIhdgLCraq8vMfqyGD5ZjxZ7TJSIod2sNsrvgPFAoWyxH4RZp0l5F5FLFoLYuM08gfRyuXR4x6cN6J9GvdPPYN_FEGgb2BpA2h7Wstjijg0xMB47d5uWVWtMGG5kiYMb88LihFilkTQd4YKzTOAMiNQNT4UWl_iHM13p2_OUXbcGYDnYgCSP6DU0Vzp8DXyTGLAcFxLPpW9KHomHT090qICPSI1ilXO_mU80f7yBAJK7LQyKWSnw6mddLW1KrCSud31hSoTm7vM2IUWZ2TnQjOALv6YRCO28K3gkzRxPAdwm40ELDgv5Y1GK6iJRVaY3-E8PdkEdYCz016XZgVS9zKxlvvxTQkxE24SogjujH2gkDAzSddSKRjjHdVSnZN5OxfYpkFoSbGszbq-F12SnEJgOgMFUsX-HjfZXRvlke7vnUYFKIaYvscz5E57wer7jfFl-fhq0dxnKySoK7mqguhHIXG0Xhm09YrCtGZ-NpJVO6sX8UYOt8WdFcmN3LsLEqJoqNkMYJmgN7cqi7e_hUH398Papp19GZwYperCI2naPgjau3B0YOYBWv2MOM_PLBX-47Z7mwo8kEr9o-E28goZDwLBRHnLFLb6jFdrcat9lLH9o-M9JnZ8j6B2NLiVXXRChdcUiZ9sjO2QjIrKR2BaeZ6-L1uTncm_CjbPIGpls1ZhKSaF8RrIpWpAC3Fj53SS9fJZxnMhub66_AHzXWF2PG98A8z2qtlV7s1xzv1sQa89XRNLqzRYCu4Rdx3QyGoGqnGcHbIY-ub9FOCtQMmDb8QgNOPtxYk4ICV6PJM2qNtQpd9PXbOKaua53ktfqFIAqiKUzgUD1neWMY7HgWehSY21h88AIduJ1LRFsGQzdRn9VgTpSyVyueDyNDfxj0UADMK6233eddpSPb1iTGzYvi_3i4VIu5kqRQaLG5h_ji8DlRv7b5nkqaLaWtqNVcuoVVdT5Oo8BWdiMjcsCYPrZ2Ci6BkT34eAdml5d8u9C8rTVuENpRsfxCiufBGBRtwLa1Ctnq5MLj3GIPUkBTmL2n5Tz2bLd4HZZAkYXCMjGdTxOkZgf67YUJ-cGUSdfSy4GPVPiU6UZhfU2LKky6ee7bSnbDmh4qKTV_79dY6z3zq6IVhPUUz3PCdi17zdV7xMuehyEUGhKhulVbVWnBswgg7OWw286EPgmBTnD5-ZMAP_USf66Oxf4zs0mcSMqiIb1j3Kc8LynpIoh8ZcYEJHYI11hDxewPKmfRHybUak7YAsYysgRkKqVX5pfJbk0JDWQLGaFgswgdOlkuo8snCbJ3nrVsaWEJMlEk_Oy5zcTGARHWwP58jeDAcUEam_0ex4U3FG7bDelOqEUB1bzr7U7NfCrLKYI_zjRV1Qd9dWGpJ-aNHAnvsTvjlOY1MfDz6f2A1b1xXsNVkZ7I_DD7zhM8sSX-EPKepEd1K79iq_G8oVrNYJLB9oTbBSVsxXihPG4ZX0f8YMIS7dWhGoMORqKoISc9iS4pz9knV_Mmxr9A002NqbdAv2h6to7l98oycUj2lfFKu6eQ1k0kvznzwhVKN5QNaVoLeUYcAz2KPRziaRMM2Cky6hBRaJOCpgT8Mlh7ikjf9tUlfUuXFYoBwUgTJIS_OCd6jbHW9wMJUpbHicLvrMxBgDelGVUPnETBl5sFomcNuIDOAPNLMv8UXl-6d313xRYqbZzqydYT3ZZR_vVT3mP7LLpwAwGKZ-0SvYjnFUXJ5L1L4yO3dLb2q0zY0kJ_rbKj9lNG_hzzN1sBeGpC8c6MHDa_7EH_jniACtMsixR4rNdN0HvSsGyh_2sUpTXh1buTYHAotBVttyLLP5CCpT4tTQo5R7FTz1v6SYen5Ym-Gzooo0GQWnXx9UfaO29PyGXDmJWD8tNnwYbPZtWRYVCppAvtaw0GNteBCwGyr1lV4j_dh8Nc-bR3G7Qo2gb2Tiqjgh2V7VHR6AYAn6Ym37nFEcz42Aa_8DEzxqiuaqszUJOh9oISLXQSTMEeygFWCDRfPiUDr4rykgvkMH4xCyFGOlO9Fat3xWktUYX6lEtr3GwKK4EcPkCe0p-0t0O_UbeQ3E8kHk_mt00B7UZQ_mMJs86jsIJDkV3HoOJJWEa3xShsb1SbKaSsA80WjiPxqcnHY2jTJ8KywKYikLY_skP8xwc4Y3MK5qaBAsK5t5Td2lGwoWe3tHpkQK1tDj_3kf21hXeWfKKbWdMroxqHm-aiOePUATxYxoqtzWUZNmp8JQ9FIjIAc_DKY6Bx2I6awr9N5wykwe80zOZ6MPmZhGplbvLplItcsF4F9LqxOMtUfOwjlb0g3ZBkV5aabCvG_Wf61SdVKhDr5Fy--R0OjuivulzZxy2LmZVRsfPBu7p_vEAd5CeF4e4TjeIbCytdShwq2qtKMqP9SL6i7-B56Y9c8iFg7yNQtBkq4djqZ8ZRkqUo-_95MhKXOMP5Rwh1WXl5df0GAKCg9c7WuPv4Kmbx8YPMxEIRpK_5j2w3X9b9i8sew1q1DRk39OYuLJqTY_QpMxwfyAcq2PMOtLPNXPs52RbpdQawnU-VtwcSa4VNNOJD32Rk5K6SF6Nl1X0_8fkYiF6-fclZBKQpx37ekOFQrB0V-IheTx_IFRkT-4WN5zRN2YMfS8jsTt6ANkARz_qdSa6VbV9r-5RTMbKd60FmoC5CADYTbZfF4BwFKj2bdPuaCrPA2CZML4f7KJyHDy4zcKGcpFxGekDo-JTli8TvRpCmgf58AT_5-9RBr1o5YS1banY_BYg6DovTD9SxScMaVlOzLIgvtRWzhfTmesa6aWzQTuesHVnhT2jbU5FPROekCfJkXP8ufTe2LMgqgYC8IkXfXY8yalWcMXcp5em50CP2Xc2gdkiPJzbimHH6sps6JJMo6PwM15Kx8XjqHgDIieZkTqyMOKL9UwUxEbcABH66XFkrKTAMazk_gb9i1m-cYPg1TM4VZkr5rkOhWl8DkRdaLFa02ga9xhjEnbx_I9n9Do_k9L96XWZ7rYeafAjVqB9nrE7_vGaQwP4-7T4_IDiccfQ0RG2tNUy7J1I7Notm7Y7HzLfXVHiGNoBschBlwKWwxPqoNX8Fg-Rhi1zJuHKjZOLaLeZIxHUNxD4scUX_FL8TdVq0in5XY2kDI7xnWf-Wh_LoJyvB-FqkHQ_JgdVT-KhC6qffyRfke5ifPd23712x2S7JpyLUTgglzagCq2-OH12rdOgmW95_D49-uc6NyFCEAWMk13BsMdtYvkZDt6-UlJRWAs5qX6vWPxO85pt6YCLBSUwTeFkqem5Iu1BIFFCl9mE6Vqj7Cft0Hs1FSkgwvfd8TS5oPid97xLxrl_3HEm13ln47G2EEDz2fzxE2lTaOQE6F20NbSe8n5SJEIB13jCyf5bbdf_AIrhWJ-dYB67oi2WphGZ_F8YlpPxagu5yQ5iYdBPQTgwW0YUuc37ow8TvBDtMXCZwmtOyoVa8bGlh3dMO8jOb8LqL3TwXvRfKJeuIqNJ7t_gKbtZ9MMPejJJJVAf7doa-1FQLcr7Hab_cfUtGnAej671RChyoMEEVtg2C5wM5pBqDGD5VEjEkA06QY3AsuFZyDSJjztFF22V52EgoCRIASbCHk59I8jml93St2MgcdUmrumUQCkiF2PVLBPhDs-AA_pUMtrMiF_rrodTssH4AccEOcfZgAjax6avhbkTcymjthEg0MmtY7PyyZ8trYaXVjUQd8mDwn18jqDPMxL2HjCk00t7WyDQ-leZJa2MFOGsqkOgwb7ey8632k3HFRyROg1ZcSz2pA5owQA_4o95DC0d_k9-DPe_5vgJhuJj24XQIPAki-mDunnvyz5QK3Z3iyYhAF4HNsE9KFd4EMiCiuouYXkIC3M8_d6lYoqt5ovOMU0BmFSGT-eLBxdu_3tEIUv28lWzvgR8c4XjQxGtTF7YpZRbKE4PhoLMxzoTaVCOO4eFQdkjiQHyqTcrbc07ttEFZzLPKZCRbSiqYykunDWSWpXQ2PKkKc36hQ0dweXZm6XW6JVK7qbObw9jSqnJpBCBLx0zdFSNOtoHWF-SwYgHCEhHv2SgtJvg==","summary":[{"type":"summary_text","text":"**Clarifying I/O counter interpretations**"},{"type":"summary_text","text":"**Identifying stream lifecycle leak in registry feed**"},{"type":"summary_text","text":"**Linking stream growth to dispatcher clogging**"}]},{"id":"rs_089095e18118ee7d016a59d350e470819096803219241b11ec","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdNi5J1Fq0ohaOwZrFfw2FZk4peCM7FHNj21lvP0Gc9wvNgcfCJ3pC6kwnR25oDtJAYrLS56NMEIrg8vONEKLCe0RovwtLubfTSFYFyUOhmDfxnE9x0NMBoGauRGNeSxG-pgVqiuU1xya_8jDcd3ZO21TmgYz6FFk3AbUE1WBXSI-hNYdQOCTOvtnb-4uCAtXAY0AA4ihLX35E7W_IlX-Uf5MI6DqbWSoLe3YzpK2qPMq7Wp5yR_hoC1tDifcE9ckaUcwDmkJRuy93LpZpO9knzTU5MEuGqA_AiakUknD6IbjHlBjBzqK03wipJI_MwxMk3GcEx5CrO0w8S3hS5jpe28IqlwfbPWEJd8a9LMcqommVATcC7agRHmSXFYc8JyJwK3yR98cN7VTlNq-Xn7r3R-BwEmq0uGm69nWe4POe-woiKxG1dtGuBeqxAnAd5PSJaBGDR47RICx29UCfKJdW7dlfn_pAMJk5E5TpwhSF8CLCdftQNt-aesDz5zfN4DJ81o1mi1jkgCu3RiUCMHsfj8t22ysqTe3yHBkpeRIfSTjWV5L0CxCNToXaploidkQgR_5EUv1ou_vUdFcGd3DAPCvnseWJe0JWP9cdbqs4yA_De9UmzGWhGlBMK3kO42ZwKmJEub1sJEziwYwo13piyxVo1-9QsvWCwn6AgdiMgdP-6GSPZsLGH-lNgqOYegrc9zn1YXG9yZ_Zd8L-8QuLuhyKzSgIsFMpQtLKi_uo9JD4WlwD_aAMXboG5tv8Tdwn8bPaC1tGa66cbhyI46HJ39Je19HhDacnCziHNo2jh6GMnC9N5qaG53YYqrNphjWWNV4dhcUPCAePaNLsco4rpa76MYH41qSA1NS1xAzjMFYrRh6U2ZR7lWBV2WbkAZJFGXyC6EidrYlumBQzYoIionlCF47iKKZnoyUlYaJEepPrBQT5l_3iXjodMDOcxUgNGBE-lyIzIx5HGUhEWgl9-G8k4nRpxoX7Lr3XsNYybViH0MKzhgzvmkFvQ6QmeXpMzyFtESogLBlnmH7Z15qdcsyCWAucWykM_y6doV32LJHDXZIptZhf0HtlpHSUK2HVi8T_isb-rHeT8mztyZ2w7WmLvgKtv5G8F6RcPi0PqKO42BcJpDRVqIKsT3rQBbZlsk2OPTQZaFfh548-rZxhtEwUTtw06WT8PXOaxXh0cYb7l29jxO-j6a4_kIofsbD6XqPWu_LdAvV3WepRylptzAcHNRppVzLhUbbnbidI6MxnyuIxWQFOwEZLgukmxQTDF81Gvw2zTKtrp1se-bLZUPo1z_BMMqSp2R8xECzm-4E5KaAEzX_TCaT2q3Dts1k7kg_OgfMuvvSmBFV7LpxLjyfIzjJ3Pf_OU8nSRTzXtnYS_zwCI7fDFqnYUVM8Awot4mnmKnIpnWF0V339H82PlWOY4Eub4RNgivaMRoMn9YjQo_fNS8CIWS-Qd3xSBsuea4pzyFIMz8QyJV2pqmJX2khrxxehMZgS1ICEI0Dm1EM3_gLgaHad1tWYX9WlaKROOZpkt0iqXzQFDcb5ltwnABMLSaPAco06L68TOfpYt_vu4V8ZLEJbCup_9XXkS3c4SAkKHbPqcpzJzDfVAK8XyhnjHgLJxy4eeLKrTeXJP6vmCcsRQ3Gf73Xu-rvOtiPLxbTAnurCC5mqNAjpOaGxq2mp95wec6W_BjaDUkSckyCN6pSPyGmFS0ZvaKdnXWh2sEGJr1hNRJ1GIUpzIqUP_aKCz0ua0FXuCirp-9HIIyy7rnpwPabt2_hvFZGJqPHjta-GRQoFf91AMo_haS_StDPnnI9hU9HIw9qtUxzt0qsdoH4XFcmHRu2kbU59FiMATERX-N3DztdC1Hb92U4BYUMTYur73Bn4LE73Gvbd_G6Fob79xv8UJgYDabm1_XBF66YWF3JDMHiWmyXoimaU-_Hgc7-hIFkn8UVbsb2xaWh_cZ6kyZ8LrkDufXEdi42P5M2bOxmUX0DsKxsTvSOovg_57h73278_1rN7XM_hcGSeVsxGhNuEHroSIr0Nvrkzz44VGqwNgRwUUgAcdBK9EwFE5ilp3q-Yzjn-QGfso-37wD7LmKYtz9tIU2cQRr2cI5t1GpOfmAkK8HyY35quuLp-MDsSZyEaIHD6B3HS73pHsK6nj3A1SFrruWmtHDHTPcO02fXEUOFQb8TJbAk4jv6C2tYGpNeFXs3vvmMGyHIcLyPZgVMPHj_DE61u-VGv7nX0gbsjqtdLHRK128xHZXDQw5rK3CzT80bmrWK-ii69re4O1-OWEo52SMAHIXOQ3G82DVkmiGTLxT33chHcXxjuyoBqbogOTdXyvAGiHvt5WxdbNWPtEcaiRdGs93-IVQUZX3YYimnQmr5hs9ZnI6-YvZBgLkgEEk1zThR3-__w518Y0NH20d9OX1NlWlrO4PolqGeG5gwrdt0JEVrmMGNNt-M7pKuuY8UnBZpU160nsRklxvYI03eaCivSC4BgS6MoS-PrN9kvbTMz7aYy_BJc7TeJSURlw2gnN230FLcOnvrNrvFs04fn9EDVDIxb0orZEFUHKi_JjgoHXcpxkqRkhvL--dZAhsPdqHo3FU7ErqbuBo98rzqVTrpnKylrJY9Wtx4AZ0b1JluHQAjwYkHdrLP8sw9fkt0SRiwQU63ASSkue1EK0mA6KOJq5ySJ5ka39UeGaeDDYgFg3BqBhwgqhAbTkJx0cSWcsHCxya4dXiNyB2F2E9gfSU6uYA8o0_c_EUglGeGHotMo59a-l_I_wKabwkETAcNsN-IVQiytWy6mJl0u9WARM_3iT_BUN4f9nnkWayv83SycYlY10LRaZYXnOTO2_AW4EQIbJjASGABVP3VqVijSwD2fLgn92xccQ9UfP73X6sD_1HEQ_MAZs4kbTpq4-N5iAuUFJ4jGQTLgwMTgX90TSXZT59OtFXVnWb_NwhLSXqTeMBHLaueAsHtd_rHelKrxu80QerkSjn4Y7pa9dqsxYqw9JZI0eQ9BLOuTDLqqPFu9jvcXCVCRll6XHZzIFQKHltVwm_dWjtAFFPziGQnAyclLpUdmcE7Fecn_vK6cHT5LU7MvvaL632zZKC1BM1Z4_TI2_iWoxA2fDJ0Qnvrbro6XQ0P3AGq6FrpnJpQKOtJ_u6_dNOO-QXbIcg3_prgJXqO0uD4D2MQm-V4mXw4ypiBC55d9W6Pxo2zbmGr8otIYoi43BJC7vcLGKj-eUr-WxJdyneUJ7SWz4KELpE_7ZBckOF6rmT7AStb5bgt7Uux16FvbTC2HBhngIgyJNV_JCQrc0R_RmEncYP63jd2ocvXCaz2yIbwrf98aLrLLNal8zYPGIJxMj7VocpJtdAs2A3Qm5yQoZzqgrwKvkODwShfuMFTnPrzLk5ARJwyWredCr0XR9NFwrynAD0akxMGDHi5fcp_kvg3P9je2jh6Qn8IEa8B4Jxz1uaEyHuAKxsYnPr8MKcYSy80r63REEz7YZnNwoHNn9sA3YUdP16rRnshi36UjmHWH5hz9ijjwaVLYIy7RxRVL-4hwmrmRnuTAQFNohiAvKFMozaEA0AP47Z7MVABBtJAnFRNwo8v9PVrs2_2ZjNwc6wFoqk-1wnbR9X4y78EuvNUBNsw135BRhx_m3ioZEPutUfeRO5EV17e0oovoBLIW3Qz_nLvEXi9czB5KSPl3c6aQHZYZ8sd30yLMFLcB0M87A10Xh9lrhr0-WdaPRKJoTvx8SKxAriV2-xA37qXhquiLJdbZ51Dk7qycfdU-3Klb5eDk_GfG0kKxWS0omqaAne98_3Eeqqv0tqjOclcUU5OexKTo4FbasQg7oXEiLqkAML-wQvzes8noY8buBb110OlyjZ7Fq2B2dDcf-DaRWI3D3CwYyuV9GJjnDNFRnZaNKMsrRo87z4t07AbSqd_7h-zk7N27D_a7AhyyIDntSgEw8WJRyUR9cr_ghJMAmdR9TLjeCrSckzXEtVlgHMji5USmUofzwv263y0QALJLd0PNy_V2LkdcaSUzwxgs3ri8m4BzBQSj4zbGXjlLsIuQBHdX9pdTo1DlKG9GDKI5RMvsY6COqbzEJtmhmV_-_kaqbibQigsx6OoIfzxBFtpS8QhGGf43RMhwUokuzmMHYNBLimDfwN_Z4dDLNC_OsVTCyE09tUlIL5UOB70naf1gGOOrYRr5p7iBn7dgYJTkeeSmvU8kpohUmfGfGxksHJEoO18uA0RDFod-MmM42qr803xLxyZmY5l7asUhPvIuE6xnkHXUe9ELXmICEgVpcQzU4s_uiJzUrxg5uPtnW7EWNiSMKz1jYWvS_DBNlmUuuw3i_PkmeiBsc73O8zPj2hlPClmucxZcHIVINU0e35F2I832GB0al6Pq0Cq3qfLY2T2dF__xGt3mMa-ywOC_2SBtZPuRMl5YecephRqiyPpqyKRAkSgR1PqfTsOQFc_AXGBBAAQLWF_baJe_1FKOpJILoA7rOY9-kOiEZ1J_6UDg==","summary":[{"type":"summary_text","text":"**Analyzing broker thread behavior and connections**"},{"type":"summary_text","text":"**Investigating thread count discrepancy and connection leaks**"},{"type":"summary_text","text":"**Planning stream retirement and connection cleanup fix**"}]},{"id":"rs_089095e18118ee7d016a59d3624ba4819092fa236d4ec148b1","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdNrnq_jkIR24N_X-nmL6eOxDqXKDJV7vIWRfumwZqg8p__Edl6HPb_d1QvFiJgdhclVKc_Mavf3xAAprmTxYOx9tagHvDDILuLBMzcYBwC0TXHR-obytxED_PDCVDTO80YQc3Snynhpoqu_h7l93dEbWknjl-OfCJVKzDJ6Lqo64qNA10lWz_3jH49WTnKXw6KcndULPlEZ2RZOyjNIRfNR2rUqBy_Hdnvo02i8LBLchAjA-LgdYhl-RJQs4_kkmC5jqOrmrXo3-O_VDUDXt56SWHrIyMuVP0eJhRObVvj0DPFM3NnfvsaHbAyMqRNfqkbQEhK_4SPGkgFhJBHAgofJqCIb2cj_nQjOQ8Vj3SltCmoeRv9AVr3LvOMCHSxK7nNXBIT92J71g9DP0rmaryXofvizPkh1SBUB5AS5qhyt5aCl7OZMuf_dIKSZid1BNy2bG9mKg42o_O_kxl0ToFhEOgjCvo5SU0360FH1jFkEpZbTDAVEwMbEHrfYs1Idr8Ev-DFg14Wpq-UXEfASnOahlZ7SXV8kUAhAfL9gY2jpmBmeUfsJC3Xcf0hAHEt6_l6O3YGcUC-nS99L39jX4wOzxRUw74IklnEVivZf9cmILGDeI-3HY9USoFZM-mXoOONI0GiL4bYiEgHxuBTc12IahZLqNLo6wd_A3Xn4TckzcLYmUmlikogof8a9aBRBgL49uqG_Lp_ObEex2QC-JMUfM1b4tr5m92YX8_VyUE6NDIwGB5UHf-z0bG2-YZGR0dvwhoethmIJ37NZTOtD7W140LvxV1ABJ3jq3eZv8W_hpY_aI5i0ChttRb6tbufSna_3Qn1tdfRHx0L91xlfAEicz8NujcoxXVrXhFotepkBlUUtGPdYsTlZa2vLSHqQhIc3GnHJFOcEcm-8GfyPngw3CPOI8T9HCYz-n0gPET6wGPEItBy0qmkgzvXPgGsfi8HnmX8IlgQQf4H8sxIb2Boeb5QSDSqSU40BrYrxV7hy73VQtihqjNJew8ODb6gbFMj99zz_DFSJOtEXyf7XfISW_YXyD4PybsNKLDn2Ajz9nVmCAGEfmbDhdJuD5DfOtDpV93G7W--E2oB49isz6clsDuEM4vd8-nMHEZJt5mmL4lc_-ElOuC0yVnNwnSMUE10QpF2yf_FhROZ0iEIqnPfUvWP7dYsJl6mJVH7UxWIjqsVK7nzDZ69sxmurY28cNSXz7kgRnqFRhpVbUn15nt2-vw2OK3bW4mwsv1fEKjEuG358RPYwJUEPl5dinUt8479WRNQfwgKPOxdSfdKyWd36hC0bpyWooZ5lVtIx3wZisDHjzr0tCtHamq-EJbIFmUf8EL_0QLAOy5vg5uyDeRbN2nJcm4opeiGydHa-AS2t2T8R-Ig9j665-itSGdYNwklqkXbmYnFhlbXMaSaiXTuS7uglHuqgdTgK9ORyeL5zsABW4Xt7wsaj3vmgVa8hMvn-LVTO9yJLv6fIEmtfiuceLzLWb7I406GLKg7u664yNqStwKgI9Ncyx-_mNjznPAJmBhJeAGVQ8iQ5AH6G5JZxqsbkRMEksqW6zxLR_bmm55PzHppVA6HhDNpUus7Az41r30Rb3UBe2FLKWRRtpZ7fxoq3leuOqBRxyDgP1jCR7QMTZ0iFK28S_w8FXQk6YBwUmtvzHiXIyVqp0uDE1LBelXxdycuBW_oGAEl-5vjfI4KYSnRPbjqwWQT3uURievCKJ5Hx5W1nZux9tkboVlLFE9Ni7gsopbsPgi_K2GpSlHs4oBtA9ntJHofWXMOuZFqEpM12vhrabG6TJ2nV30CfsdZfyik42y40GZJjoU9vuufqJdivO7xLybLRdEHzCO42Eomrta0eJSuA2_ouyJyBVwBLkHckNi0GCQYpNOeX4PLYZDtupbW2N7qcZyk6HuFTrlHT67RqltRBZ-o8Mhuyg0B-U39KIafIQQGREXl5hsMSQOMtnypKdbzK_dvMj_mnThn0sA3EIK46B9MAZ6QcM7OOYtbBpuNXRYqvDQyxgfU=","summary":[{"type":"summary_text","text":"**Assessing command retirement timing risks**"},{"type":"summary_text","text":"**Examining stream ownership in register_stream**"}]},{"id":"fc_089095e18118ee7d016a59d36bf52881908cafe5f15fb21db9","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream entry registration\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"760-850,980-1060,1600-1720\"}","call_id":"call_f1gaGGaeLvuuY7gT4ga5Lpwq","name":"read"},{"id":"fc_089095e18118ee7d016a59d36bf53c8190b8e24fc923bd646a","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding physical stream counts\",\"pattern\":\"stream_count|streams\\\\.len|test_.*stream|stream_infos\\\\(\\\\).*len|retired\",\"path\":\"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_FeJTs5WaJnmcxanoqAMAQKLd","name":"grep"},{"id":"fc_089095e18118ee7d016a59d36bf5448190a1ff5f862dd02d27","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding push feed API tests\",\"pattern\":\"push_feed\\\\(|one stream per feed|fire-and-forget|finished on send\",\"path\":\"crates/spt-daemon/src/pump; crates/spt-daemon/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_Iy6sa7FBUnFoUoEOj8jKdB4m","name":"grep"},{"id":"fc_089095e18118ee7d016a59d36bf54c8190ad7edb00f5e6be89","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding stream removal semantics\",\"pattern\":\"streams\\\\.remove|retain\\\\(\\\\|_, e\\\\| e\\\\.conn_id|retire_stream|retired\\\\.swap|finished.*remove\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_kTp57LXQ3soIwIUsxEecP2JZ","name":"grep"}]},"duration":61200.78339999914,"ttft":4050.3379000127316,"contextSnapshot":{"promptTokens":220969,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_f1gaGGaeLvuuY7gT4ga5Lpwq|fc_089095e18118ee7d016a59d36bf52881908cafe5f15fb21db9","toolName":"read","startedAt":"2026-07-17T07:02:04.053Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream entry registration"},"id":"2df0e301","parentId":"38b1bf52","timestamp":"2026-07-17T07:02:04.053Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_FeJTs5WaJnmcxanoqAMAQKLd|fc_089095e18118ee7d016a59d36bf53c8190b8e24fc923bd646a","toolName":"grep","startedAt":"2026-07-17T07:02:04.053Z","args":{"path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs"},"intent":"Finding physical stream counts"},"id":"09bbe74e","parentId":"2df0e301","timestamp":"2026-07-17T07:02:04.053Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Iy6sa7FBUnFoUoEOj8jKdB4m|fc_089095e18118ee7d016a59d36bf5448190a1ff5f862dd02d27","toolName":"grep","startedAt":"2026-07-17T07:02:04.053Z","args":{"path":"crates/spt-daemon/src/pump; crates/spt-daemon/tests"},"intent":"Finding push feed API tests"},"id":"1db53e3c","parentId":"09bbe74e","timestamp":"2026-07-17T07:02:04.053Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_kTp57LXQ3soIwIUsxEecP2JZ|fc_089095e18118ee7d016a59d36bf54c8190ad7edb00f5e6be89","toolName":"grep","startedAt":"2026-07-17T07:02:04.053Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Finding stream removal semantics"},"id":"2e6208f7","parentId":"1db53e3c","timestamp":"2026-07-17T07:02:04.054Z"}
{"type":"message","id":"f417c3c7","parentId":"2e6208f7","timestamp":"2026-07-17T07:02:04.076Z","message":{"role":"toolResult","toolCallId":"call_f1gaGGaeLvuuY7gT4ga5Lpwq|fc_089095e18118ee7d016a59d36bf52881908cafe5f15fb21db9","toolName":"read","content":[{"type":"text","text":"impl SendHalf {\n…\n    async fn finish(&mut self) -> io::Result<()> {\n        match self {\n…\n            SendHalf::Loopback(w) => w.shutdown().await,\n        }\n    }\n}\n\n/// A stream's read half — fed into the single read pump in [`register_stream`].\n/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.\n/// Both append to the same [`StreamLog`], so the subscriber (and everything\n/// above it) cannot tell the transports apart.\nenum RecvHalf {\n    Quic(RecvStream),\n    Loopback(ReadHalf<DuplexStream>),\n}\n\n/// One live stream the broker holds (QUIC or loopback): the send half (brain\n/// writes ride it over IPC) + the sequenced read log.\nstruct StreamEntry {\n    conn_id: u64,\n    remote_id_hex: String,\n    /// Whether THIS node opened the stream (`open_stream`) vs the peer\n    /// (accept loop). The inbound dispatcher (D9-1) claims only\n    /// peer-initiated streams; without this fact the table is ambiguous —\n    /// it holds locally-opened outbound streams too (gotcha: a dispatcher\n    /// that served its own requester stream would deadlock on itself).\n    initiated_locally: bool,\n    /// The send half, serialized behind an async mutex (writes happen on the\n    /// host's runtime via `block_on`).\n    send: tokio::sync::Mutex<SendHalf>,\n    log: Arc<Mutex<StreamLog>>,\n    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below\n    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].\n    /// Unused by ordinary (non-retentive) streams.\n    room: Arc<tokio::sync::Notify>,\n    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n    /// retire must never sever the transport — the worker's reply send is\n    /// fire-and-forget, so a retire that dropped this entry could overtake the\n    /// still-queued reply inside the broker and reset the never-finished QUIC\n    /// send half (requester sees torn-EOF, no reply). The reply-flush ordering\n    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    retired: std::sync::atomic::AtomicBool,\n}\n\n/// Tables shared between the host's sync surface and its runtime tasks\n/// (accept loop, per-conn stream acceptors, per-stream read pumps).\nstruct NetShared {\n    next_conn_id: AtomicU64,\n    conns: Mutex<HashMap<u64, ConnEntry>>,\n    next_stream_id: AtomicU64,\n    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,\n    ring_cap: usize,\n    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.\n    presence: Mutex<PresenceLog>,\n}\n\nimpl NetShared {\n    fn new() -> Self {\n        NetShared {\n            next_conn_id: AtomicU64::new(1),\n            conns: Mutex::new(HashMap::new()),\n            next_stream_id: AtomicU64::new(1),\n            streams: Mutex::new(HashMap::new()),\n            ring_cap: ring_cap_from_env(),\n            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),\n        }\n    }\n}\n\n/// The stream ring depth — [`DEFAULT_STREAM_RING_CHUNKS`] in production.\n/// `SPT_STREAM_RING_CHUNKS` is a TEST-RIG-ONLY valve (the D2 regression rolls\n/// an opener out of a small ring deterministically instead of pushing >4096\n/// real transport chunks); production semantics and sizing are untouched.\nfn ring_cap_from_env() -> usize {\n    std::env::var(\"SPT_STREAM_RING_CHUNKS\")\n        .ok()\n        .and_then(|v| v.parse().ok())\n        .filter(|n: &usize| *n > 0)\n        .unwrap_or(DEFAULT_STREAM_RING_CHUNKS)\n}\n\n/// Register a live connection: mint its broker id and spawn its inbound-stream\n/// acceptor. Must run inside the host's runtime context (the accept loop, or a\n/// `block_on` future) — it spawns tasks.\n/// The connection's OBSERVED remote address as opaque seed JSON (REQ-CONV-1):\n/// the path address string, or `null` when id-only / path-less (nothing worth\n/// seeding over discovery). Shared by [`register_conn`]'s presence write-back\n/// and [`NetHost::conn_remote_addr_json`] so both seed identically.\n// [impl->REQ-CONV-1]\nfn observed_addr_json(conn: &Connection) -> serde_json::Value {\n    let addr = spt_net::net::endpoint::conn_remote_addr(conn);\n    if addr.is_empty() {\n…\n    }\n…\n}\n…\nfn register_conn(\n…\n) -> (u64, String) {\n…\n    tokio::spawn(async move {\n        while let Ok((send, recv)) = conn.accept_bi().await {\n            register_stream(\n                &sh,\n                id,\n                &hex,\n                SendHalf::Quic(send),\n                RecvHalf::Quic(recv),\n                false,\n            );\n        }\n    });\n\n    (id, remote_id_hex)\n}\n\n/// Register a live stream (opened or accepted): mint its broker id and spawn\n/// its read pump. Must run inside the host's runtime context.\n/// `initiated_locally` records the direction fact for the table (true only\n/// from [`NetHost::open_stream`]).\nfn register_stream(\n    shared: &Arc<NetShared>,\n    conn_id: u64,\n    remote_id_hex: &str,\n    send: SendHalf,\n    recv: RecvHalf,\n    initiated_locally: bool,\n) -> u64 {\n    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n    let room = Arc::new(tokio::sync::Notify::new());\n    shared.streams.lock().unwrap().insert(\n        id,\n        Arc::new(StreamEntry {\n            conn_id,\n            remote_id_hex: remote_id_hex.to_string(),\n            initiated_locally,\n            send: tokio::sync::Mutex::new(send),\n            log: Arc::clone(&log),\n            room: Arc::clone(&room),\n            retired: std::sync::atomic::AtomicBool::new(false),\n        }),\n    );\n\n    // The read pump — the stream's single producer. A clean end (peer finish)\n    // and a torn end (conn lost) both finish the log; the buffered chunks stay\n    // replayable either way. The transport differs only at this read leaf\n    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\n    // same `StreamLog`, so nothing above the pump can tell them apart.\n    //\n    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\n    // so before pulling the next chunk the pump WAITS while the undrained ring is\n    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\n    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\n    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\n    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n    tokio::spawn(async move {\n        match recv {\n            RecvHalf::Quic(mut recv) => loop {\n                await_ring_room(&log, &room).await;\n                match recv.read_chunk(STREAM_READ_CHUNK).await {\n                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\n                    Ok(None) | Err(_) => {\n                        log.lock().unwrap().finish();\n                        break;\n                    }\n                }\n            },\n            RecvHalf::Loopback(mut recv) => {\n                let mut buf = vec![0u8; STREAM_READ_CHUNK];\n                loop {\n                    await_ring_room(&log, &room).await;\n                    match recv.read(&mut buf).await {\n                        Ok(0) | Err(_) => {\n                            log.lock().unwrap().finish();\n                            break;\n                        }\n                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\n                    }\n                }\n            }\n        }\n    });\n…\n}\n…\n    pub fn conn_remote_addr_json(&self, conn_id: u64) -> serde_json::Value {\n        let conn = {\n…\n            match conns.get(&conn_id) {\n…\n                Some(e) => match &e.kind {\n…\n                },\n                None => return serde_json::Value::Null,\n            }\n        };\n        observed_addr_json(&conn)\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and return `(conn_id, remote_id_hex)` — `remote_id_hex` is\n    /// THIS node's own id, so a stream served over it gates same-node (access\n    /// short-circuit Allow). The conn carries NO iroh handshake: its identity is\n    /// broker-minted BY CONSTRUCTION and can never arise from an inbound dial.\n    /// This is what lets a LOCAL `spt rc` ride the byte-identical `serve_attach`\n    /// pump — QUIC self-dial is refused by iroh, so local attach uses this.\n    /// Idempotent: every call returns the same singleton conn (one is all a\n    /// local attach needs); no journaling needed (reuse is the dedup).\n    // [impl->REQ-RC-1]\n    pub fn dial_loopback(&self) -> (u64, String) {\n        let remote_id_hex = self.local_id.to_hex();\n        let mut slot = self.loopback_conn.lock().unwrap();\n        if let Some(id) = *slot {\n            return (id, remote_id_hex);\n        }\n        let id = self.shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n        self.shared.conns.lock().unwrap().insert(\n            id,\n            ConnEntry {\n                kind: ConnKind::Loopback,\n                remote_id_hex: remote_id_hex.clone(),\n                proven_subnets: HashSet::new(),\n            },\n        );\n        *slot = Some(id);\n        (id, remote_id_hex)\n    }\n\n    /// Open a bidi stream on a held connection, registering it (with its read\n    /// pump) in the stream table (D4b). Note QUIC streams are lazy: the peer\n    /// sees the stream on its first write.\n    ///\n    /// On a [`ConnKind::Loopback`] conn there is no peer to accept the stream,\n    /// so the open mints a CROSS-WIRED PAIR in-process: an operator row\n    /// (`initiated_locally = true`, the returned id) and a peer row\n    /// (`initiated_locally = false`) the inbound dispatcher claims and serves via\n    /// `serve_attach` — exactly as an `accept_bi` would surface a remote-opened\n    /// stream. One `tokio::io::duplex` wires both directions; each row's read\n    /// pump and `StreamLog` are the SAME machinery the QUIC path uses (the W1.5\n    /// one-pump invariant). The peer row's `remote_id_hex` is this node's own id,\n    /// so its serve gates same-node.\n    // [impl->REQ-DAEMON-2]\n    // [impl->REQ-RC-1]\n    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\n        enum Backing {\n            Quic(Connection),\n            Loopback,\n        }\n        let (backing, remote_id_hex) = {\n            let conns = self.shared.conns.lock().unwrap();\n            let e = conns.get(&conn_id).ok_or_else(|| {\n                io::Error::new(io::ErrorKind::NotFound, format!(\"no such conn {conn_id}\"))\n            })?;\n            let backing = match &e.kind {\n                ConnKind::Quic(c) => Backing::Quic(c.clone()),\n                ConnKind::Loopback => Backing::Loopback,\n            };\n            (backing, e.remote_id_hex.clone())\n        };\n        let shared = Arc::clone(&self.shared);\n        match backing {\n            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed\n            // peer can block on stream flow-control credit the dead peer never grants.\n            Backing::Quic(conn) => self.bounded_block_on(async move {\n                let (send, recv) = conn\n                    .open_bi()\n                    .await\n                    .map_err(|e| io::Error::other(e.to_string()))?;\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Quic(send),\n                    RecvHalf::Quic(recv),\n                    true,\n                ))\n            }),\n            // Bounded like the QUIC arm (REQ-BROKER-ATTACH-JOURNAL-RESILIENT, bug\n            // #16): the loopback open is all in-memory and normally returns\n            // instantly, but a SICK NetHost runtime (saturated/wedged after a\n            // self-update brain-respawn) would otherwise hang this journaled op with\n            // no deadline — killing the per-conn reply thread so every attach\n            // silently deadlines (\"brain IPC read deadline elapsed\") while\n            // non-journaled ops keep working. `bounded_block_on` turns that into a\n            // prompt ordinary error frame; the bound never bites a healthy runtime.\n            // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\n            Backing::Loopback => self.bounded_block_on(async move {\n                // One duplex carries BOTH directions: operator writes reach the\n                // peer's read pump and vice-versa. Split each end into a read +\n                // write half so each row owns exactly its leg.\n                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);\n                let (op_r, op_w) = tokio::io::split(op_io);\n                let (peer_r, peer_w) = tokio::io::split(peer_io);\n                // Register the peer (inbound) row first so it is in the table\n                // before the operator can write its Request onto the wire — the\n                // dispatcher must never miss the stream.\n                register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(peer_w),\n                    RecvHalf::Loopback(peer_r),\n                    false,\n                );\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(op_w),\n                    RecvHalf::Loopback(op_r),\n                    true,\n                ))\n            }),\n        }\n    }"}],"details":{"displayContent":{"text":"impl SendHalf {\n…\n    async fn finish(&mut self) -> io::Result<()> {\n        match self {\n…\n            SendHalf::Loopback(w) => w.shutdown().await,\n        }\n    }\n}\n\n/// A stream's read half — fed into the single read pump in [`register_stream`].\n/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.\n/// Both append to the same [`StreamLog`], so the subscriber (and everything\n/// above it) cannot tell the transports apart.\nenum RecvHalf {\n    Quic(RecvStream),\n    Loopback(ReadHalf<DuplexStream>),\n}\n\n/// One live stream the broker holds (QUIC or loopback): the send half (brain\n/// writes ride it over IPC) + the sequenced read log.\nstruct StreamEntry {\n    conn_id: u64,\n    remote_id_hex: String,\n    /// Whether THIS node opened the stream (`open_stream`) vs the peer\n    /// (accept loop). The inbound dispatcher (D9-1) claims only\n    /// peer-initiated streams; without this fact the table is ambiguous —\n    /// it holds locally-opened outbound streams too (gotcha: a dispatcher\n    /// that served its own requester stream would deadlock on itself).\n    initiated_locally: bool,\n    /// The send half, serialized behind an async mutex (writes happen on the\n    /// host's runtime via `block_on`).\n    send: tokio::sync::Mutex<SendHalf>,\n    log: Arc<Mutex<StreamLog>>,\n    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below\n    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].\n    /// Unused by ordinary (non-retentive) streams.\n    room: Arc<tokio::sync::Notify>,\n    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n    /// retire must never sever the transport — the worker's reply send is\n    /// fire-and-forget, so a retire that dropped this entry could overtake the\n    /// still-queued reply inside the broker and reset the never-finished QUIC\n    /// send half (requester sees torn-EOF, no reply). The reply-flush ordering\n    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    retired: std::sync::atomic::AtomicBool,\n}\n\n/// Tables shared between the host's sync surface and its runtime tasks\n/// (accept loop, per-conn stream acceptors, per-stream read pumps).\nstruct NetShared {\n    next_conn_id: AtomicU64,\n    conns: Mutex<HashMap<u64, ConnEntry>>,\n    next_stream_id: AtomicU64,\n    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,\n    ring_cap: usize,\n    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.\n    presence: Mutex<PresenceLog>,\n}\n\nimpl NetShared {\n    fn new() -> Self {\n        NetShared {\n            next_conn_id: AtomicU64::new(1),\n            conns: Mutex::new(HashMap::new()),\n            next_stream_id: AtomicU64::new(1),\n            streams: Mutex::new(HashMap::new()),\n            ring_cap: ring_cap_from_env(),\n            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),\n        }\n    }\n}\n\n/// The stream ring depth — [`DEFAULT_STREAM_RING_CHUNKS`] in production.\n/// `SPT_STREAM_RING_CHUNKS` is a TEST-RIG-ONLY valve (the D2 regression rolls\n/// an opener out of a small ring deterministically instead of pushing >4096\n/// real transport chunks); production semantics and sizing are untouched.\nfn ring_cap_from_env() -> usize {\n    std::env::var(\"SPT_STREAM_RING_CHUNKS\")\n        .ok()\n        .and_then(|v| v.parse().ok())\n        .filter(|n: &usize| *n > 0)\n        .unwrap_or(DEFAULT_STREAM_RING_CHUNKS)\n}\n\n/// Register a live connection: mint its broker id and spawn its inbound-stream\n/// acceptor. Must run inside the host's runtime context (the accept loop, or a\n/// `block_on` future) — it spawns tasks.\n/// The connection's OBSERVED remote address as opaque seed JSON (REQ-CONV-1):\n/// the path address string, or `null` when id-only / path-less (nothing worth\n/// seeding over discovery). Shared by [`register_conn`]'s presence write-back\n/// and [`NetHost::conn_remote_addr_json`] so both seed identically.\n// [impl->REQ-CONV-1]\nfn observed_addr_json(conn: &Connection) -> serde_json::Value {\n    let addr = spt_net::net::endpoint::conn_remote_addr(conn);\n    if addr.is_empty() {\n…\n    }\n…\n}\n…\nfn register_conn(\n…\n) -> (u64, String) {\n…\n    tokio::spawn(async move {\n        while let Ok((send, recv)) = conn.accept_bi().await {\n            register_stream(\n                &sh,\n                id,\n                &hex,\n                SendHalf::Quic(send),\n                RecvHalf::Quic(recv),\n                false,\n            );\n        }\n    });\n\n    (id, remote_id_hex)\n}\n\n/// Register a live stream (opened or accepted): mint its broker id and spawn\n/// its read pump. Must run inside the host's runtime context.\n/// `initiated_locally` records the direction fact for the table (true only\n/// from [`NetHost::open_stream`]).\nfn register_stream(\n    shared: &Arc<NetShared>,\n    conn_id: u64,\n    remote_id_hex: &str,\n    send: SendHalf,\n    recv: RecvHalf,\n    initiated_locally: bool,\n) -> u64 {\n    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n    let room = Arc::new(tokio::sync::Notify::new());\n    shared.streams.lock().unwrap().insert(\n        id,\n        Arc::new(StreamEntry {\n            conn_id,\n            remote_id_hex: remote_id_hex.to_string(),\n            initiated_locally,\n            send: tokio::sync::Mutex::new(send),\n            log: Arc::clone(&log),\n            room: Arc::clone(&room),\n            retired: std::sync::atomic::AtomicBool::new(false),\n        }),\n    );\n\n    // The read pump — the stream's single producer. A clean end (peer finish)\n    // and a torn end (conn lost) both finish the log; the buffered chunks stay\n    // replayable either way. The transport differs only at this read leaf\n    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\n    // same `StreamLog`, so nothing above the pump can tell them apart.\n    //\n    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\n    // so before pulling the next chunk the pump WAITS while the undrained ring is\n    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\n    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\n    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\n    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n    tokio::spawn(async move {\n        match recv {\n            RecvHalf::Quic(mut recv) => loop {\n                await_ring_room(&log, &room).await;\n                match recv.read_chunk(STREAM_READ_CHUNK).await {\n                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\n                    Ok(None) | Err(_) => {\n                        log.lock().unwrap().finish();\n                        break;\n                    }\n                }\n            },\n            RecvHalf::Loopback(mut recv) => {\n                let mut buf = vec![0u8; STREAM_READ_CHUNK];\n                loop {\n                    await_ring_room(&log, &room).await;\n                    match recv.read(&mut buf).await {\n                        Ok(0) | Err(_) => {\n                            log.lock().unwrap().finish();\n                            break;\n                        }\n                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\n                    }\n                }\n            }\n        }\n    });\n…\n}\n…\n    pub fn conn_remote_addr_json(&self, conn_id: u64) -> serde_json::Value {\n        let conn = {\n…\n            match conns.get(&conn_id) {\n…\n                Some(e) => match &e.kind {\n…\n                },\n                None => return serde_json::Value::Null,\n            }\n        };\n        observed_addr_json(&conn)\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and return `(conn_id, remote_id_hex)` — `remote_id_hex` is\n    /// THIS node's own id, so a stream served over it gates same-node (access\n    /// short-circuit Allow). The conn carries NO iroh handshake: its identity is\n    /// broker-minted BY CONSTRUCTION and can never arise from an inbound dial.\n    /// This is what lets a LOCAL `spt rc` ride the byte-identical `serve_attach`\n    /// pump — QUIC self-dial is refused by iroh, so local attach uses this.\n    /// Idempotent: every call returns the same singleton conn (one is all a\n    /// local attach needs); no journaling needed (reuse is the dedup).\n    // [impl->REQ-RC-1]\n    pub fn dial_loopback(&self) -> (u64, String) {\n        let remote_id_hex = self.local_id.to_hex();\n        let mut slot = self.loopback_conn.lock().unwrap();\n        if let Some(id) = *slot {\n            return (id, remote_id_hex);\n        }\n        let id = self.shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n        self.shared.conns.lock().unwrap().insert(\n            id,\n            ConnEntry {\n                kind: ConnKind::Loopback,\n                remote_id_hex: remote_id_hex.clone(),\n                proven_subnets: HashSet::new(),\n            },\n        );\n        *slot = Some(id);\n        (id, remote_id_hex)\n    }\n\n    /// Open a bidi stream on a held connection, registering it (with its read\n    /// pump) in the stream table (D4b). Note QUIC streams are lazy: the peer\n    /// sees the stream on its first write.\n    ///\n    /// On a [`ConnKind::Loopback`] conn there is no peer to accept the stream,\n    /// so the open mints a CROSS-WIRED PAIR in-process: an operator row\n    /// (`initiated_locally = true`, the returned id) and a peer row\n    /// (`initiated_locally = false`) the inbound dispatcher claims and serves via\n    /// `serve_attach` — exactly as an `accept_bi` would surface a remote-opened\n    /// stream. One `tokio::io::duplex` wires both directions; each row's read\n    /// pump and `StreamLog` are the SAME machinery the QUIC path uses (the W1.5\n    /// one-pump invariant). The peer row's `remote_id_hex` is this node's own id,\n    /// so its serve gates same-node.\n    // [impl->REQ-DAEMON-2]\n    // [impl->REQ-RC-1]\n    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\n        enum Backing {\n            Quic(Connection),\n            Loopback,\n        }\n        let (backing, remote_id_hex) = {\n            let conns = self.shared.conns.lock().unwrap();\n            let e = conns.get(&conn_id).ok_or_else(|| {\n                io::Error::new(io::ErrorKind::NotFound, format!(\"no such conn {conn_id}\"))\n            })?;\n            let backing = match &e.kind {\n                ConnKind::Quic(c) => Backing::Quic(c.clone()),\n                ConnKind::Loopback => Backing::Loopback,\n            };\n            (backing, e.remote_id_hex.clone())\n        };\n        let shared = Arc::clone(&self.shared);\n        match backing {\n            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed\n            // peer can block on stream flow-control credit the dead peer never grants.\n            Backing::Quic(conn) => self.bounded_block_on(async move {\n                let (send, recv) = conn\n                    .open_bi()\n                    .await\n                    .map_err(|e| io::Error::other(e.to_string()))?;\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Quic(send),\n                    RecvHalf::Quic(recv),\n                    true,\n                ))\n            }),\n            // Bounded like the QUIC arm (REQ-BROKER-ATTACH-JOURNAL-RESILIENT, bug\n            // #16): the loopback open is all in-memory and normally returns\n            // instantly, but a SICK NetHost runtime (saturated/wedged after a\n            // self-update brain-respawn) would otherwise hang this journaled op with\n            // no deadline — killing the per-conn reply thread so every attach\n            // silently deadlines (\"brain IPC read deadline elapsed\") while\n            // non-journaled ops keep working. `bounded_block_on` turns that into a\n            // prompt ordinary error frame; the bound never bites a healthy runtime.\n            // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\n            Backing::Loopback => self.bounded_block_on(async move {\n                // One duplex carries BOTH directions: operator writes reach the\n                // peer's read pump and vice-versa. Split each end into a read +\n                // write half so each row owns exactly its leg.\n                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);\n                let (op_r, op_w) = tokio::io::split(op_io);\n                let (peer_r, peer_w) = tokio::io::split(peer_io);\n                // Register the peer (inbound) row first so it is in the table\n                // before the operator can write its Request onto the wire — the\n                // dispatcher must never miss the stream.\n                register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(peer_w),\n                    RecvHalf::Loopback(peer_r),\n                    false,\n                );\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(op_w),\n                    RecvHalf::Loopback(op_r),\n                    true,\n                ))\n            }),\n        }\n    }","startLine":743,"lineNumbers":[743,null,757,758,null,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,852,null,854,null,916,null,920,null,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,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,null,1065,null,1592,1593,null,1595,null,1597,null,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"}}},"isError":false,"timestamp":1784271724076}}
{"type":"message","id":"2b594f29","parentId":"f417c3c7","timestamp":"2026-07-17T07:02:04.078Z","message":{"role":"toolResult","toolCallId":"call_kTp57LXQ3soIwIUsxEecP2JZ|fc_089095e18118ee7d016a59d36bf54c8190ad7edb00f5e6be89","toolName":"grep","content":[{"type":"text","text":" 966|            .unwrap()\n*967|            .retain(|_, e| e.conn_id != id);\n 968|        sh_close\n 969|            .presence\n 970|            .lock()\n...\n 1835|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*1836|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837|        let Some(entry) = self\n 1838|            .shared\n 1839|            .streams\n...\n 2413|\n*2414|        assert!(host.retire_stream(shell), \"first retire hides the row\");\n 2415|        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416|        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417|        assert!(ids.contains(&owner), \"the active sibling row stays\");\n*2418|        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n 2419|        assert!(\n 2420|            host.stream_opener(shell).is_none(),\n 2421|            \"a retired stream answers no-such-stream\"\n...\n 2447|        // Retire FIRST — the ordering the race loses in the field.\n*2448|        assert!(host.retire_stream(shell), \"retire the serving row\");\n 2449|\n 2450|        // The worker's reply lands AFTER retirement and must still deliver.\n 2451|        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":5,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":5}],"truncated":false,"displayContent":"  966│            .unwrap()\n *967│            .retain(|_, e| e.conn_id != id);\n  968│        sh_close\n  969│            .presence\n  970│            .lock()\n     │...\n 1835│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*1836│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837│        let Some(entry) = self\n 1838│            .shared\n 1839│            .streams\n     │...\n 2413│\n*2414│        assert!(host.retire_stream(shell), \"first retire hides the row\");\n 2415│        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416│        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417│        assert!(ids.contains(&owner), \"the active sibling row stays\");\n*2418│        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n 2419│        assert!(\n 2420│            host.stream_opener(shell).is_none(),\n 2421│            \"a retired stream answers no-such-stream\"\n     │...\n 2447│        // Retire FIRST — the ordering the race loses in the field.\n*2448│        assert!(host.retire_stream(shell), \"retire the serving row\");\n 2449│\n 2450│        // The worker's reply lands AFTER retirement and must still deliver.\n 2451│        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)"},"isError":false,"timestamp":1784271724078}}
{"type":"message","id":"286c2cfd","parentId":"2b594f29","timestamp":"2026-07-17T07:02:04.086Z","message":{"role":"toolResult","toolCallId":"call_Iy6sa7FBUnFoUoEOj8jKdB4m|fc_089095e18118ee7d016a59d36bf5448190a1ff5f862dd02d27","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## src/pump/\n### mod.rs\n 48|//!\n*49|//! Every pump write is fire-and-forget (`op_id: None`) — a journaled\n 50|//! `net_stream_send` blocks on its ack and DISCARDS interleaved events\n 51|//! (gotcha #1). Stream opens for push feeds carry no op either: the feeds\n 52|//! are idempotent by construction. The sync pull's `open_op` IS durable\n...\n 366|/// **KH 7.4 — never an LLM-bearing worker.** A pump worker is cadenced,\n*367|/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile\n 368|/// turn) runs at activation paths with a manifest runtime to ride (ADR-0013),\n 369|/// NEVER on the pump thread — the seam must not become the invitation to put\n 370|/// it here.\n...\n 492|impl PeerIo<'_> {\n*493|    /// Open a fresh stream and write one whole feed fire-and-forget (the\n*494|    /// push-feed discipline — `op_id: None`, finished on send).\n*495|    fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {\n*496|        push_feed(self.brain, self.conn_id, feed)\n 497|    }\n 498|\n 499|    /// Mint the next durable open-op from the pump's sole counter (exactly-once\n...\n 1115|\n*1116|/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\n 1117|/// and finish it (the receiver's dispatcher classifies + applies). One\n 1118|/// stream per feed: streams are single-purpose by the demux contract.\n*1119|fn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n 1120|    let opened = brain.net_open_stream(conn_id, None)?;\n 1121|    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n 1122|    Ok(())\n### notif.rs\n 42|    /// Emit this node's notif feed for `subnet` (the full semilattice spool)\n*43|    /// and push it fire-and-forget; the receiver's join dedups.\n 44|    // [impl->REQ-NOTIF-1]\n 45|    fn peer_step(\n 46|        &mut self,\n...\n 55|        if !feed.is_empty() {\n*56|            io.push_feed(&feed)?;\n 57|        }\n 58|        Ok(())\n 59|    }\n### registry.rs\n 158|    /// Push this node's own rows for `subnet` over the peer's feed stream\n*159|    /// (per-subnet filter of the `pre_round` adverts). Fire-and-forget, one\n 160|    /// stream per feed.\n 161|    fn peer_step(\n 162|        &mut self,\n...\n 174|        if !lines.is_empty() {\n*175|            io.push_feed(&lines)?;\n 176|        }\n 177|        Ok(())\n 178|    }\n\n## tests/\n### brain_resume_conn_deadlock.rs\n 276|///\n*277|/// DETERMINISM of the seam arm (no timing window): `subscribe_with` is FIRE-AND-FORGET\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...\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...\n 342|        .sessions()\n*343|        .expect(\"barrier round-trip flushes the fire-and-forget subscribes\");\n 344|    for &sid in &sids {\n 345|        let count = broker.test_session_viewer_count(sid);\n 346|        assert!(\n### pump.rs\n 672|/// Ok on stop — a dead PEER never bubbles Err, so it NEVER triggers a supervised\n*673|/// restart. (`cadence.sync/update` are parked long so only the fire-and-forget\n 674|/// registry/notif push legs fire — the live peer stays connected + advertised\n 675|/// without a reply-read, keeping the round fast and the connect signal stable.)\n 676|#[test]\n### redispatch_stall.rs\n 478|            f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");\n*479|            // Give the fire-and-forget subscribe a beat to process, then\n 480|            // probe the lease with a real send on a separate conn.\n 481|            thread::sleep(Duration::from_millis(300));\n 482|            let mut s = connect_retry(&name_a);"}],"details":{"scopePath":"crates/spt-daemon/src/pump, crates/spt-daemon/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":17,"fileCount":6,"files":["crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/notif.rs","crates/spt-daemon/src/pump/registry.rs","crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","crates/spt-daemon/tests/pump.rs","crates/spt-daemon/tests/redispatch_stall.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/pump/mod.rs","count":8},{"path":"crates/spt-daemon/src/pump/notif.rs","count":2},{"path":"crates/spt-daemon/src/pump/registry.rs","count":2},{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","count":3},{"path":"crates/spt-daemon/tests/pump.rs","count":1},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/\n\n## src/pump/\n### mod.rs\n   48│//!\n  *49│//! Every pump write is fire-and-forget (`op_id: None`) — a journaled\n   50│//! `net_stream_send` blocks on its ack and DISCARDS interleaved events\n   51│//! (gotcha #1). Stream opens for push feeds carry no op either: the feeds\n   52│//! are idempotent by construction. The sync pull's `open_op` IS durable\n     │...\n  366│/// **KH 7.4 — never an LLM-bearing worker.** A pump worker is cadenced,\n *367│/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile\n  368│/// turn) runs at activation paths with a manifest runtime to ride (ADR-0013),\n  369│/// NEVER on the pump thread — the seam must not become the invitation to put\n  370│/// it here.\n     │...\n  492│impl PeerIo<'_> {\n *493│    /// Open a fresh stream and write one whole feed fire-and-forget (the\n *494│    /// push-feed discipline — `op_id: None`, finished on send).\n *495│    fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {\n *496│        push_feed(self.brain, self.conn_id, feed)\n  497│    }\n  498│\n  499│    /// Mint the next durable open-op from the pump's sole counter (exactly-once\n     │...\n 1115│\n*1116│/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\n 1117│/// and finish it (the receiver's dispatcher classifies + applies). One\n 1118│/// stream per feed: streams are single-purpose by the demux contract.\n*1119│fn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n 1120│    let opened = brain.net_open_stream(conn_id, None)?;\n 1121│    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n 1122│    Ok(())\n### notif.rs\n 42│    /// Emit this node's notif feed for `subnet` (the full semilattice spool)\n*43│    /// and push it fire-and-forget; the receiver's join dedups.\n 44│    // [impl->REQ-NOTIF-1]\n 45│    fn peer_step(\n 46│        &mut self,\n   │...\n 55│        if !feed.is_empty() {\n*56│            io.push_feed(&feed)?;\n 57│        }\n 58│        Ok(())\n 59│    }\n### registry.rs\n 158│    /// Push this node's own rows for `subnet` over the peer's feed stream\n*159│    /// (per-subnet filter of the `pre_round` adverts). Fire-and-forget, one\n 160│    /// stream per feed.\n 161│    fn peer_step(\n 162│        &mut self,\n    │...\n 174│        if !lines.is_empty() {\n*175│            io.push_feed(&lines)?;\n 176│        }\n 177│        Ok(())\n 178│    }\n\n## tests/\n### brain_resume_conn_deadlock.rs\n 276│///\n*277│/// DETERMINISM of the seam arm (no timing window): `subscribe_with` is FIRE-AND-FORGET\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    │...\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    │...\n 342│        .sessions()\n*343│        .expect(\"barrier round-trip flushes the fire-and-forget subscribes\");\n 344│    for &sid in &sids {\n 345│        let count = broker.test_session_viewer_count(sid);\n 346│        assert!(\n### pump.rs\n 672│/// Ok on stop — a dead PEER never bubbles Err, so it NEVER triggers a supervised\n*673│/// restart. (`cadence.sync/update` are parked long so only the fire-and-forget\n 674│/// registry/notif push legs fire — the live peer stays connected + advertised\n 675│/// without a reply-read, keeping the round fast and the connect signal stable.)\n 676│#[test]\n### redispatch_stall.rs\n 478│            f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");\n*479│            // Give the fire-and-forget subscribe a beat to process, then\n 480│            // probe the lease with a real send on a separate conn.\n 481│            thread::sleep(Duration::from_millis(300));\n 482│            let mut s = connect_retry(&name_a);"},"isError":false,"timestamp":1784271724086}}
{"type":"message","id":"23b67dec","parentId":"286c2cfd","timestamp":"2026-07-17T07:02:04.092Z","message":{"role":"toolResult","toolCallId":"call_FeJTs5WaJnmcxanoqAMAQKLd|fc_089095e18118ee7d016a59d36bf53c8190b8e24fc923bd646a","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## nethost.rs\n 792|    room: Arc<tokio::sync::Notify>,\n*793|    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n 794|    /// retire must never sever the transport — the worker's reply send is\n 795|    /// fire-and-forget, so a retire that dropped this entry could overtake the\n 796|    /// still-queued reply inside the broker and reset the never-finished QUIC\n...\n 799|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*800|    retired: std::sync::atomic::AtomicBool,\n 801|}\n 802|\n 803|/// Tables shared between the host's sync surface and its runtime tasks\n...\n 1019|            room: Arc::clone(&room),\n*1020|            retired: std::sync::atomic::AtomicBool::new(false),\n 1021|        }),\n 1022|    );\n 1023|\n...\n 1802|    /// streams (including peer-initiated ones that arrived while it was down).\n*1803|    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n 1804|    /// enumeration is exactly what a future dispatcher generation would replay.\n 1805|    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806|        let streams = self.shared.streams.lock().unwrap();\n...\n 1808|            .iter()\n*1809|            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n 1810|            .map(|(id, e)| {\n 1811|                let log = e.log.lock().unwrap();\n 1812|                NetStreamInfo {\n...\n 1831|    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n*1832|    /// reply sends when it removed the entry (see the `retired` field note).\n 1833|    /// Physical teardown stays with the conn-close sweep. Returns whether this\n*1834|    /// call newly retired the row (idempotent; absent row = false).\n 1835|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1836|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837|        let Some(entry) = self\n...\n 1847|        let newly = !entry\n*1848|            .retired\n 1849|            .swap(true, std::sync::atomic::Ordering::SeqCst);\n 1850|        if newly {\n*1851|            // A retired exchange's request bytes are dead weight — free the\n 1852|            // ring; the pinned opener and cursors stay (cheap, bounded).\n 1853|            entry.log.lock().unwrap().ring.clear();\n 1854|        }\n...\n 1864|        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n*1865|        // A retired row answers no-such-stream: classification exists only to\n*1866|        // serve, and a retired exchange must never be served again.\n*1867|        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n 1868|            return None;\n 1869|        }\n 1870|        let line = entry.log.lock().unwrap().opener_line();\n...\n 2386|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n*2387|    // named row from dispatch eligibility: the retired stream leaves the\n 2388|    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389|    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390|    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391|    // teardown — the transport half of the contract is the sibling test\n*2392|    // `retired_row_still_flushes_a_late_reply`.\n 2393|    #[test]\n 2394|    fn retire_hides_the_row_while_active_rows_stay() {\n 2395|        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n...\n 2415|        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n*2416|        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417|        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418|        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n 2419|        assert!(\n 2420|            host.stream_opener(shell).is_none(),\n*2421|            \"a retired stream answers no-such-stream\"\n 2422|        );\n 2423|    }\n 2424|\n 2425|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n*2426|    // transport: a reply sent AFTER the row was retired still flushes to the\n 2427|    // peer. This is the field race's lost ordering made deterministic — the\n 2428|    // worker's reply send is fire-and-forget (brain returns before the broker\n 2429|    // processes the envelope), so the acked retire on its own conn can be\n...\n 2434|    #[test]\n*2435|    fn retired_row_still_flushes_a_late_reply() {\n 2436|        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n 2437|        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\n 2438|        // The requester's one-shot request: write + FIN (the rest/sync shape —\n...\n 2451|        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\n*2452|            .expect(\"a retired row still accepts the in-flight reply\");\n 2453|        let mut got = Vec::new();\n 2454|        let mut finished = false;\n 2455|        for _ in 0..400 {\n...\n 2465|            String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),\n*2466|            \"the reply flushed through the retired row (got {got:?})\"\n 2467|        );\n 2468|        assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");\n 2469|    }"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs, crates/spt-daemon/tests/pump.rs, crates/spt-daemon/tests/mesh.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":20,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":20}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## nethost.rs\n  792│    room: Arc<tokio::sync::Notify>,\n *793│    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n  794│    /// retire must never sever the transport — the worker's reply send is\n  795│    /// fire-and-forget, so a retire that dropped this entry could overtake the\n  796│    /// still-queued reply inside the broker and reset the never-finished QUIC\n     │...\n  799│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n *800│    retired: std::sync::atomic::AtomicBool,\n  801│}\n  802│\n  803│/// Tables shared between the host's sync surface and its runtime tasks\n     │...\n 1019│            room: Arc::clone(&room),\n*1020│            retired: std::sync::atomic::AtomicBool::new(false),\n 1021│        }),\n 1022│    );\n 1023│\n     │...\n 1802│    /// streams (including peer-initiated ones that arrived while it was down).\n*1803│    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n 1804│    /// enumeration is exactly what a future dispatcher generation would replay.\n 1805│    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806│        let streams = self.shared.streams.lock().unwrap();\n     │...\n 1808│            .iter()\n*1809│            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n 1810│            .map(|(id, e)| {\n 1811│                let log = e.log.lock().unwrap();\n 1812│                NetStreamInfo {\n     │...\n 1831│    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n*1832│    /// reply sends when it removed the entry (see the `retired` field note).\n 1833│    /// Physical teardown stays with the conn-close sweep. Returns whether this\n*1834│    /// call newly retired the row (idempotent; absent row = false).\n 1835│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1836│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1837│        let Some(entry) = self\n     │...\n 1847│        let newly = !entry\n*1848│            .retired\n 1849│            .swap(true, std::sync::atomic::Ordering::SeqCst);\n 1850│        if newly {\n*1851│            // A retired exchange's request bytes are dead weight — free the\n 1852│            // ring; the pinned opener and cursors stay (cheap, bounded).\n 1853│            entry.log.lock().unwrap().ring.clear();\n 1854│        }\n     │...\n 1864│        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n*1865│        // A retired row answers no-such-stream: classification exists only to\n*1866│        // serve, and a retired exchange must never be served again.\n*1867│        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n 1868│            return None;\n 1869│        }\n 1870│        let line = entry.log.lock().unwrap().opener_line();\n     │...\n 2386│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n*2387│    // named row from dispatch eligibility: the retired stream leaves the\n 2388│    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389│    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390│    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391│    // teardown — the transport half of the contract is the sibling test\n*2392│    // `retired_row_still_flushes_a_late_reply`.\n 2393│    #[test]\n 2394│    fn retire_hides_the_row_while_active_rows_stay() {\n 2395│        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n     │...\n 2415│        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n*2416│        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417│        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418│        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n 2419│        assert!(\n 2420│            host.stream_opener(shell).is_none(),\n*2421│            \"a retired stream answers no-such-stream\"\n 2422│        );\n 2423│    }\n 2424│\n 2425│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n*2426│    // transport: a reply sent AFTER the row was retired still flushes to the\n 2427│    // peer. This is the field race's lost ordering made deterministic — the\n 2428│    // worker's reply send is fire-and-forget (brain returns before the broker\n 2429│    // processes the envelope), so the acked retire on its own conn can be\n     │...\n 2434│    #[test]\n*2435│    fn retired_row_still_flushes_a_late_reply() {\n 2436│        let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");\n 2437│        let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");\n 2438│        // The requester's one-shot request: write + FIN (the rest/sync shape —\n     │...\n 2451│        host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\n*2452│            .expect(\"a retired row still accepts the in-flight reply\");\n 2453│        let mut got = Vec::new();\n 2454│        let mut finished = false;\n 2455│        for _ in 0..400 {\n     │...\n 2465│            String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),\n*2466│            \"the reply flushed through the retired row (got {got:?})\"\n 2467│        );\n 2468│        assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");\n 2469│    }"},"isError":false,"timestamp":1784271724092}}
{"type":"message","id":"17c633f9","parentId":"23b67dec","timestamp":"2026-07-17T07:02:18.249Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Prioritizing communication to Main**"},{"type":"toolCall","id":"call_jLI1v912GxxscAq4vplBoZ9L|fc_089095e18118ee7d016a59d371bea081908646a5e5f34506f2","name":"irc","arguments":{"i":"Sending Registry root cause","op":"send","to":"Main","message":"Registry-specific RCA: periodic pump opens+FINs a fresh bidi stream every 30s/peer/subnet (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`) but sender local StreamEntry is never retired; receiver `serve_registry_feed` returns on EOF without FINning reverse half (`dispatch.rs:984-1046`). `retire_stream` only hides receiver row and deliberately never removes physical entry until conn close (`nethost.rs:1826-1854`); long-lived pump conn means rows accumulate. Worse `stream_infos()` filters only retired, not initiated-local (`nethost.rs:1805-1823`), so dispatcher asks at 100ms and broker serializes/clones ALL historical sender rows before client-side initiated_local skip (`dispatch.rs:455-469`). Thus sustained O(total feeds) IPC/I/O, not O(active), and fresh Registry replay/poison can coexist for failed unretired receiver rows. 550 threads not explained by pool alone (MAX workers=8); add conn-handler/watchdog census. Target regression: real long-lived pump+dispatcher, N registry-only rounds, assert eligible enumeration/physical stream count+broker conn_handler_count plateau and post-refresh stream-sub-attach Registry count is O(active), no poison/replay.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8657,"output":338,"cacheRead":220672,"cacheWrite":0,"totalTokens":229667,"reasoningTokens":14,"cost":{"input":0.043285000000000004,"output":0.01014,"cacheRead":0.11033599999999999,"cacheWrite":0,"total":0.163761}},"stopReason":"toolUse","timestamp":1784271724095,"responseId":"resp_089095e18118ee7d016a59d36c5c688190a54610119b982d07","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d370eb248190a82fe5e69d6649b4","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdNxmchKkC4YcSbbN7uvhQ65UjFx1qcgjq2rDPAW4AxIclLZH2hSq0xK9yx2bL4m1MhyWComO_h4eWlc_lbwyh0Ye6RWE4SG4nJVgTE-6VSK_jf9AMrnvYeDSNan4WSFHtB5-cK5Xea6KqvGvNQyVnxfBvFUzAe8ewtqA3C60vQ4YnB-Q4DPfLQMTC0B67RpuhkQ9A7qbjIdIP-9YsmpURdyux7QiZLi9qcSGmzKYrgG8ryQgJmhrHK8jGJ6WP06qh6hpLU09PedK2EBdEYNX0wXwf67veWi1mULu3-seFeTzqqBzSDOsYG7IKx3nZulo33m_El3uNGgO2DNj5u6_vQtFuJsqXNlv7osE3Q47eiBZhuC_8lQr3dxVk1yGrnAFIAzere6UrFZyBVilDB8IIQlvS298Jujbsl_Tf6OYTwDI1iQw-QEy40hjrEqHWcQVAj-uEFWKBdztje04Aa8nY-s8HbDzAMee_0m4Elnl20T4f7lx_kXoFZL_y-6UU65HXp7xQ8x5jPumzd_frLyR1WFHyLhjdZ1TGAawAUPs8834mhO47CMuuwRDSna7-AGL-kSTEPzN3vE_FJr3Y-01NvXX7ta_YyL082-mC8boXTV5jb1qWkswmW_gM_xma0KZydoXIKUyBdYnuKC7qNzIC_z8vTh-l3vNMafboLFLPER3owYbGyTpjbSsw0ndPHpnzbh6yAkIswtr_z0abccXE8K5XVmftacccvqwEoOq9psX44EHOaYu2O-o1vwlZFUFkgDfeO5RnVxMYf5CVs9fbgzgsoKyG5kgob4lmqBRSwxptbeDcCYSfVtTfxiXQn-ejRYYZKQjfcAapblTnC0GrwqNdtogj1dsobVO1-qEAO98_AI5XyJNke9PJIc6wTHZPDdz6VFaoPebK54v_Sy2ZXVXXbz2KwOHYDYT1ir_Y7snETJMjVIGzUZ-tEMdKvAWXSXIMY1Mn7X7uuqKhac66eSynhCXPri4VGpA_D8UBmla24=","summary":[{"type":"summary_text","text":"**Prioritizing communication to Main**"}]},{"id":"fc_089095e18118ee7d016a59d371bea081908646a5e5f34506f2","type":"function_call","status":"completed","arguments":"{\"i\":\"Sending Registry root cause\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Registry-specific RCA: periodic pump opens+FINs a fresh bidi stream every 30s/peer/subnet (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`) but sender local StreamEntry is never retired; receiver `serve_registry_feed` returns on EOF without FINning reverse half (`dispatch.rs:984-1046`). `retire_stream` only hides receiver row and deliberately never removes physical entry until conn close (`nethost.rs:1826-1854`); long-lived pump conn means rows accumulate. Worse `stream_infos()` filters only retired, not initiated-local (`nethost.rs:1805-1823`), so dispatcher asks at 100ms and broker serializes/clones ALL historical sender rows before client-side initiated_local skip (`dispatch.rs:455-469`). Thus sustained O(total feeds) IPC/I/O, not O(active), and fresh Registry replay/poison can coexist for failed unretired receiver rows. 550 threads not explained by pool alone (MAX workers=8); add conn-handler/watchdog census. Target regression: real long-lived pump+dispatcher, N registry-only rounds, assert eligible enumeration/physical stream count+broker conn_handler_count plateau and post-refresh stream-sub-attach Registry count is O(active), no poison/replay.\" ,\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_jLI1v912GxxscAq4vplBoZ9L","name":"irc"}]},"duration":14153.32959999144,"ttft":4835.582900002599,"contextSnapshot":{"promptTokens":229329,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_jLI1v912GxxscAq4vplBoZ9L|fc_089095e18118ee7d016a59d371bea081908646a5e5f34506f2","toolName":"irc","startedAt":"2026-07-17T07:02:18.249Z","intent":"Sending Registry root cause"},"id":"421cffa2","parentId":"17c633f9","timestamp":"2026-07-17T07:02:18.249Z"}
{"type":"message","id":"5f8c0fca","parentId":"421cffa2","timestamp":"2026-07-17T07:02:18.249Z","message":{"role":"toolResult","toolCallId":"call_jLI1v912GxxscAq4vplBoZ9L|fc_089095e18118ee7d016a59d371bea081908646a5e5f34506f2","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1784271738249}}
{"type":"message","id":"f034b6ec","parentId":"5f8c0fca","timestamp":"2026-07-17T07:02:58.385Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Investigating thread cleanup and stream retirement**\n\n**Identifying overflow blocking worker reads**\n\n**Tracing worker thread retention causes**"},{"type":"thinking","thinking":"**Tracing replay loop cause**\n\n**Analyzing feed size and decoding impact**\n\n**Estimating historical row volume**"},{"type":"thinking","thinking":"**Inspecting write_snapshots atomicity**"},{"type":"toolCall","id":"call_4wHreODXu9JcEXtXssqMWAgJ|fc_089095e18118ee7d016a59d3a2319c819082d4a6fae79966de","name":"grep","arguments":{"i":"Locating snapshot writes","pattern":"fn write_snapshots|write_snapshots\\(|snapshot","path":"crates/spt-daemon/src/registryhost.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_zXUEEiEeo1H40wo4pEwxqeol|fc_089095e18118ee7d016a59d3a231b08190a1fe732b5325c19a","name":"read","arguments":{"i":"Reading snapshot write implementation","path":"crates/spt-daemon/src/registryhost.rs","selector":"630-730"}},{"type":"toolCall","id":"call_5nCxaAUX7ECx8utFEECmgfAL|fc_089095e18118ee7d016a59d3a231c4819094bce5107113845d","name":"grep","arguments":{"i":"Finding advertised row payload fields","pattern":"fn advertise_local|read_to_string|read\\(|load|for .*read_dir|instances|resources","path":"crates/spt-daemon/src/registryhost.rs","selector":"360-640","case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_U3UhBSWmVHUaEMcVNrWe510l|fc_089095e18118ee7d016a59d3a231d081909a7654d9b704a791","name":"read","arguments":{"i":"Reading advertise scan","path":"crates/spt-daemon/src/registryhost.rs","selector":"360-640"}},{"type":"toolCall","id":"call_1bikgrk1z4x5ETjKgdxuzA8b|fc_089095e18118ee7d016a59d3a2337c8190b9e3c711b7f2857e","name":"grep","arguments":{"i":"Finding pump loop sleep cadence","pattern":"thread::sleep|PUMP_POLL|DEFAULT.*POLL|Duration::from_millis","path":"crates/spt-daemon/src/pump/mod.rs","selector":"500-700","case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_pI5enrkImHd4fAzTIlJfgvCk|fc_089095e18118ee7d016a59d3a234f881909590af3b12960e33","name":"read","arguments":{"i":"Reading pump loop cadence","path":"crates/spt-daemon/src/pump/mod.rs","selector":"500-680"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":824,"output":1444,"cacheRead":228864,"cacheWrite":0,"totalTokens":231132,"reasoningTokens":1105,"cost":{"input":0.00412,"output":0.043320000000000004,"cacheRead":0.11443199999999999,"cacheWrite":0,"total":0.161872}},"stopReason":"toolUse","timestamp":1784271738253,"responseId":"resp_089095e18118ee7d016a59d37aa2dc81909c894dbd580ef0fe","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d37cc34c8190962e6894b2dabad4","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdONXxWQtucUyx9tXhkUj3wtdg-TO2wsF-Qmn4mw2880ttlXsY44RGItXQMQUc8Rn7nRmy9qjleMlLgDxwDi2pW-evOswXIH3GKo1chw5IBvjUC659iGrYqnr7Eo876kjIbzuj-cf0TyW-xUSalc692rbj3U7xvrCMPpSpeGFbD74dTvkn59n9sg9eGL2fJlrDV2q_oEgo2ahcv3nGM1_ffSn5ygwZtiOnPa9ITZltgWg6eOv0RVcy9K1iArZikWNnXHR8jGFXHuD8Eoz_oJ8HoS3LxB24FUHuKApG6_NooIopnGNvDxwudWgPOdW8QnVEOTaJep5lBZnEw4jvdtrF2AaZDXFEcPsWPGNhOsI1t4roo2atYBjpTGcx4gQMTHurN4f_YsnPy9cGXgX1TPkiWPsOrd0EhhQiWlTJBhoSSk7df72YItWJZuq_KvHGu92dCxIZ_x3Eq2_USKXftDSn2XWtWC2hPyUB9BqJsq5wZUl0CHisszttpiKc3M6XSgPgiFFTongcIRE3tBaK-ZLVN6E5QipXjbBFdo_rzqBGmetd-EKN-XPv549UTHGt1XGM2SO7x5w8LnQwOw-8mYFowV6baVrfMKk50XeGUcsPuR0lRRInQwJ5dngcU3kiPlVbw5PJ9gtSpyCgMsgZqHMrBcsaCAkBMWsOGE-4cg8tgAsAl05yDS94ELwUVxYnBVRw0O7fRLxFESNGNz3nIxMz9eq9Vwy6NzGyyPs0gS_lFDVUvL8HQwM3G1IAhIQL3htil12LcWC4iGPlIBWmrgAkx1VVM9uQ4nMFMFrFUN5bhrYATNvE0aa2HEs9tzbJlJLi0Gqtzi-zd_T9_6yX39DdzA7XS4DQzq2ysVaC9Dst6lEHAOyoxk_x6ZuJZolex6l7ot9-fJOXt5ge361gXYkqYHqxQIbxb8238j-K6DKQRg-471Ros_j8XT-PTQxe9eMO_PnwUSYxzu8vMq3tB_cZvqbX-zQSuCpMxaa4-PXO4I2DyyyfsJOBlzmvUxt49s-m1RNl4xlAyXtWcEXFpX2MqGmDKRVD8qHGMP4D2VpG4PX-axvC6YRWSk3smedOLvmtieW_kOgV-UK9pHpCy_lo0sbWKNUqKEpfMwraOc5yixRq7r1q5OXgbUbEsymWUED3BLdZFIHm7TLEJ0nlR5qC-cULZptr-JxQeOyh8Mh9UhD_RUfv7fU82kBFopcihiqp6W1xdfDyTamDZif2GV_CnOU0Rxg6rw8dLEOF1NH8U2bILK1EvdXxwAHMk6YUpjk9Nfq4k7AvSc2VHAcUz51bkL2Kp3ULywMQuXqg2rFMrSrMzK2rN98fUDXL1vwbbAAXa2jzcG6ZgbYDBuygmumaOeKNYOycIyK0AkrP1dmMxZddpbVqJrTvwtJ1iAv3yQoF9JNY721rKI9DBKEopuFB9pWn_ptSs6-QD87QOYuQfsnwOV0dRAP1IRE1TCmZmdAMAzGYFC5iWQcO_4c2dCBi3SE64HqPbxZqu5gP__tU0l6rTvyjpAp-TLywb7MtbX3ZOwguDhTuxI-gmlLN-M4y9peUtIfSPs5fJTeruPDHTz9QB9RThyPGmrcVwdfFlZ3XAAcVrdBMId0zusUwfLtSqg4QC2lGmnRRkHPm-Wvw35qdkY0ux3nG-mNfaWNN91ZIuIjPOj5KOScVQzvpQJadEruCuFtN50uRpyc2-_38QAmf2fbk7ZzrwoTOtbK4lVv_2LL0Ym2Xr7Bc2-OPe-jbUAMju8FPVdfdUyU4NLZhEAdr2xkLR8hHNjh5y7YZ0PCLQxyc9g_Mik5XAI0JEFq5TON8AwV2LUim3QCpGq6NyocGweqvj967UnBIsixUOTI_jdgEBzd2hc9p3HPbpDUIWfmZq6x1B12WIB5Zr6hJtlV7RWG_3c2FB_kIVtMorjzNa7MCf27jqyf-sVgnqIyF7VVTmE7ZeI3_y76AYXbWDw1LXCymZHo6T1202FIn1kBx-42qblokeAe8jske_pnwiCdmeFz2pUHtXr5z36ZBQD6vT95LXvcO7hhzy1ZBkQH5JFeplzBsqS3Q6VfcdEablum3xgcQoUttUq8uHVVZuk7pNK5fKEvy2i_jlPRwgR4eG7rB9dH827FycZ8eT2vBHN9c78pkZ-WiymXMx1b9HUydIlTBURZpSs5vIlUMpXBmij5kVHOmxJ7kUFnrgJFSjpMMEG986JLOcS3khNXy3gFNh9MoIGy9FiB4MFYNhtDd38m2uLUpNmfXVObKiybLby_H2ff2EicEmf38VFXjrBkEjUqG-fAvZztPIZ2E7G3UxxfBOHdHzNSMx-A5dt29eLQAruiEaLEP21oMisByZGH9MTAIPTedIBdM17Vmyn3DnhMFHSWc0DVaMGpXm8lh7C8bTZ4DBIsWS5dPL8FgMhe5Us5GK-85vcK8NJgjr7AEuo5bSR4mfuZmIx9PWEz5aMPoivGGv_zGKve53s4HRNNo7lqwnIxfSeWFT80B-Khi6EPEIy_6TvpElwe4rkoaQ1b6jTkHajGOQBURJPE29vtg1YyJT1kGqNOHFtPW2-Ftc_vi5kLk-ocdQbA4SWiptRw0G3j1E9qSu9Sgxqmgb6mLotxQBkDOczHSDZZkL2QGmuj1OYKfGObLZvJ5AaEnw7wzMefL4QMyHcRqQ9c5z6J8wcL6_lT4lF_HQ9es5M8GzYsE8leDDyGFHbFLDjZFxbcguY5rRf_hsuE5-_gOpx04r0_WWYENyubItd8nBzw3avrzKwcnjo1BgkWK309xhJv5Aj70VJv00PaJtyCOcsPjJ7yiAZRnOfkcMnS1A2Wq3HyuPLn47P-uvJYiKI_0YPcTtH3FjL3Fbsp-jLOoPU7l7GA-5B1xjPYK460XOvmuG3dMmn1Lu6-8N8todyBGt9pZFiRGDQWXtdVPeL_TZalNBpLGGDlEnY5IO-BgP9QbTSUtZl7W7AqieNcvLQJLC6XxL2YErcIoJVbhNSwTgX3FXXmKEBtQJQjYTRn5dU2GbVzPrlWHTGj2EQXBHgKJIALeArkqBmeHlrPnxISkoolBFr_N6OBDbqGOWrF2aA5k-dhqetp-Y9gk-PmG94HJho2VeBNWGHLDWuz3mvHwrdMgf3fqO3ZOtOmQ-AbBoHD8wcIYqaIsm6jI4AdeJ08bmtb_ycQswzJsLKFGi_6lYVJ-afbV0nkBNku5YrC93UBcF_SDIjq5mDfB-7VTn1ew92dNjtxZevONvHn8wrLKFjuRuyDzJPY3alcHNg1UvlIHepW8TPRVedll3vCljRaURBVxXCuh63n2AZa-8PlJZvDcYo2hwz5HrDUIjjRoqH0jYp7nt2INY65BK69udVzqSmdphjjQFAKOI8Gnv6pkJhsEt-yH_lJZ1wB-jjqEbHz5w-jyN_J2H2b7AuWs0Ndd9uJjr4z7e4ZgACia7_yOLSYcEGqWPcvF-S5tD9HdyJVY_jlyxDf9Yd5wbUTrzGzXvj4cunzsDEYtGwymZP4Td8Rksjdc_xFECTHLYoqI5ighblS2DkEUfTmtZJfKOg1E80GK8ILBUGMzZWd8TT8Feg6kB8R1LIVvX9SNsuUufB0wX8pifE4zcX30PZRRIel4iuKi8k-rO5o_fx_5MS-9vsYUUPwlGBmavLgy5eJi8Wn4hNXck3qSJhGHfkM2XzeFr_prBfxHjqS6ruQghSN59b4Pwv9LHbD89GmIq-NHuy6WF9xUDsME2Pwb4fPUXwjU4Oga2lQzKdRq551o2fJVL0Thrq10eA7wAdI-z32X9X1tsyV9QXg8Sws_skBdgTBymuTz6g2AX7uByx7wrWDph8F2ZtgVpoABHcMdq7emPhR5itcaBs1TE_cvQqQIQq_5JYwKzNyWnM2A2n3UPEgaQUYbff53H1KZRBX-OCVnCeIl_XvcQZADF3iOuY4YHMyYg0q7V3CQQ7SZIHUOdNep_jA2oRsybRpbM3GDYOVxqlZ7_db6cy5A2YBxrelaCcDZy_V9W046xff63az3NVVbRUdeaPRMRvh2TWPbyvu_XyGp2gWKHiqKqAknIRzbuypa1Y6UpibGUNEnwDxhER3hrdIvlPYpgWKRghPH0I9f2yo5vFSCNZf6Xy51hBa66009bbby93Dj8_iyGa0CLpTSufmCJ2jRTzGR_ae_-77n_dF8_XdZf56kKYgiO77-dsz4n9AXIQr5gzD_aBVS9qxqB8gDNs61FNHVanSRpho0ZAsVJ0B5a9h5KLsUceHrnhCONK948DYE6NxOf5ewvFpGIXkZmz7dGKgeukJYfXhMPsWfzxDRYEDGgdIfFGZsBwLmHsSmZ0dR6iMeE3xLb-wZX3cwldFpgYQObw74XUFFIJURMTJA4SAOh6uoqpCthvi2VwGg==","summary":[{"type":"summary_text","text":"**Investigating thread cleanup and stream retirement**"},{"type":"summary_text","text":"**Identifying overflow blocking worker reads**"},{"type":"summary_text","text":"**Tracing worker thread retention causes**"}]},{"id":"rs_089095e18118ee7d016a59d38d49a48190936fd8ee150c9fca","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdOacYv8l9lmxb9sYyCV1AJlrhBKcshU5QXowU2NetwEzOlpyBpKclbk9d3ET93nlzKz37SIkXb5OHtKPqEEozmk5tfoZa9xg6yhB3qaNb_6qTMDbMJSZdP5994ptilcFMREtFhmjUCGsZohBQGbMkcAcEfHf_-IV4V7nHGwOCWW_X9NUsd-hyEHuNCyjq8rapNcj0ubkLwL2XKctnDCEVpOdg8wRpFCb9ma0V447OZ3CScS8COikgNtRi-RUWXFxEj2740CR9Gv-PKu0vCCGe157CsFI1VE1UCKsNsLaFAcndvj5zqXsUvmSdbaSc0tei5Fg8zEMMTlsMWzVyIe4oAvKhm8aKLAlvA68PoeiAaXSn6CF_0CZNYPe36loZMcZxO-E99ft9a8UYVl-ECm60RVzWqF-VbwcsY4oifm94zi9zBY-X9uaRSNbb7B5eUTEl-c-3DekOW9HKgiIg1N73FAGuOp1DBPfRdapJBGG2T_WHkhuq_UhdYzty5wTN15V12vj2BPUpTDe5k14wfvaprm_kQUa6XI9mpnt91WRZcx8Ae4Gj1uq3h2BRy0ivZKzgVdMuGcBzlBcAXLEWR-nVXi6W1uMoZFjQneMtDo4LeclySj4mcnQY2ZWGRngIPU1CMXjEYkrBqTQmH9gMSL7tDJnbNq5Jx-11MBwZ0Jw57lfKHMUHWwOkR97Tt6ZxaRbIHzA4Ye9w4xTIXyw_rpirAL2tTiams9rudCBdffHoKIIZP9XsDLU2nJ55rw65xiLVhgMiMr720vc3VDLhxoGkBkAYui00QjLr1cW77M-bBBT7oaV-zk424tvujp9LKx1nFJiIzimHL9Nn-kKC4B5p1PgN-T2P-Z047yD6Jr5Ub6oh-GeyAefwoyBhmXnU1JEUzolAebrVX6aVUyVp5FGSiVfErs2P1AMvPkUGYEGTFPceOBhmnm2HRQVJvIpchVbBeoou5UtPfq9lCtih7k-VEv5ucIVZAU-aUUR8wsnNt77vLf_6OBOvSdL7kqmpYBDsm1OEVXO8iDWHFyM4bPhIccjgSWf2lY24Z5D3G0ehPHJZ1ohhxKgGz1lw2koeExJkboQkW6KWqGqht0uHntHonXEiofoyIVuEOLpkeK_QFgW411IH1dlAUPkDVi2KauTZ8_hz_Ac201wkmvOJcKmiAwT7SuDPUzsTBg4l9vOVdI-XrDvhHTbCahUiRPDw1uy65dAjhfLSlEF_378nlsQbZatxj-Qh1tuSg5m8UokK_kJ8cNoXmv8dH0GuF_WdLU6qy5Wwvr4oIJzC1BjC6Hz8LTe9GS5eBjaY6deP9eD0hgv038-uoQ66Q3FPRq1ruYymlaMOThZgLxXmpBfnYxHQT089g46lGCh4fptAfEjbapKcXBer_b80XzVcFUT7eyapFGMFjk_9JhZ0U8gbPQP1QzKgS-ztL1z1bmkpCJ-wO4_Gz4gKow8NKTqnUcCkhpJA0FjPrCa6ZaFOtJJc2BzyiWPSCo3v7UFxvGV-hm3xuLzUP1LWJ2nHiy2AybOIbfCLpRpC8JxhD_rWQE03_H1G2I3JoTuQTzAF4qHemutHVOLnA3qjoqytBIvKbuUZS89TwKYn8eoa8gc3n2UYM66CGA1ghFKbr8gedjEOBj3ch-5hjwgwB4_pQjyaLJcmNAxyA8zSqGRthGJ5mH99_Y-bcB0zwejuWF29yySVxQBAlcYIqGMgd97UlEyqyK-DBQ6hGXoenu8IzEhxvXQLoF0Vm830TX_rAfKSWkoXpEz72Di_vNPzGgs75So-ZhJhJZpgVemneJ3n7uBc4LSky-Y-iMyJocNYIlH_T_ZyyIHG_4ABvQiZ6bvy92sygCeApA0P4iw6dwIy_IpxgWlbUrVsgMMaM4FUY9We6Iz33lIq0It1Wbn28nQpVIuaGL8TVKqAf-oZRqcc8C9LAa85JMqqfEivgehqpQobaQ3Zb-WQ0DcC0NMWRa7ISLJaSDiQpXLK5F3l8hDiD1_rqMfl_MazLrn0yDkyjoeEd1SgqFHYTk0xmSJN8EgqkSi44r0SeYUgpK_J1I4S-3Sd6ExDBzbemiWjlhQGj_KOz1SlrhIxzQeHu9Ip4ixDL7bswHLWfq9Qa1QzlkTm2lWEvuF4vh6uT8AKVaX2GlQ9XgYHFLue7b72CO3QTxdKWQBdpEqAzzD-bu-hoFR3P-z8IPTMqOtjGq15Jgl8CYLVrq7P18MiYAZ8v1qP-HcZNmmJaAca21FilKnYEgR726PzoifjL9ODMx22FZcFitQANPO5fUWs1T8-xYGS7Mhlj317Qau8JOY8wPP0DD1wK3UUqIUi_1rWf1XowMbX3GUvJltDNAQzsaXq81ZBhWFaBp9mD2yejsd92hwHzcKfgLKCudZP-fBi2m1P0tQE8FomPa7LErCXkVQOQodBQcJljqT4bFTAD8QzpVRaCh0_yjQ3Hf1B_2mEhWNYJj4PrJMPSuyUWOA1fZ25UBFyAjeS7xK2wiUfdKnJ7lXCiW7qUivzw-tIH_z6aKWKRCztJxPPcntD7_KN9tdkOzrf8PQs_pLd8t1ZIACkBiBY2wjfEGJ9L6LFfkTaRY1wBFXlgwJeQhn81p0Qd0Q_zAr6cb6TU4kZjuTzKCugEOk-A-niXxhBY8H39ZGTdbr9Jm6C5V0PXuVJHjfO0aBYsG17GmpcduB1YjTCX3W2CgNFvc_iC0TEQNS_Lf7GC5awWjOSXw08-w49CmPWJQnaHAfslwgsaX0hHuDQ0pbIiWNu-rbNx9MnwSNeqic0qoXtDi7NArYFGVZG_QMHxyvKFGrcOgDK-os5rhgoas-z-4Q5hnF5Rue82m5lGVAJ2ugOpmw2VMyA28OdhALEZkKFwLYdjeep1m8u--Tk93Lip3VheYUbnqLBeVwEDYagB-Kbu-3bsaLlBXHmFPQOwStwIm8MgHPE--caqqxYbHfI4YHSODQDyYZoyrvOvVhViwEfvejsArz78SlxIwL5EwdRnTt-iZVdEaRi6jXRwf73YI2_QmnBBELJlWFslHIqTqWHCPMGiCo2OGXCwmyg_wcRBkJsnZ_zLYoXlI2aliGDShT5oDA6CIXHB5Ih3o3X3jzrpTLaVPon08OvP-s0OPrQMwJ6kfC1sbt--uYYggWgGfx_j1PhIsqokXoBB1LR3dmaX3YI1jAMs-CPw5xZuI1fno2huFj2oca1bAzZ3OZc5sfTbyE8Ot5OgRiZ802tAkipGMCS85HpCMHt3cARU0oN9aN4XtHUFB_SdqXjgjcFdkA8-xqyroPubrP0LlaDG_zA3CxAYu0IIR8UPhdBIsklJxnZJpzOj5zC1UwlKoAVsiMJZymS-ZXHuCLhYALtBhPMuxpe00vXuWE1XN6xuhGkNrKJjiVYwf4DK3P7Qy1T-Uo6naUrWIXLC77V1nD3PziNyEr-rVbqYDwS2izz0pz8zzajefJI_-2QBW9Otky3kzyNGQHBC0uZM6SwO2jvhLBZIDDM9qJq9W880ywX-djmVQjWnNDI7KBvhUOSqVUF3ZGdrHNomwnFcDAt6Dp-B59QfBpxgt5ttp2kTgudiNgT1GZJu0CyQNStr7KMBH6_q0lx4pXZW_qEa7slTm5AMZaT2lxlKEzvPEVYizLaQoYk1EWJeCB1IfRTC-6aGqtc4qRfkccphrG1o5yRWltDlxAUculxy00o-zLWg5fX5n3QLjD5IEeI2N79JqqRlc0TQO94DQ-h6OryU-bDsYfnyvqHOnhQ1UWvV59bftoN5FXVAhYyCmpqmkZgpAeblfje55WZQdtm2euffKeyH3CVGj3pnspXEsQvcp8sy6iuKBFiwfGVVewIDLbIDIc1DaNWH51oHZMpsgndYaU83ADrmJo4tLi_DxW6ryDbIw3QzZITATZvMwOwZTAbzdnuNwMgkqI_8ZMYmBYdoTArwZnj2P_idiIXs8UuFrW8wM1BpMfZbKRs-6suHpSOxG470YzYPGNDiPYWUfBBqZJAiB8tPjE_AdlIm3sAoi_pRWntB5AVPTZScaQNDd_7bOejF7NAFbP6VdTvA7yvCB6djdqxucng==","summary":[{"type":"summary_text","text":"**Tracing replay loop cause**"},{"type":"summary_text","text":"**Analyzing feed size and decoding impact**"},{"type":"summary_text","text":"**Estimating historical row volume**"}]},{"id":"rs_089095e18118ee7d016a59d39af2008190ab082c0570316be3","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdOimBR0y_783bdrZdCFao5GT1kL7jfl0rGxuvvjzQfUZIalz7VPr46ml4M-VzO13KCHYOWJv3XTDnP0NmREaw4KFjY66Fp7P7E21RVHUVpEyGezbpX2cWykAQAdMiCLD5TEzW4HBdP7WUdUOOPLl0cTgHc02KfBtHXYi7ldA6-zJxn32qgLaLE9Cy7gxGwDpigvpwgKNsZp9GgGv4eUD4e3TlcsuSEwh3YhZyNjRdYMJnfdM1DRDkPvBFvkKudokUNl1JIzZFbcysXrQVHlnUiWI1e8otfFLGP6IFocHPUFfYBHb-kesEdgxJBGGXK7XcgY0E20PACPGwXxeKl5CnNssRzG6E2cFCWj2UsLjmcpVqGKS6ZdYNdJB0SNoeeZJ9HOKJJux_-OjtpONy9dMRmByJY-x887BDmBMlfQeIJcEeVGnyA2ACPI_u7LeK3amfH5aok7SI8dIJtzvYUMyTybZy9y6a2YBm7gorzPIJPwh9w7rirlKMIhvlLt3szsGo6sFRGXa3cKyrkAg-zlT90YLF78Iu2iD9HisPo6gspJPE1bHpl435GTcxIpAccaLpwq4qZiYw3L5FO7jnS0vSzh_dSYuZTLeFEjcHF8fD8c3lHNbauE9TApaYp5P568AN_Ql8s-UXHZ3tDXXSgDxvJIvWYvLEQbNIxBSSNcUmOQw4xC3k3lU4ge-Lw5PuWBR2kBn4Xa514YaXNDC6Cdg4AlVJtjd6B-VO1zZ2ZwajCWJ08geVV3iwn21Kl8DsNLL6TLLtIgwiJ9CoASMYY9LlT_x2r-OF8zhkXzchsgEw6xtwrkpKb4h-UP6CCa0WQwfPsUy8sF-1eTV-OLq6boHlWThRS1q75KF021DLXil70nX9bWcVdBUTif5yuT8J10B9ifDBUCfuD8HcYU19fP-HiPTnMRkvFDagkDHMjaPugK-Ihkc3FsCnLmN02fWeQUdPAMcFU-6C8D1Wz9DbIAvZH9wOXkkEsb9yRZA4siisTe6h2Nteh69caQlHhPJ3oV4LcL8lwB_pzPjXHHFElwttTKnoXt2EPvUmMpGvZjjzwxSmK5qMmcOcngXAxe6x-d2Prz7Ad6ohR1ag27ydPCoqHunZ7yjhjfEE4gec_kuvgorg1CnubhrK_4ZRI4zrMdalf_aLaxbpW1l4h5-eBVHfKE494jDUfj6vwcnil_5AFA4jhdbQtavr14t55Xv8BGctiz8hHa3Qki0YRlYKmXhAAiyFIVN_drs3XNwtciH5ldwTgaWsgrEGtGZxlRMlyfNmPNm2x_ZYx8sZxYasHTmdMIASHeKPRHuORSwLSjHZYkWpqjzM82cBi083L8EgoD_j3heexkKKx6qErUf3NWRq0YJA==","summary":[{"type":"summary_text","text":"**Inspecting write_snapshots atomicity**"}]},{"id":"fc_089095e18118ee7d016a59d3a2319c819082d4a6fae79966de","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating snapshot writes\",\"pattern\":\"fn write_snapshots|write_snapshots\\\\(|snapshot\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_4wHreODXu9JcEXtXssqMWAgJ","name":"grep"},{"id":"fc_089095e18118ee7d016a59d3a231b08190a1fe732b5325c19a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading snapshot write implementation\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"630-730\"}","call_id":"call_zXUEEiEeo1H40wo4pEwxqeol","name":"read"},{"id":"fc_089095e18118ee7d016a59d3a231c4819094bce5107113845d","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding advertised row payload fields\",\"pattern\":\"fn advertise_local|read_to_string|read\\\\(|load|for .*read_dir|instances|resources\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"360-640\",\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_5nCxaAUX7ECx8utFEECmgfAL","name":"grep"},{"id":"fc_089095e18118ee7d016a59d3a231d081909a7654d9b704a791","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading advertise scan\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"360-640\"}","call_id":"call_U3UhBSWmVHUaEMcVNrWe510l","name":"read"},{"id":"fc_089095e18118ee7d016a59d3a2337c8190b9e3c711b7f2857e","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding pump loop sleep cadence\",\"pattern\":\"thread::sleep|PUMP_POLL|DEFAULT.*POLL|Duration::from_millis\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"500-700\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_1bikgrk1z4x5ETjKgdxuzA8b","name":"grep"},{"id":"fc_089095e18118ee7d016a59d3a234f881909590af3b12960e33","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump loop cadence\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"500-680\"}","call_id":"call_pI5enrkImHd4fAzTIlJfgvCk","name":"read"}]},"duration":40103.787299990654,"ttft":2522.531599998474,"contextSnapshot":{"promptTokens":229688,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4wHreODXu9JcEXtXssqMWAgJ|fc_089095e18118ee7d016a59d3a2319c819082d4a6fae79966de","toolName":"grep","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Locating snapshot writes"},"id":"f48d0725","parentId":"f034b6ec","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_zXUEEiEeo1H40wo4pEwxqeol|fc_089095e18118ee7d016a59d3a231b08190a1fe732b5325c19a","toolName":"read","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Reading snapshot write implementation"},"id":"7b814290","parentId":"f48d0725","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5nCxaAUX7ECx8utFEECmgfAL|fc_089095e18118ee7d016a59d3a231c4819094bce5107113845d","toolName":"grep","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Finding advertised row payload fields"},"id":"957596d5","parentId":"7b814290","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_U3UhBSWmVHUaEMcVNrWe510l|fc_089095e18118ee7d016a59d3a231d081909a7654d9b704a791","toolName":"read","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Reading advertise scan"},"id":"5f8ca40b","parentId":"957596d5","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_1bikgrk1z4x5ETjKgdxuzA8b|fc_089095e18118ee7d016a59d3a2337c8190b9e3c711b7f2857e","toolName":"grep","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Finding pump loop sleep cadence"},"id":"8d400458","parentId":"5f8ca40b","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_pI5enrkImHd4fAzTIlJfgvCk|fc_089095e18118ee7d016a59d3a234f881909590af3b12960e33","toolName":"read","startedAt":"2026-07-17T07:02:58.386Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Reading pump loop cadence"},"id":"04883ac5","parentId":"8d400458","timestamp":"2026-07-17T07:02:58.386Z"}
{"type":"message","id":"488d8415","parentId":"04883ac5","timestamp":"2026-07-17T07:02:58.392Z","message":{"role":"toolResult","toolCallId":"call_4wHreODXu9JcEXtXssqMWAgJ|fc_089095e18118ee7d016a59d3a2319c819082d4a6fae79966de","toolName":"grep","content":[{"type":"text","text":" 23|//! consumers (`spt send`'s WAN resolution, D9-1-4) the host mirrors each\n*24|//! subnet's registry to an atomic JSON snapshot under\n 25|//! `identity/registry/` — readers tolerate staleness (the 4.3\n 26|//! stale-clean posture), never block on the daemon.\n 27|// [impl->REQ-INST-7]\n...\n 111|    /// a probe; only stale rows get dialed. Local-only (never on the wire);\n*112|    /// mirrored to `heard.meta` beside the snapshots for the CLI reader.\n 113|    heard: Mutex<BTreeMap<String, u64>>,\n 114|    /// Wall-clock ms of the LAST admitted inbound registry/label feed from\n 115|    /// ANY origin — the \"last admitted registry update\" health field\n...\n 122|    epoch: Mutex<EpochSource>,\n*123|    /// Where snapshots land (`identity/registry/<subnet>.json`).\n*124|    snapshot_dir: PathBuf,\n 125|}\n 126|\n 127|impl RegistryHost {\n 128|    /// Stand the host up for this node, with the canonical epoch counter and\n*129|    /// snapshot dir under the spt home. Reads the OS hostname here — daemon\n 130|    /// startup IS the label's re-check point (REQ-SUBNET-3).\n 131|    pub fn new(node_hex: &str) -> RegistryHost {\n 132|        RegistryHost::new_at(\n...\n 141|    /// Explicit-paths constructor (tests; the pump E2E gives each in-process\n*142|    /// \"node\" its own counter + snapshot dir). No label by default — tests\n 143|    /// stay hostname-independent; chain [`with_node_label`](Self::with_node_label).\n*144|    pub fn new_at(node_hex: &str, epoch: EpochSource, snapshot_dir: PathBuf) -> RegistryHost {\n 145|        RegistryHost {\n 146|            node_hex: node_hex.to_string(),\n 147|            node_label: None,\n...\n 152|            epoch: Mutex::new(epoch),\n*153|            snapshot_dir,\n 154|        }\n 155|    }\n 156|\n...\n 294|        }\n*295|        self.write_snapshots();\n 296|        (verdicts, flips)\n 297|    }\n 298|\n...\n 341|        }\n*342|        self.write_snapshots();\n 343|        verdicts\n 344|    }\n 345|\n...\n 611|        // than rendering a bare key-prefix. Rides the same epoch lease; merged\n*612|        // into our own registry too so the snapshot carries it. Emitted every\n 613|        // round regardless of endpoint count — redundant-but-robust when rows\n 614|        // exist (the render prefers a trust/instance label anyway), the SOLE\n 615|        // carrier when they don't.\n...\n 631|        drop(regs);\n*632|        self.write_snapshots();\n 633|        out\n 634|    }\n 635|\n...\n 638|    /// within `ttl` has its rows removed from every subnet registry and the\n*639|    /// snapshots rewritten, so a dead node identity can no longer poison\n 640|    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\n 641|    /// node authors them fresh each pump tick and is never in its own heard\n 642|    /// map). A row whose author has NO heard stamp is treated as silent —\n...\n 676|        if evicted > 0 {\n*677|            self.write_snapshots();\n 678|        }\n 679|        evicted\n 680|    }\n...\n 712|        if evicted > 0 {\n*713|            self.write_snapshots();\n 714|        }\n 715|        evicted\n 716|    }\n...\n 729|    /// A point-in-time clone of one subnet's registry (resolution input).\n*730|    pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {\n 731|        self.regs.lock().unwrap().get(subnet).cloned()\n 732|    }\n 733|\n...\n 735|    /// (tmp-write + atomic rename — KNOWN-HAZARDS 5.2) for out-of-process\n*736|    /// readers. Best-effort: a failed snapshot write must not fail the merge\n 737|    /// that triggered it (readers tolerate staleness by contract).\n*738|    fn write_snapshots(&self) {\n 739|        let regs = self.regs.lock().unwrap();\n 740|        if regs.is_empty() {\n 741|            return;\n 742|        }\n*743|        if std::fs::create_dir_all(&self.snapshot_dir).is_err() {\n 744|            return;\n 745|        }\n 746|        for (subnet, reg) in regs.iter() {\n...\n 749|            };\n*750|            let _ = atomic_write(&self.snapshot_dir.join(format!(\"{subnet}.json\")), &json);\n 751|        }\n 752|        drop(regs);\n 753|        // The heard-map mirror (M7 D2 hybrid liveness). `.meta`, not `.json`:\n*754|        // the snapshot reader globs `<subnet>.json`, so this can never shadow\n 755|        // a subnet (even one literally named \"heard\").\n 756|        let heard = self.heard.lock().unwrap();\n 757|        if !heard.is_empty() {\n 758|            if let Ok(json) = serde_json::to_vec_pretty(&*heard) {\n*759|                let _ = atomic_write(&Self::heard_path(&self.snapshot_dir), &json);\n 760|            }\n 761|        }\n 762|    }\n 763|\n*764|    /// The snapshot path for one subnet (the CLI reader's contract).\n*765|    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {\n 766|        dir.join(format!(\"{subnet}.json\"))\n 767|    }\n 768|\n...\n 778|    /// consumed by the pump's registry tick so the IN-MEMORY rows go too\n*779|    /// (a disk-only eviction would be resurrected by the next snapshot\n 780|    /// mirror). `.meta` for the same can't-shadow-a-subnet reason as the\n 781|    /// heard map.\n 782|    pub fn repair_evict_path(dir: &Path) -> PathBuf {\n...\n 788|    /// from the in-memory registries + the heard map, and rewrite the\n*789|    /// snapshots. No marker = no-op. Returns evicted row count.\n 790|    // [impl->REQ-SUBNET-7]\n 791|    pub fn consume_repair_evictions(&self) -> usize {\n*792|        let path = Self::repair_evict_path(&self.snapshot_dir);\n 793|        let Ok(raw) = std::fs::read_to_string(&path) else {\n 794|            return 0;\n 795|        };\n...\n 818|        if evicted > 0 {\n*819|            self.write_snapshots();\n 820|        }\n 821|        evicted\n 822|    }\n...\n 961|/// by the pairing host AFTER a completed ceremony: resolve the superseded\n*962|/// identities from the subnet's snapshot, tombstone their roster entries (this\n 963|/// subnet's scope — the ceremony proved control of THIS subnet's code, nothing\n*964|/// about others), rewrite the disk snapshot, and note the hexes in the eviction\n 965|/// marker so the pump clears the in-memory rows on its next tick. The caller\n 966|/// persists `roster`. Returns one [`RekeyNotice`] per superseded identity (the\n 967|/// demoted warn-on-change, REQ-MESH-5) — empty when nothing supersedes.\n...\n 985|) -> Vec<RekeyNotice> {\n*986|    let snap_path = RegistryHost::snapshot_path(snap_dir, subnet);\n 987|    let Some(mut reg) = std::fs::read_to_string(&snap_path)\n 988|        .ok()\n 989|        .and_then(|s| serde_json::from_str::<SubnetRegistry>(&s).ok())\n...\n 1302|    // `is_member` — and returns a machine_id-anchored rekey awareness notice\n*1303|    // (the demoted warn-on-change, never a block). Rewrites the disk snapshot\n 1304|    // without its rows, notes it in the marker; the host's consume then clears\n 1305|    // the in-memory rows + the heard entry. A no-match intro (and an intro\n 1306|    // with no machine id) evicts nothing and raises no notice.\n 1307|    #[test]\n*1308|    fn repair_evict_tombstones_roster_snapshot_and_notices() {\n 1309|        let dir = tempfile::tempdir().unwrap();\n 1310|        let snap_dir = dir.path().join(\"registry\");\n 1311|        let h = RegistryHost::new_at(\n...\n 1327|        // Snapshot mirrored to disk by the merge.\n*1328|        assert!(RegistryHost::snapshot_path(&snap_dir, \"home\").exists());\n 1329|\n 1330|        // Roster membership in both subnets — the gate-relevant state (D5a).\n 1331|        let mut roster = RosterStore::default();\n...\n 1363|        let on_disk: SubnetRegistry = serde_json::from_str(\n*1364|            &std::fs::read_to_string(RegistryHost::snapshot_path(&snap_dir, \"home\")).unwrap(),\n 1365|        )\n 1366|        .unwrap();\n 1367|        assert!(\n 1368|            on_disk.instances(\"ling\").is_empty(),\n*1369|            \"superseded rows gone from the disk snapshot\"\n 1370|        );\n 1371|        // The marker holds the hex; consume clears the IN-MEMORY rows too\n*1372|        // (else the next snapshot mirror would resurrect the disk eviction).\n 1373|        assert!(RegistryHost::repair_evict_path(&snap_dir).exists());\n 1374|        assert_eq!(h.consume_repair_evictions(), 1, \"in-memory row evicted\");\n 1375|        assert!(h.rows(\"home\", \"ling\").is_empty());\n...\n 1393|\n*1394|        // Absent machine id: a FRESH gossip snapshot with a labeled identity;\n 1395|        // an intro carrying the same label but NO machine id must not supersede\n 1396|        // it (REQ-MESH-5 absent-id rule) — no eviction, no notice. Fresh dir so\n 1397|        // the match has a real row to (not) hit, unlike the already-evicted one.\n...\n 1693|        // The label landed and is the only carrier — no endpoint row created.\n*1694|        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 1695|        let reg: SubnetRegistry =\n 1696|            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 1697|        assert_eq!(reg.node_labels().collect::<Vec<_>>(), vec![(\"bb22\", \"RENAMED\")]);\n...\n 1984|\n*1985|            // The snapshot mirror exists and parses back.\n*1986|            let snap = RegistryHost::snapshot_path(&home.join(\"registry\"), \"adv\");\n*1987|            let bytes = std::fs::read(&snap).expect(\"snapshot written\");\n 1988|            let parsed: SubnetRegistry = serde_json::from_slice(&bytes).expect(\"parses\");\n 1989|            assert!(!parsed.instances(\"vis-ag\").is_empty());\n 1990|        });\n...\n 2033|    // (recently-heard = alive, no eviction) and evict once it falls silent\n*2034|    // past it — snapshots rewritten so the CLI reader (spt send's WAN\n 2035|    // resolution) stops seeing the ghost. Idempotent.\n 2036|    #[test]\n*2037|    fn evict_silent_peers_drops_unheard_rows_and_rewrites_snapshots() {\n 2038|        let dir = tempfile::tempdir().unwrap();\n 2039|        let h = host(dir.path()); // node_hex = \"aa11\"\n 2040|        let p = policy(\"home\", \"bb22\");\n...\n 2071|        // THE field shape: the dead identity's sergey row no longer poisons\n*2072|        // resolution — and the snapshot mirror agrees.\n 2073|        assert!(h.rows(\"home\", \"sergey\").is_empty(), \"ghost row evicted\");\n*2074|        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 2075|        let parsed: SubnetRegistry =\n 2076|            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 2077|        assert!(\n 2078|            parsed.instances(\"sergey\").is_empty(),\n*2079|            \"snapshot rewritten without the ghost\"\n 2080|        );\n 2081|\n 2082|        // Idempotent: a second sweep finds nothing.\n...\n 2099|    // ghost-heal re-advertise) has that row decay once it has been non-routable\n*2100|    // past the grace — snapshots rewritten so the remote viewer's count stops\n 2101|    // accumulating ghosts. Within grace it survives; the sweep is idempotent.\n 2102|    #[test]\n*2103|    fn evict_aged_offline_rows_reaps_ghosts_and_rewrites_snapshots() {\n 2104|        let dir = tempfile::tempdir().unwrap();\n 2105|        let h = host(dir.path()); // node_hex = \"aa11\"\n 2106|        let p = policy(\"home\", \"bb22\");\n...\n 2118|\n*2119|        // Past the grace the aged Offline ghost evicts and the snapshot is rewritten.\n 2120|        let later = 1_000 + 300_000 + 1;\n 2121|        assert_eq!(h.evict_aged_offline_rows_at(grace, later), 1);\n 2122|        assert!(h.rows(\"home\", \"ghost\").is_empty(), \"aged Offline ghost evicted\");\n*2123|        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 2124|        let parsed: SubnetRegistry =\n 2125|            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 2126|        assert!(\n 2127|            parsed.instances(\"ghost\").is_empty(),\n*2128|            \"snapshot rewritten without the aged Offline ghost\"\n 2129|        );\n 2130|\n 2131|        // Idempotent: a second sweep finds nothing."}],"details":{"scopePath":"crates/spt-daemon/src/registryhost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":52,"fileCount":1,"files":["crates/spt-daemon/src/registryhost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/registryhost.rs","count":52}],"truncated":false,"displayContent":"   23│//! consumers (`spt send`'s WAN resolution, D9-1-4) the host mirrors each\n  *24│//! subnet's registry to an atomic JSON snapshot under\n   25│//! `identity/registry/` — readers tolerate staleness (the 4.3\n   26│//! stale-clean posture), never block on the daemon.\n   27│// [impl->REQ-INST-7]\n     │...\n  111│    /// a probe; only stale rows get dialed. Local-only (never on the wire);\n *112│    /// mirrored to `heard.meta` beside the snapshots for the CLI reader.\n  113│    heard: Mutex<BTreeMap<String, u64>>,\n  114│    /// Wall-clock ms of the LAST admitted inbound registry/label feed from\n  115│    /// ANY origin — the \"last admitted registry update\" health field\n     │...\n  122│    epoch: Mutex<EpochSource>,\n *123│    /// Where snapshots land (`identity/registry/<subnet>.json`).\n *124│    snapshot_dir: PathBuf,\n  125│}\n  126│\n  127│impl RegistryHost {\n  128│    /// Stand the host up for this node, with the canonical epoch counter and\n *129│    /// snapshot dir under the spt home. Reads the OS hostname here — daemon\n  130│    /// startup IS the label's re-check point (REQ-SUBNET-3).\n  131│    pub fn new(node_hex: &str) -> RegistryHost {\n  132│        RegistryHost::new_at(\n     │...\n  141│    /// Explicit-paths constructor (tests; the pump E2E gives each in-process\n *142│    /// \"node\" its own counter + snapshot dir). No label by default — tests\n  143│    /// stay hostname-independent; chain [`with_node_label`](Self::with_node_label).\n *144│    pub fn new_at(node_hex: &str, epoch: EpochSource, snapshot_dir: PathBuf) -> RegistryHost {\n  145│        RegistryHost {\n  146│            node_hex: node_hex.to_string(),\n  147│            node_label: None,\n     │...\n  152│            epoch: Mutex::new(epoch),\n *153│            snapshot_dir,\n  154│        }\n  155│    }\n  156│\n     │...\n  294│        }\n *295│        self.write_snapshots();\n  296│        (verdicts, flips)\n  297│    }\n  298│\n     │...\n  341│        }\n *342│        self.write_snapshots();\n  343│        verdicts\n  344│    }\n  345│\n     │...\n  611│        // than rendering a bare key-prefix. Rides the same epoch lease; merged\n *612│        // into our own registry too so the snapshot carries it. Emitted every\n  613│        // round regardless of endpoint count — redundant-but-robust when rows\n  614│        // exist (the render prefers a trust/instance label anyway), the SOLE\n  615│        // carrier when they don't.\n     │...\n  631│        drop(regs);\n *632│        self.write_snapshots();\n  633│        out\n  634│    }\n  635│\n     │...\n  638│    /// within `ttl` has its rows removed from every subnet registry and the\n *639│    /// snapshots rewritten, so a dead node identity can no longer poison\n  640│    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\n  641│    /// node authors them fresh each pump tick and is never in its own heard\n  642│    /// map). A row whose author has NO heard stamp is treated as silent —\n     │...\n  676│        if evicted > 0 {\n *677│            self.write_snapshots();\n  678│        }\n  679│        evicted\n  680│    }\n     │...\n  712│        if evicted > 0 {\n *713│            self.write_snapshots();\n  714│        }\n  715│        evicted\n  716│    }\n     │...\n  729│    /// A point-in-time clone of one subnet's registry (resolution input).\n *730│    pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {\n  731│        self.regs.lock().unwrap().get(subnet).cloned()\n  732│    }\n  733│\n     │...\n  735│    /// (tmp-write + atomic rename — KNOWN-HAZARDS 5.2) for out-of-process\n *736│    /// readers. Best-effort: a failed snapshot write must not fail the merge\n  737│    /// that triggered it (readers tolerate staleness by contract).\n *738│    fn write_snapshots(&self) {\n  739│        let regs = self.regs.lock().unwrap();\n  740│        if regs.is_empty() {\n  741│            return;\n  742│        }\n *743│        if std::fs::create_dir_all(&self.snapshot_dir).is_err() {\n  744│            return;\n  745│        }\n  746│        for (subnet, reg) in regs.iter() {\n     │...\n  749│            };\n *750│            let _ = atomic_write(&self.snapshot_dir.join(format!(\"{subnet}.json\")), &json);\n  751│        }\n  752│        drop(regs);\n  753│        // The heard-map mirror (M7 D2 hybrid liveness). `.meta`, not `.json`:\n *754│        // the snapshot reader globs `<subnet>.json`, so this can never shadow\n  755│        // a subnet (even one literally named \"heard\").\n  756│        let heard = self.heard.lock().unwrap();\n  757│        if !heard.is_empty() {\n  758│            if let Ok(json) = serde_json::to_vec_pretty(&*heard) {\n *759│                let _ = atomic_write(&Self::heard_path(&self.snapshot_dir), &json);\n  760│            }\n  761│        }\n  762│    }\n  763│\n *764│    /// The snapshot path for one subnet (the CLI reader's contract).\n *765│    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {\n  766│        dir.join(format!(\"{subnet}.json\"))\n  767│    }\n  768│\n     │...\n  778│    /// consumed by the pump's registry tick so the IN-MEMORY rows go too\n *779│    /// (a disk-only eviction would be resurrected by the next snapshot\n  780│    /// mirror). `.meta` for the same can't-shadow-a-subnet reason as the\n  781│    /// heard map.\n  782│    pub fn repair_evict_path(dir: &Path) -> PathBuf {\n     │...\n  788│    /// from the in-memory registries + the heard map, and rewrite the\n *789│    /// snapshots. No marker = no-op. Returns evicted row count.\n  790│    // [impl->REQ-SUBNET-7]\n  791│    pub fn consume_repair_evictions(&self) -> usize {\n *792│        let path = Self::repair_evict_path(&self.snapshot_dir);\n  793│        let Ok(raw) = std::fs::read_to_string(&path) else {\n  794│            return 0;\n  795│        };\n     │...\n  818│        if evicted > 0 {\n *819│            self.write_snapshots();\n  820│        }\n  821│        evicted\n  822│    }\n     │...\n  961│/// by the pairing host AFTER a completed ceremony: resolve the superseded\n *962│/// identities from the subnet's snapshot, tombstone their roster entries (this\n  963│/// subnet's scope — the ceremony proved control of THIS subnet's code, nothing\n *964│/// about others), rewrite the disk snapshot, and note the hexes in the eviction\n  965│/// marker so the pump clears the in-memory rows on its next tick. The caller\n  966│/// persists `roster`. Returns one [`RekeyNotice`] per superseded identity (the\n  967│/// demoted warn-on-change, REQ-MESH-5) — empty when nothing supersedes.\n     │...\n  985│) -> Vec<RekeyNotice> {\n *986│    let snap_path = RegistryHost::snapshot_path(snap_dir, subnet);\n  987│    let Some(mut reg) = std::fs::read_to_string(&snap_path)\n  988│        .ok()\n  989│        .and_then(|s| serde_json::from_str::<SubnetRegistry>(&s).ok())\n     │...\n 1302│    // `is_member` — and returns a machine_id-anchored rekey awareness notice\n*1303│    // (the demoted warn-on-change, never a block). Rewrites the disk snapshot\n 1304│    // without its rows, notes it in the marker; the host's consume then clears\n 1305│    // the in-memory rows + the heard entry. A no-match intro (and an intro\n 1306│    // with no machine id) evicts nothing and raises no notice.\n 1307│    #[test]\n*1308│    fn repair_evict_tombstones_roster_snapshot_and_notices() {\n 1309│        let dir = tempfile::tempdir().unwrap();\n 1310│        let snap_dir = dir.path().join(\"registry\");\n 1311│        let h = RegistryHost::new_at(\n     │...\n 1327│        // Snapshot mirrored to disk by the merge.\n*1328│        assert!(RegistryHost::snapshot_path(&snap_dir, \"home\").exists());\n 1329│\n 1330│        // Roster membership in both subnets — the gate-relevant state (D5a).\n 1331│        let mut roster = RosterStore::default();\n     │...\n 1363│        let on_disk: SubnetRegistry = serde_json::from_str(\n*1364│            &std::fs::read_to_string(RegistryHost::snapshot_path(&snap_dir, \"home\")).unwrap(),\n 1365│        )\n 1366│        .unwrap();\n 1367│        assert!(\n 1368│            on_disk.instances(\"ling\").is_empty(),\n*1369│            \"superseded rows gone from the disk snapshot\"\n 1370│        );\n 1371│        // The marker holds the hex; consume clears the IN-MEMORY rows too\n*1372│        // (else the next snapshot mirror would resurrect the disk eviction).\n 1373│        assert!(RegistryHost::repair_evict_path(&snap_dir).exists());\n 1374│        assert_eq!(h.consume_repair_evictions(), 1, \"in-memory row evicted\");\n 1375│        assert!(h.rows(\"home\", \"ling\").is_empty());\n     │...\n 1393│\n*1394│        // Absent machine id: a FRESH gossip snapshot with a labeled identity;\n 1395│        // an intro carrying the same label but NO machine id must not supersede\n 1396│        // it (REQ-MESH-5 absent-id rule) — no eviction, no notice. Fresh dir so\n 1397│        // the match has a real row to (not) hit, unlike the already-evicted one.\n     │...\n 1693│        // The label landed and is the only carrier — no endpoint row created.\n*1694│        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 1695│        let reg: SubnetRegistry =\n 1696│            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 1697│        assert_eq!(reg.node_labels().collect::<Vec<_>>(), vec![(\"bb22\", \"RENAMED\")]);\n     │...\n 1984│\n*1985│            // The snapshot mirror exists and parses back.\n*1986│            let snap = RegistryHost::snapshot_path(&home.join(\"registry\"), \"adv\");\n*1987│            let bytes = std::fs::read(&snap).expect(\"snapshot written\");\n 1988│            let parsed: SubnetRegistry = serde_json::from_slice(&bytes).expect(\"parses\");\n 1989│            assert!(!parsed.instances(\"vis-ag\").is_empty());\n 1990│        });\n     │...\n 2033│    // (recently-heard = alive, no eviction) and evict once it falls silent\n*2034│    // past it — snapshots rewritten so the CLI reader (spt send's WAN\n 2035│    // resolution) stops seeing the ghost. Idempotent.\n 2036│    #[test]\n*2037│    fn evict_silent_peers_drops_unheard_rows_and_rewrites_snapshots() {\n 2038│        let dir = tempfile::tempdir().unwrap();\n 2039│        let h = host(dir.path()); // node_hex = \"aa11\"\n 2040│        let p = policy(\"home\", \"bb22\");\n     │...\n 2071│        // THE field shape: the dead identity's sergey row no longer poisons\n*2072│        // resolution — and the snapshot mirror agrees.\n 2073│        assert!(h.rows(\"home\", \"sergey\").is_empty(), \"ghost row evicted\");\n*2074│        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 2075│        let parsed: SubnetRegistry =\n 2076│            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 2077│        assert!(\n 2078│            parsed.instances(\"sergey\").is_empty(),\n*2079│            \"snapshot rewritten without the ghost\"\n 2080│        );\n 2081│\n 2082│        // Idempotent: a second sweep finds nothing.\n     │...\n 2099│    // ghost-heal re-advertise) has that row decay once it has been non-routable\n*2100│    // past the grace — snapshots rewritten so the remote viewer's count stops\n 2101│    // accumulating ghosts. Within grace it survives; the sweep is idempotent.\n 2102│    #[test]\n*2103│    fn evict_aged_offline_rows_reaps_ghosts_and_rewrites_snapshots() {\n 2104│        let dir = tempfile::tempdir().unwrap();\n 2105│        let h = host(dir.path()); // node_hex = \"aa11\"\n 2106│        let p = policy(\"home\", \"bb22\");\n     │...\n 2118│\n*2119│        // Past the grace the aged Offline ghost evicts and the snapshot is rewritten.\n 2120│        let later = 1_000 + 300_000 + 1;\n 2121│        assert_eq!(h.evict_aged_offline_rows_at(grace, later), 1);\n 2122│        assert!(h.rows(\"home\", \"ghost\").is_empty(), \"aged Offline ghost evicted\");\n*2123│        let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");\n 2124│        let parsed: SubnetRegistry =\n 2125│            serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();\n 2126│        assert!(\n 2127│            parsed.instances(\"ghost\").is_empty(),\n*2128│            \"snapshot rewritten without the aged Offline ghost\"\n 2129│        );\n 2130│\n 2131│        // Idempotent: a second sweep finds nothing."},"isError":false,"timestamp":1784271778392}}
{"type":"message","id":"e313f772","parentId":"488d8415","timestamp":"2026-07-17T07:02:58.392Z","message":{"role":"toolResult","toolCallId":"call_1bikgrk1z4x5ETjKgdxuzA8b|fc_089095e18118ee7d016a59d3a2337c8190b9e3c711b7f2857e","toolName":"grep","content":[{"type":"text","text":" 611|        if !any_due {\n*612|            thread::sleep(TICK);\n 613|            continue;\n 614|        }\n 615|"}],"details":{"scopePath":"crates/spt-daemon/src/pump/mod.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/pump/mod.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":1,"fileCount":1,"files":["crates/spt-daemon/src/pump/mod.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/pump/mod.rs","count":1}],"truncated":false,"displayContent":" 611│        if !any_due {\n*612│            thread::sleep(TICK);\n 613│            continue;\n 614│        }\n 615│"},"isError":false,"timestamp":1784271778392}}
{"type":"message","id":"e4cbbcd5","parentId":"e313f772","timestamp":"2026-07-17T07:02:58.393Z","message":{"role":"toolResult","toolCallId":"call_5nCxaAUX7ECx8utFEECmgfAL|fc_089095e18118ee7d016a59d3a231c4819094bce5107113845d","toolName":"grep","content":[{"type":"text","text":" 372|    // [impl->REQ-INST-7]\n*373|    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n*374|        let subnets = SubnetStore::load();\n*375|        let mut vis = VisibilityStore::load();\n 376|        // First-join home adoption (D9-5, REQ-INST-15): within one\n 377|        // advertisement cadence of the node's first join (a pair lands the\n 378|        // subnet record; no pairing-protocol threading needed), unhomed Self\n...\n 388|        // agent refines — CONTEXT §resource advertisement).\n*389|        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\n 390|        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\n 391|        // §Discovery scope): registered kind=\"shell\" adapters that chose\n 392|        // broadcast=subnet ride every row this node authors — the other-node\n...\n 431|            let info = spt_store::info::read_info(&perch_path);\n*432|            let resources = info\n 433|                .as_ref()\n*434|                .and_then(|i| i.resources.clone())\n 435|                .or_else(|| node_blurb.clone());\n 436|            // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat\n 437|            // stamp (`lifecycle::touch_active` — Active seats only; a resting\n...\n 483|            //  - adapter: the composite `<adapter>[:profile]` off info.json (the picker\n*484|            //    used to show the resources blurb here).\n 485|            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\n 486|            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\n 487|            //    a local controller has driven_by=None so that WHO datum undercounts it).\n...\n 498|                    epoch: ep,\n*499|                    resources: resources.clone(),\n 500|                    last_active_ms,\n 501|                    shell_adapters: shell_adapters.clone(),\n 502|                    // The node label rides every row this node authors\n...\n 568|                .filter(|id| {\n*569|                    reg.instances(id)\n 570|                        .iter()\n 571|                        .any(|i| i.node == self.node_hex && i.status.routable())\n 572|                })\n...\n 580|                    epoch: ep,\n*581|                    resources: None,\n 582|                    last_active_ms: None,\n 583|                    shell_adapters: Vec::new(),\n 584|                    node_label: self.node_label.clone(),"}],"details":{"scopePath":"crates/spt-daemon/src/registryhost.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/registryhost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":10,"fileCount":1,"files":["crates/spt-daemon/src/registryhost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/registryhost.rs","count":10}],"truncated":false,"displayContent":" 372│    // [impl->REQ-INST-7]\n*373│    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n*374│        let subnets = SubnetStore::load();\n*375│        let mut vis = VisibilityStore::load();\n 376│        // First-join home adoption (D9-5, REQ-INST-15): within one\n 377│        // advertisement cadence of the node's first join (a pair lands the\n 378│        // subnet record; no pairing-protocol threading needed), unhomed Self\n    │...\n 388│        // agent refines — CONTEXT §resource advertisement).\n*389│        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\n 390│        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\n 391│        // §Discovery scope): registered kind=\"shell\" adapters that chose\n 392│        // broadcast=subnet ride every row this node authors — the other-node\n    │...\n 431│            let info = spt_store::info::read_info(&perch_path);\n*432│            let resources = info\n 433│                .as_ref()\n*434│                .and_then(|i| i.resources.clone())\n 435│                .or_else(|| node_blurb.clone());\n 436│            // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat\n 437│            // stamp (`lifecycle::touch_active` — Active seats only; a resting\n    │...\n 483│            //  - adapter: the composite `<adapter>[:profile]` off info.json (the picker\n*484│            //    used to show the resources blurb here).\n 485│            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\n 486│            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\n 487│            //    a local controller has driven_by=None so that WHO datum undercounts it).\n    │...\n 498│                    epoch: ep,\n*499│                    resources: resources.clone(),\n 500│                    last_active_ms,\n 501│                    shell_adapters: shell_adapters.clone(),\n 502│                    // The node label rides every row this node authors\n    │...\n 568│                .filter(|id| {\n*569│                    reg.instances(id)\n 570│                        .iter()\n 571│                        .any(|i| i.node == self.node_hex && i.status.routable())\n 572│                })\n    │...\n 580│                    epoch: ep,\n*581│                    resources: None,\n 582│                    last_active_ms: None,\n 583│                    shell_adapters: Vec::new(),\n 584│                    node_label: self.node_label.clone(),"},"isError":false,"timestamp":1784271778393}}
{"type":"message","id":"a135c25b","parentId":"e4cbbcd5","timestamp":"2026-07-17T07:02:58.419Z","message":{"role":"toolResult","toolCallId":"call_zXUEEiEeo1H40wo4pEwxqeol|fc_089095e18118ee7d016a59d3a231b08190a1fe732b5325c19a","toolName":"read","content":[{"type":"text","text":"    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n…\n        if let Some(label) = self.node_label.clone() {\n…\n        }\n        drop(epoch);\n        drop(regs);\n        self.write_snapshots();\n        out\n    }\n\n    /// Evict every registry row authored by a **silent** peer node\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\n    /// within `ttl` has its rows removed from every subnet registry and the\n    /// snapshots rewritten, so a dead node identity can no longer poison\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\n    /// node authors them fresh each pump tick and is never in its own heard\n    /// map). A row whose author has NO heard stamp is treated as silent —\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\n    /// (own rows, excluded), so an unstamped foreign row is already an\n    /// anomaly and fails toward eviction. A merely-offline peer reconverges\n    /// within one pump cadence of its return (re-advertisement from its\n    /// durable epoch). Returns the number of rows evicted.\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n    pub fn evict_silent_peers(&self, ttl: std::time::Duration) -> usize {\n        self.evict_silent_peers_at(ttl, wall_ms())\n    }\n\n    /// [`evict_silent_peers`](Self::evict_silent_peers) with an explicit\n    /// clock (the testable seam).\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n    pub fn evict_silent_peers_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\n        let ttl_ms = ttl.as_millis() as u64;\n        let heard = self.heard.lock().unwrap();\n        let silent = |node: &str| {\n            node != self.node_hex\n                && heard\n                    .get(node)\n                    .is_none_or(|&t| now_ms.saturating_sub(t) > ttl_ms)\n        };\n        let mut regs = self.regs.lock().unwrap();\n        let mut evicted = 0usize;\n        for (subnet, reg) in regs.iter_mut() {\n            let n = reg.evict_nodes(silent);\n            if n > 0 {\n                eprintln!(\"REGISTRY_EVICT:{subnet}: {n} row(s) from silent node(s)\");\n            }\n            evicted += n;\n        }\n        drop(regs);\n        drop(heard);\n        if evicted > 0 {\n            self.write_snapshots();\n        }\n        evicted\n    }\n\n    /// Evict per-row **aged-Offline ghosts** across every subnet registry\n    /// (REQ-HAZARD-REGISTRY-GHOST-ROWS trigger (b), #2-secondary): a row that has\n    /// been non-routable (`Offline`) on this receiver beyond `ttl` is removed even\n    /// while its author node is alive — the decay [`evict_silent_peers`] (node\n    /// silence) cannot give, so a `purge`/`erase` Offline ghost (ghost-heal\n    /// re-advertises Offline once, whole-node eviction never fires) stops\n    /// accumulating unbounded on a remote viewer under churn. Own rows never decay.\n    /// Snapshots are rewritten iff a row was evicted. The per-row receiver-observed\n    /// clock is the model's self-pruning [`SubnetRegistry::evict_aged_offline`]\n    /// side-map (re-derived each call — no leak, no explicit whole-node purge).\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n    pub fn evict_aged_offline_rows(&self, ttl: std::time::Duration) -> usize {\n        self.evict_aged_offline_rows_at(ttl, wall_ms())\n    }\n\n    /// [`evict_aged_offline_rows`](Self::evict_aged_offline_rows) with an explicit\n    /// clock (the testable seam, mirroring [`evict_silent_peers_at`]).\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n    pub fn evict_aged_offline_rows_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\n        let grace_ms = ttl.as_millis() as u64;\n        let mut regs = self.regs.lock().unwrap();\n        let mut evicted = 0usize;\n        for (subnet, reg) in regs.iter_mut() {\n            let n = reg.evict_aged_offline(now_ms, grace_ms, &self.node_hex);\n            if n > 0 {\n                eprintln!(\"REGISTRY_EVICT_OFFLINE:{subnet}: {n} aged-offline row(s)\");\n            }\n            evicted += n;\n        }\n        drop(regs);\n        if evicted > 0 {\n            self.write_snapshots();\n        }\n        evicted\n    }\n\n    /// Every instance row for `endpoint_id` in `subnet` (the reconcile\n    /// election's input; empty when unknown).\n    pub fn rows(&self, subnet: &str, endpoint_id: &str) -> Vec<Instance> {\n        self.regs\n            .lock()\n            .unwrap()\n            .get(subnet)\n            .map(|r| r.instances(endpoint_id).to_vec())\n            .unwrap_or_default()\n    }\n\n    /// A point-in-time clone of one subnet's registry (resolution input).\n    pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {\n        self.regs.lock().unwrap().get(subnet).cloned()\n    }\n\n\n[Showing lines 629-733 of 2238. Use :734 to continue]"}],"details":{"truncation":{"content":"        }\r\n        drop(epoch);\r\n        drop(regs);\r\n        self.write_snapshots();\r\n        out\r\n    }\r\n\r\n    /// Evict every registry row authored by a **silent** peer node\r\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\r\n    /// within `ttl` has its rows removed from every subnet registry and the\r\n    /// snapshots rewritten, so a dead node identity can no longer poison\r\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\r\n    /// node authors them fresh each pump tick and is never in its own heard\r\n    /// map). A row whose author has NO heard stamp is treated as silent —\r\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\r\n    /// (own rows, excluded), so an unstamped foreign row is already an\r\n    /// anomaly and fails toward eviction. A merely-offline peer reconverges\r\n    /// within one pump cadence of its return (re-advertisement from its\r\n    /// durable epoch). Returns the number of rows evicted.\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_silent_peers(&self, ttl: std::time::Duration) -> usize {\r\n        self.evict_silent_peers_at(ttl, wall_ms())\r\n    }\r\n\r\n    /// [`evict_silent_peers`](Self::evict_silent_peers) with an explicit\r\n    /// clock (the testable seam).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_silent_peers_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\r\n        let ttl_ms = ttl.as_millis() as u64;\r\n        let heard = self.heard.lock().unwrap();\r\n        let silent = |node: &str| {\r\n            node != self.node_hex\r\n                && heard\r\n                    .get(node)\r\n                    .is_none_or(|&t| now_ms.saturating_sub(t) > ttl_ms)\r\n        };\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut evicted = 0usize;\r\n        for (subnet, reg) in regs.iter_mut() {\r\n            let n = reg.evict_nodes(silent);\r\n            if n > 0 {\r\n                eprintln!(\"REGISTRY_EVICT:{subnet}: {n} row(s) from silent node(s)\");\r\n            }\r\n            evicted += n;\r\n        }\r\n        drop(regs);\r\n        drop(heard);\r\n        if evicted > 0 {\r\n            self.write_snapshots();\r\n        }\r\n        evicted\r\n    }\r\n\r\n    /// Evict per-row **aged-Offline ghosts** across every subnet registry\r\n    /// (REQ-HAZARD-REGISTRY-GHOST-ROWS trigger (b), #2-secondary): a row that has\r\n    /// been non-routable (`Offline`) on this receiver beyond `ttl` is removed even\r\n    /// while its author node is alive — the decay [`evict_silent_peers`] (node\r\n    /// silence) cannot give, so a `purge`/`erase` Offline ghost (ghost-heal\r\n    /// re-advertises Offline once, whole-node eviction never fires) stops\r\n    /// accumulating unbounded on a remote viewer under churn. Own rows never decay.\r\n    /// Snapshots are rewritten iff a row was evicted. The per-row receiver-observed\r\n    /// clock is the model's self-pruning [`SubnetRegistry::evict_aged_offline`]\r\n    /// side-map (re-derived each call — no leak, no explicit whole-node purge).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_aged_offline_rows(&self, ttl: std::time::Duration) -> usize {\r\n        self.evict_aged_offline_rows_at(ttl, wall_ms())\r\n    }\r\n\r\n    /// [`evict_aged_offline_rows`](Self::evict_aged_offline_rows) with an explicit\r\n    /// clock (the testable seam, mirroring [`evict_silent_peers_at`]).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_aged_offline_rows_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\r\n        let grace_ms = ttl.as_millis() as u64;\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut evicted = 0usize;\r\n        for (subnet, reg) in regs.iter_mut() {\r\n            let n = reg.evict_aged_offline(now_ms, grace_ms, &self.node_hex);\r\n            if n > 0 {\r\n                eprintln!(\"REGISTRY_EVICT_OFFLINE:{subnet}: {n} aged-offline row(s)\");\r\n            }\r\n            evicted += n;\r\n        }\r\n        drop(regs);\r\n        if evicted > 0 {\r\n            self.write_snapshots();\r\n        }\r\n        evicted\r\n    }\r\n\r\n    /// Every instance row for `endpoint_id` in `subnet` (the reconcile\r\n    /// election's input; empty when unknown).\r\n    pub fn rows(&self, subnet: &str, endpoint_id: &str) -> Vec<Instance> {\r\n        self.regs\r\n            .lock()\r\n            .unwrap()\r\n            .get(subnet)\r\n            .map(|r| r.instances(endpoint_id).to_vec())\r\n            .unwrap_or_default()\r\n    }\r\n\r\n    /// A point-in-time clone of one subnet's registry (resolution input).\r\n    pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {\r\n        self.regs.lock().unwrap().get(subnet).cloned()\r\n    }\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":1610,"totalBytes":4698,"outputLines":105,"outputBytes":4698,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n…\n        if let Some(label) = self.node_label.clone() {\n…\n        }\r\n        drop(epoch);\r\n        drop(regs);\r\n        self.write_snapshots();\r\n        out\r\n    }\r\n\r\n    /// Evict every registry row authored by a **silent** peer node\r\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\r\n    /// within `ttl` has its rows removed from every subnet registry and the\r\n    /// snapshots rewritten, so a dead node identity can no longer poison\r\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\r\n    /// node authors them fresh each pump tick and is never in its own heard\r\n    /// map). A row whose author has NO heard stamp is treated as silent —\r\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\r\n    /// (own rows, excluded), so an unstamped foreign row is already an\r\n    /// anomaly and fails toward eviction. A merely-offline peer reconverges\r\n    /// within one pump cadence of its return (re-advertisement from its\r\n    /// durable epoch). Returns the number of rows evicted.\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_silent_peers(&self, ttl: std::time::Duration) -> usize {\r\n        self.evict_silent_peers_at(ttl, wall_ms())\r\n    }\r\n\r\n    /// [`evict_silent_peers`](Self::evict_silent_peers) with an explicit\r\n    /// clock (the testable seam).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_silent_peers_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\r\n        let ttl_ms = ttl.as_millis() as u64;\r\n        let heard = self.heard.lock().unwrap();\r\n        let silent = |node: &str| {\r\n            node != self.node_hex\r\n                && heard\r\n                    .get(node)\r\n                    .is_none_or(|&t| now_ms.saturating_sub(t) > ttl_ms)\r\n        };\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut evicted = 0usize;\r\n        for (subnet, reg) in regs.iter_mut() {\r\n            let n = reg.evict_nodes(silent);\r\n            if n > 0 {\r\n                eprintln!(\"REGISTRY_EVICT:{subnet}: {n} row(s) from silent node(s)\");\r\n            }\r\n            evicted += n;\r\n        }\r\n        drop(regs);\r\n        drop(heard);\r\n        if evicted > 0 {\r\n            self.write_snapshots();\r\n        }\r\n        evicted\r\n    }\r\n\r\n    /// Evict per-row **aged-Offline ghosts** across every subnet registry\r\n    /// (REQ-HAZARD-REGISTRY-GHOST-ROWS trigger (b), #2-secondary): a row that has\r\n    /// been non-routable (`Offline`) on this receiver beyond `ttl` is removed even\r\n    /// while its author node is alive — the decay [`evict_silent_peers`] (node\r\n    /// silence) cannot give, so a `purge`/`erase` Offline ghost (ghost-heal\r\n    /// re-advertises Offline once, whole-node eviction never fires) stops\r\n    /// accumulating unbounded on a remote viewer under churn. Own rows never decay.\r\n    /// Snapshots are rewritten iff a row was evicted. The per-row receiver-observed\r\n    /// clock is the model's self-pruning [`SubnetRegistry::evict_aged_offline`]\r\n    /// side-map (re-derived each call — no leak, no explicit whole-node purge).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_aged_offline_rows(&self, ttl: std::time::Duration) -> usize {\r\n        self.evict_aged_offline_rows_at(ttl, wall_ms())\r\n    }\r\n\r\n    /// [`evict_aged_offline_rows`](Self::evict_aged_offline_rows) with an explicit\r\n    /// clock (the testable seam, mirroring [`evict_silent_peers_at`]).\r\n    // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\r\n    pub fn evict_aged_offline_rows_at(&self, ttl: std::time::Duration, now_ms: u64) -> usize {\r\n        let grace_ms = ttl.as_millis() as u64;\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut evicted = 0usize;\r\n        for (subnet, reg) in regs.iter_mut() {\r\n            let n = reg.evict_aged_offline(now_ms, grace_ms, &self.node_hex);\r\n            if n > 0 {\r\n                eprintln!(\"REGISTRY_EVICT_OFFLINE:{subnet}: {n} aged-offline row(s)\");\r\n            }\r\n            evicted += n;\r\n        }\r\n        drop(regs);\r\n        if evicted > 0 {\r\n            self.write_snapshots();\r\n        }\r\n        evicted\r\n    }\r\n\r\n    /// Every instance row for `endpoint_id` in `subnet` (the reconcile\r\n    /// election's input; empty when unknown).\r\n    pub fn rows(&self, subnet: &str, endpoint_id: &str) -> Vec<Instance> {\r\n        self.regs\r\n            .lock()\r\n            .unwrap()\r\n            .get(subnet)\r\n            .map(|r| r.instances(endpoint_id).to_vec())\r\n            .unwrap_or_default()\r\n    }\r\n\r\n    /// A point-in-time clone of one subnet's registry (resolution input).\r\n    pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {\r\n        self.regs.lock().unwrap().get(subnet).cloned()\r\n    }\r\n\r","startLine":373,"lineNumbers":[373,null,617,null,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2238,"totalBytes":4698,"outputLines":105,"outputBytes":4698,"shownRange":{"start":629,"end":733},"nextOffset":734}}},"isError":false,"timestamp":1784271778419}}
{"type":"message","id":"281e2acb","parentId":"a135c25b","timestamp":"2026-07-17T07:02:58.431Z","message":{"role":"toolResult","toolCallId":"call_pI5enrkImHd4fAzTIlJfgvCk|fc_089095e18118ee7d016a59d3a234f881909590af3b12960e33","toolName":"read","content":[{"type":"text","text":"impl PeerIo<'_> {\n…\n    /// Mint the next durable open-op from the pump's sole counter (exactly-once\n    /// open identity — a restart can never reuse one), tagged with the `pump`\n    /// journal minter (ADR-0034): the pump is the sole writer of `pump-ops.json`,\n    /// so its ops namespace under `pump` and never collide with cli/rc/shell ops.\n    fn open_op(&mut self) -> io::Result<MintedOp> {\n        Ok(MintedOp::new(Minter::Pump, self.ops.next_epoch()?))\n    }\n}\n\n/// Run the pump until `stop` is set. Owns one brain IPC connection and a\n/// per-peer connection cache (an IO failure against a peer drops its cache\n/// row; the next tick redials — conns are rebuilt freely, nothing leaks\n/// because the broker's closed-watcher reaps dead conns).\n///\n/// The pure scheduler + fan-out (pump-seam P4, all four legs behind the seam):\n/// heartbeat → poll every worker's wake → the [`due`] kernel → each due\n/// worker's `pre_round` (once, before any peer_step) → per-subnet × per-peer\n/// (`ensure_conn` → each due worker's `peer_step` in fan-out order,\n/// abort-on-first-fail) → mark-after-round with a POST-round instant. No leg\n/// logic lives here — the shell owns only *when* and *toward whom*.\npub fn run_peer_pump(\n    broker_name: &str,\n    registry: Arc<RegistryHost>,\n    paths: &PumpPaths,\n    resolver: PeerResolver,\n    config: PumpConfig,\n    hooks: PumpHooks,\n    stop: &AtomicBool,\n) -> io::Result<()> {\n    let PumpConfig {\n        cadence,\n        full_auto_update,\n    } = config;\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    let mut brain = connect(broker_name)?;\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\n    // whose reply-reads consume every event kind off their carrier; sharing one\n    // carrier would make a pull-read swallow a presence frame (advancing the\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\n    // carriers keep the drain and the pulls from swallowing each other by\n    // construction. Tail-follow (no history replay, baseline on first live event)\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\n    let mut events = connect(broker_name)?;\n    events.net_presence_subscribe_tail()?;\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\n    // addresses reconcile into the dial cache BEFORE the first round —\n    // recovery is connection-independent (a node restarted mid-strand heals\n    // here, no operator surgery) — and rows violating `outer key ==\n    // address.id` are repaired from the roster or dropped loudly (the first\n    // post-upgrade load is the migration; the file is never bare-deleted —\n    // valid rows and the id-only fallback behind them stay untouched).\n    // Idempotent, so re-running on every supervised pump restart is free.\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\n    // so the last-good stamps survive a restart; the live counts re-prime as\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\n    let mut health = PumpHealth::load_from(&health_path);\n    health.note_disconnected(0); // no conns exist yet in this pump generation\n    let mut ops = EpochSource::load_from(&paths.ops);\n    // The live per-peer connection cache (updated by presence events) + the\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\n    let mut conns: HashMap<String, u64> = HashMap::new();\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\n    // The four cadenced legs behind the worker seam, in fan-out order\n    // (registry → notif → sync → update). The shell drives them generically;\n    // each leg's *what* lives in its module (§Frozen). Construction capture\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\n    // knob it needs — the sync worker additionally owns the conflict-surface\n    // hooks (ADR-0013).\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\n    let mut workers: [&mut dyn PumpWorker; 4] = [\n        &mut registry_worker,\n        &mut notif_worker,\n        &mut sync_worker,\n        &mut update_worker,\n    ];\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\n    // `workers`. `None` = never run yet → due immediately, so the first tick\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\n    let mut last: [Option<Instant>; 4] = [None; 4];\n    let mut last_hb: Option<Instant> = None;\n\n    while !stop.load(Ordering::Relaxed) {\n        let now = Instant::now();\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\n        // while the loop is ALIVE — including idle ticks — never per work round,\n        // so a wedged step and a healthy quiet pump are distinguishable.\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\n        // [impl->REQ-DAEMON-5]\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\n            write_heartbeat(&paths.heartbeat);\n            last_hb = Some(now);\n        }\n        // Poll every worker's wake and decide due (the pure scheduler step):\n        // every wake is consumed EVERY tick so a marker is never stranded (the\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\n        // forced round.\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\n        if !any_due {\n            thread::sleep(TICK);\n            continue;\n        }\n\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\n        // roster-backed policy, read ONCE and handed to every worker.\n        let ctx = RoundCtx::load(paths);\n\n        // Each due worker's node-local pre_round, once per round before ANY\n        // peer_step (only the registry leg does work here — its sweep +\n        // advertise; the rest default to a no-op).\n        pre_rounds(&mut workers, &due_flags, &ctx);\n\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\n        // peer may target several subnets; one conn serves them all).\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\n        }\n        // Health targets = this round's fan-out set; snapshot the registry\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\n        health.note_registry_admit(registry.last_admit_ms());\n        let round_start = Instant::now();\n        let mut pending: HashSet<String> = HashSet::new();\n\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\n        // legs NOW for already-connected ones. The live peer advertises without\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\n        // pending outcome, never a serial blocker.\n        for (peer_hex, subs) in &peer_subnets {\n            if let Some(&conn_id) = conns.get(peer_hex) {\n                run_peer_subnets(\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n                    conn_id, subs, peer_hex, &ctx, round_start,\n                )?;\n            } else if peer_eligible(&sched, peer_hex, round_start) {\n                if let Some(addr) =\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\n                {\n                    match brain.submit_dial(addr) {\n                        Ok(()) => {\n                            pending.insert(peer_hex.clone());\n                        }\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\n                        // genuine broker-IPC desync → bubble → supervised restart.\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\n                        // The broker refused the submit (net disabled / bad addr):\n                        // an ordinary per-peer failure — back off, round continues.\n                        Err(e) => {\n                            eprintln!(\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\n                                now_ms(),\n                                mono_ms()\n                            );\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\n                            let prev = sched.get(peer_hex).copied();\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\n                        }\n                    }\n                } else {\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\n                    // `address-resolution`). Transition-logged: the first\n                    // unroutable round per peer is loud, repeats are quiet —\n                    // the peer retries on its own cadence like before.\n                    if health.note_failed(\n                        peer_hex,\n                        crate::seedproofx::STAGE_RESOLVE,\n                        conns.len(),\n                        now_ms(),\n                    ) {\n…\n                    }\n                }\n            }\n        }\n…\n    }\n…\n}\n\n[Showing lines 499-683 of 1982. Use :684 to continue]"}],"details":{"truncation":{"content":"    /// Mint the next durable open-op from the pump's sole counter (exactly-once\r\n    /// open identity — a restart can never reuse one), tagged with the `pump`\r\n    /// journal minter (ADR-0034): the pump is the sole writer of `pump-ops.json`,\r\n    /// so its ops namespace under `pump` and never collide with cli/rc/shell ops.\r\n    fn open_op(&mut self) -> io::Result<MintedOp> {\r\n        Ok(MintedOp::new(Minter::Pump, self.ops.next_epoch()?))\r\n    }\r\n}\r\n\r\n/// Run the pump until `stop` is set. Owns one brain IPC connection and a\r\n/// per-peer connection cache (an IO failure against a peer drops its cache\r\n/// row; the next tick redials — conns are rebuilt freely, nothing leaks\r\n/// because the broker's closed-watcher reaps dead conns).\r\n///\r\n/// The pure scheduler + fan-out (pump-seam P4, all four legs behind the seam):\r\n/// heartbeat → poll every worker's wake → the [`due`] kernel → each due\r\n/// worker's `pre_round` (once, before any peer_step) → per-subnet × per-peer\r\n/// (`ensure_conn` → each due worker's `peer_step` in fan-out order,\r\n/// abort-on-first-fail) → mark-after-round with a POST-round instant. No leg\r\n/// logic lives here — the shell owns only *when* and *toward whom*.\r\npub fn run_peer_pump(\r\n    broker_name: &str,\r\n    registry: Arc<RegistryHost>,\r\n    paths: &PumpPaths,\r\n    resolver: PeerResolver,\r\n    config: PumpConfig,\r\n    hooks: PumpHooks,\r\n    stop: &AtomicBool,\r\n) -> io::Result<()> {\r\n    let PumpConfig {\r\n        cadence,\r\n        full_auto_update,\r\n    } = config;\r\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\r\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\r\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\r\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\r\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    let mut brain = connect(broker_name)?;\r\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\r\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\r\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\r\n    // whose reply-reads consume every event kind off their carrier; sharing one\r\n    // carrier would make a pull-read swallow a presence frame (advancing the\r\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\r\n    // carriers keep the drain and the pulls from swallowing each other by\r\n    // construction. Tail-follow (no history replay, baseline on first live event)\r\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\r\n    let mut events = connect(broker_name)?;\r\n    events.net_presence_subscribe_tail()?;\r\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\r\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\r\n    // addresses reconcile into the dial cache BEFORE the first round —\r\n    // recovery is connection-independent (a node restarted mid-strand heals\r\n    // here, no operator surgery) — and rows violating `outer key ==\r\n    // address.id` are repaired from the roster or dropped loudly (the first\r\n    // post-upgrade load is the migration; the file is never bare-deleted —\r\n    // valid rows and the id-only fallback behind them stay untouched).\r\n    // Idempotent, so re-running on every supervised pump restart is free.\r\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\r\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\r\n    // so the last-good stamps survive a restart; the live counts re-prime as\r\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let mut health = PumpHealth::load_from(&health_path);\r\n    health.note_disconnected(0); // no conns exist yet in this pump generation\r\n    let mut ops = EpochSource::load_from(&paths.ops);\r\n    // The live per-peer connection cache (updated by presence events) + the\r\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\r\n    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let now = Instant::now();\r\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\r\n        // while the loop is ALIVE — including idle ticks — never per work round,\r\n        // so a wedged step and a healthy quiet pump are distinguishable.\r\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\r\n        // [impl->REQ-DAEMON-5]\r\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\r\n            write_heartbeat(&paths.heartbeat);\r\n            last_hb = Some(now);\r\n        }\r\n        // Poll every worker's wake and decide due (the pure scheduler step):\r\n        // every wake is consumed EVERY tick so a marker is never stranded (the\r\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\r\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\r\n        // forced round.\r\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\r\n        if !any_due {\r\n            thread::sleep(TICK);\r\n            continue;\r\n        }\r\n\r\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\r\n        // roster-backed policy, read ONCE and handed to every worker.\r\n        let ctx = RoundCtx::load(paths);\r\n\r\n        // Each due worker's node-local pre_round, once per round before ANY\r\n        // peer_step (only the registry leg does work here — its sweep +\r\n        // advertise; the rest default to a no-op).\r\n        pre_rounds(&mut workers, &due_flags, &ctx);\r\n\r\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\r\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\r\n        // peer may target several subnets; one conn serves them all).\r\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\r\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\r\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\r\n        }\r\n        // Health targets = this round's fan-out set; snapshot the registry\r\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\r\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\r\n        health.note_registry_admit(registry.last_admit_ms());\r\n        let round_start = Instant::now();\r\n        let mut pending: HashSet<String> = HashSet::new();\r\n\r\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\r\n        // legs NOW for already-connected ones. The live peer advertises without\r\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\r\n        // pending outcome, never a serial blocker.\r\n        for (peer_hex, subs) in &peer_subnets {\r\n            if let Some(&conn_id) = conns.get(peer_hex) {\r\n                run_peer_subnets(\r\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    conn_id, subs, peer_hex, &ctx, round_start,\r\n                )?;\r\n            } else if peer_eligible(&sched, peer_hex, round_start) {\r\n                if let Some(addr) =\r\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\r\n                {\r\n                    match brain.submit_dial(addr) {\r\n                        Ok(()) => {\r\n                            pending.insert(peer_hex.clone());\r\n                        }\r\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\r\n                        // genuine broker-IPC desync → bubble → supervised restart.\r\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\r\n                        // The broker refused the submit (net disabled / bad addr):\r\n                        // an ordinary per-peer failure — back off, round continues.\r\n                        Err(e) => {\r\n                            eprintln!(\r\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\r\n                                now_ms(),\r\n                                mono_ms()\r\n                            );\r\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\r\n                            let prev = sched.get(peer_hex).copied();\r\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\r\n                        }\r\n                    }\r\n                } else {\r\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\r\n                    // `address-resolution`). Transition-logged: the first\r\n                    // unroutable round per peer is loud, repeats are quiet —\r\n                    // the peer retries on its own cadence like before.\r\n                    if health.note_failed(\r\n                        peer_hex,\r\n                        crate::seedproofx::STAGE_RESOLVE,\r\n                        conns.len(),\r\n                        now_ms(),\r\n                    ) {\r","truncated":true,"truncatedBy":"lines","totalLines":1484,"totalBytes":10744,"outputLines":185,"outputBytes":10744,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl PeerIo<'_> {\n…\n    /// Mint the next durable open-op from the pump's sole counter (exactly-once\r\n    /// open identity — a restart can never reuse one), tagged with the `pump`\r\n    /// journal minter (ADR-0034): the pump is the sole writer of `pump-ops.json`,\r\n    /// so its ops namespace under `pump` and never collide with cli/rc/shell ops.\r\n    fn open_op(&mut self) -> io::Result<MintedOp> {\r\n        Ok(MintedOp::new(Minter::Pump, self.ops.next_epoch()?))\r\n    }\r\n}\r\n\r\n/// Run the pump until `stop` is set. Owns one brain IPC connection and a\r\n/// per-peer connection cache (an IO failure against a peer drops its cache\r\n/// row; the next tick redials — conns are rebuilt freely, nothing leaks\r\n/// because the broker's closed-watcher reaps dead conns).\r\n///\r\n/// The pure scheduler + fan-out (pump-seam P4, all four legs behind the seam):\r\n/// heartbeat → poll every worker's wake → the [`due`] kernel → each due\r\n/// worker's `pre_round` (once, before any peer_step) → per-subnet × per-peer\r\n/// (`ensure_conn` → each due worker's `peer_step` in fan-out order,\r\n/// abort-on-first-fail) → mark-after-round with a POST-round instant. No leg\r\n/// logic lives here — the shell owns only *when* and *toward whom*.\r\npub fn run_peer_pump(\r\n    broker_name: &str,\r\n    registry: Arc<RegistryHost>,\r\n    paths: &PumpPaths,\r\n    resolver: PeerResolver,\r\n    config: PumpConfig,\r\n    hooks: PumpHooks,\r\n    stop: &AtomicBool,\r\n) -> io::Result<()> {\r\n    let PumpConfig {\r\n        cadence,\r\n        full_auto_update,\r\n    } = config;\r\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\r\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\r\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\r\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\r\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    let mut brain = connect(broker_name)?;\r\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\r\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\r\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\r\n    // whose reply-reads consume every event kind off their carrier; sharing one\r\n    // carrier would make a pull-read swallow a presence frame (advancing the\r\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\r\n    // carriers keep the drain and the pulls from swallowing each other by\r\n    // construction. Tail-follow (no history replay, baseline on first live event)\r\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\r\n    let mut events = connect(broker_name)?;\r\n    events.net_presence_subscribe_tail()?;\r\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\r\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\r\n    // addresses reconcile into the dial cache BEFORE the first round —\r\n    // recovery is connection-independent (a node restarted mid-strand heals\r\n    // here, no operator surgery) — and rows violating `outer key ==\r\n    // address.id` are repaired from the roster or dropped loudly (the first\r\n    // post-upgrade load is the migration; the file is never bare-deleted —\r\n    // valid rows and the id-only fallback behind them stay untouched).\r\n    // Idempotent, so re-running on every supervised pump restart is free.\r\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\r\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\r\n    // so the last-good stamps survive a restart; the live counts re-prime as\r\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let mut health = PumpHealth::load_from(&health_path);\r\n    health.note_disconnected(0); // no conns exist yet in this pump generation\r\n    let mut ops = EpochSource::load_from(&paths.ops);\r\n    // The live per-peer connection cache (updated by presence events) + the\r\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\r\n    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let now = Instant::now();\r\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\r\n        // while the loop is ALIVE — including idle ticks — never per work round,\r\n        // so a wedged step and a healthy quiet pump are distinguishable.\r\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\r\n        // [impl->REQ-DAEMON-5]\r\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\r\n            write_heartbeat(&paths.heartbeat);\r\n            last_hb = Some(now);\r\n        }\r\n        // Poll every worker's wake and decide due (the pure scheduler step):\r\n        // every wake is consumed EVERY tick so a marker is never stranded (the\r\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\r\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\r\n        // forced round.\r\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\r\n        if !any_due {\r\n            thread::sleep(TICK);\r\n            continue;\r\n        }\r\n\r\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\r\n        // roster-backed policy, read ONCE and handed to every worker.\r\n        let ctx = RoundCtx::load(paths);\r\n\r\n        // Each due worker's node-local pre_round, once per round before ANY\r\n        // peer_step (only the registry leg does work here — its sweep +\r\n        // advertise; the rest default to a no-op).\r\n        pre_rounds(&mut workers, &due_flags, &ctx);\r\n\r\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\r\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\r\n        // peer may target several subnets; one conn serves them all).\r\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\r\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\r\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\r\n        }\r\n        // Health targets = this round's fan-out set; snapshot the registry\r\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\r\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\r\n        health.note_registry_admit(registry.last_admit_ms());\r\n        let round_start = Instant::now();\r\n        let mut pending: HashSet<String> = HashSet::new();\r\n\r\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\r\n        // legs NOW for already-connected ones. The live peer advertises without\r\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\r\n        // pending outcome, never a serial blocker.\r\n        for (peer_hex, subs) in &peer_subnets {\r\n            if let Some(&conn_id) = conns.get(peer_hex) {\r\n                run_peer_subnets(\r\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    conn_id, subs, peer_hex, &ctx, round_start,\r\n                )?;\r\n            } else if peer_eligible(&sched, peer_hex, round_start) {\r\n                if let Some(addr) =\r\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\r\n                {\r\n                    match brain.submit_dial(addr) {\r\n                        Ok(()) => {\r\n                            pending.insert(peer_hex.clone());\r\n                        }\r\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\r\n                        // genuine broker-IPC desync → bubble → supervised restart.\r\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\r\n                        // The broker refused the submit (net disabled / bad addr):\r\n                        // an ordinary per-peer failure — back off, round continues.\r\n                        Err(e) => {\r\n                            eprintln!(\r\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\r\n                                now_ms(),\r\n                                mono_ms()\r\n                            );\r\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\r\n                            let prev = sched.get(peer_hex).copied();\r\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\r\n                        }\r\n                    }\r\n                } else {\r\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\r\n                    // `address-resolution`). Transition-logged: the first\r\n                    // unroutable round per peer is loud, repeats are quiet —\r\n                    // the peer retries on its own cadence like before.\r\n                    if health.note_failed(\r\n                        peer_hex,\r\n                        crate::seedproofx::STAGE_RESOLVE,\r\n                        conns.len(),\r\n                        now_ms(),\r\n                    ) {\r\n…\n                    }\n                }\n            }\n        }\n…\n    }\n…\n}","startLine":492,"lineNumbers":[492,null,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,null,690,691,692,693,null,730,null,732]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\mod.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1982,"totalBytes":10744,"outputLines":185,"outputBytes":10744,"shownRange":{"start":499,"end":683},"nextOffset":684}}},"isError":false,"timestamp":1784271778431}}
{"type":"message","id":"f22d0aba","parentId":"281e2acb","timestamp":"2026-07-17T07:02:58.448Z","message":{"role":"toolResult","toolCallId":"call_U3UhBSWmVHUaEMcVNrWe510l|fc_089095e18118ee7d016a59d3a231d081909a7654d9b704a791","toolName":"read","content":[{"type":"text","text":"    pub fn last_admit_ms(&self) -> Option<u64> {\n        match self\n…\n            .load(std::sync::atomic::Ordering::Relaxed)\n        {\n            0 => None,\n            ms => Some(ms),\n        }\n    }\n\n    /// Scan the owlery and (re-)advertise every self perch into every member\n    /// subnet it is visible in, each row stamped with a fresh epoch. Returns\n    /// the updates to push to peers (the node's own rows only — peers' rows\n    /// reconverge from their own re-advertisement, no transitive gossip in\n    /// v1: trust is pairwise per subnet, a relayed third-party row would\n    /// carry an origin the receiver can't gate honestly).\n    // [impl->REQ-INST-7]\n    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\n        let subnets = SubnetStore::load();\n        let mut vis = VisibilityStore::load();\n        // First-join home adoption (D9-5, REQ-INST-15): within one\n        // advertisement cadence of the node's first join (a pair lands the\n        // subnet record; no pairing-protocol threading needed), unhomed Self\n        // perches adopt the sole subnet + seed their sync scope. Strictly\n        // adopt-if-unset; a multi-subnet store adopts nothing.\n        // [impl->REQ-INST-15]\n        if !spt_store::home::adopt_for_unset(owlery, &subnets, &mut vis).is_empty() {\n            let _ = vis.save();\n        }\n        let vis = vis;\n        // The node-level resource-blurb seed (D9-4): an endpoint that has not\n        // authored its own blurb advertises this default (config seeds, the\n        // agent refines — CONTEXT §resource advertisement).\n        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\n        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\n        // §Discovery scope): registered kind=\"shell\" adapters that chose\n        // broadcast=subnet ride every row this node authors — the other-node\n        // discovery arm. Computed once per scan (the registration set is\n        // node-level).\n        // [impl->REQ-EP-5]\n        let shell_adapters: Vec<String> = spt_runtime::registry::registered(&perch::adapters_dir())\n            .iter()\n            .filter(|(r, m)| {\n                r.kind == spt_runtime::manifest::AdapterKind::Shell\n                    && m.shell.as_ref().and_then(|s| s.broadcast)\n                        == Some(spt_runtime::manifest::Broadcast::Subnet)\n            })\n            .map(|(r, _)| r.name.clone())\n            .collect();\n        let mut out = Vec::new();\n        let mut regs = self.regs.lock().unwrap();\n        let mut epoch = self.epoch.lock().unwrap();\n\n        // The set of self perches that still EXIST on disk this round — the\n        // ghost-heal predicate below (a LOCAL registry row whose id is absent\n        // here was erased, REQ-HAZARD-ROSTER-GHOST).\n        let live_ids: std::collections::BTreeSet<String> =\n            perch::list_self_perch_ids(owlery).into_iter().collect();\n\n        for id in perch::list_self_perch_ids(owlery) {\n            let perch_path = perch::resolve_perch_path_in(owlery, &id, ParentHint::Infer);\n            // Advertisement follows the resting-state machine (D9-2,\n            // REQ-INST-3): a live perch with a resting record advertises its\n            // machine state (active / dormant / suspended); a recordless\n            // (pre-D9-2 / interim) or dead perch keeps the legacy\n            // liveness-derived mapping. Every advertisement stamps a fresh\n            // epoch below, so a transition reaches peers as an epoch-bumped\n            // update — the lease orders it (KH 4.8).\n            // [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\n            let status = advertised_status(&perch_path);\n            // Endpoint-authored blurb wins; the node seed fills the gap\n            // (both-authored, REQ-INST-14). Rides the same epoch lease as\n            // every instance update — a blurb edit reaches peers as an\n            // ordered, epoch-bumped row.\n            // [impl->REQ-INST-14]\n            let info = spt_store::info::read_info(&perch_path);\n            let resources = info\n                .as_ref()\n                .and_then(|i| i.resources.clone())\n                .or_else(|| node_blurb.clone());\n            // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat\n            // stamp (`lifecycle::touch_active` — Active seats only; a resting\n            // seat never stamps) rides the row like the blurb does — same\n            // epoch lease, same visibility gate, no new channel. Subnet-wide\n            // most-recently-active is a projection of these rows (D6b).\n            // [impl->REQ-PRES-1]\n            // The endpoint's own advertised type tag (M9, REQ-MSG-6): the\n            // info.json `state` IS the bound endpoint type\n            // (`EndpointType::as_tag` — `\"gateway\"` for a Gateway surface).\n            // A node advertises ONLY its own endpoints' types; the cross-node\n            // user-msg gate reads this at the receive funnel (T3). A recordless\n            // perch advertises no type → the funnel fail-closes to a re-stamp.\n            // [impl->REQ-MSG-6]\n            let endpoint_type = info.as_ref().map(|i| i.state.clone());\n            // The unified subnet-display parity datums (REQ-SUBNET-DISPLAY-PARITY):\n            // the SAME local facts the picker's `local_rows` reads off info.json,\n            // now GOSSIPED so a remote viewer renders bound/controlled/harness-only\n            // identically instead of reducing every remote row to plain green.\n            //  - bound: a bound live broker session (`is_perch_alive` is bound-gated,\n            //    so an UNBOUND perch reads false — exactly the fill duality the\n            //    picker keys on).\n            //  - controller_node: the broker-owned `driven_by` (the driving node),\n            //    if any → the remote blue \"controlled\" + `controlled by <node>` pin.\n            //  - harness_only: a LiveAgent with no broker PTY seat — mirrors the\n            //    picker's `controllable != Some(true)` for a `live_agent` (a legacy\n            //    None self-corrects on the next bind, so it advertises harness-only\n            //    until then, matching the local amber).\n            // [impl->REQ-SUBNET-DISPLAY-PARITY]\n            let bound = is_perch_alive(&perch_path);\n            // #3 (REQ-GOSSIP-CONTROLLED-ANY): gossip ANY controller, not just a\n            // remote one. `driven_by` is remote-only (KH 7.15); a LOCAL controller\n            // sets the separate `controlled` flag instead, and its driving node IS\n            // this node — so advertise `self.node_hex`. A remote controller keeps\n            // its own origin. This is what lets a remote viewer see a\n            // locally-controlled endpoint as CONTROLLED, not \"ready to control\".\n            // [impl->REQ-GOSSIP-CONTROLLED-ANY]\n            let controller_node = gossip_controller_node(\n                info.as_ref().and_then(|i| i.driven_by.as_deref()),\n                info.as_ref().is_some_and(|i| i.controlled),\n                &self.node_hex,\n            );\n            let harness_only = info.as_ref().is_some_and(|i| {\n                i.state == \"live_agent\" && i.controllable != Some(true)\n            });\n            // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS): the endpoint's\n            // real harness adapter + its recent projects + the EXPLICIT any-controller\n            // truth, gossiped so `from_resource_row` stops faking remote rows.\n            //  - adapter: the composite `<adapter>[:profile]` off info.json (the picker\n            //    used to show the resources blurb here).\n            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\n            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\n            //    a local controller has driven_by=None so that WHO datum undercounts it).\n            // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n            let adapter = info.as_ref().and_then(|i| i.adapter.clone());\n            let controlled = info.as_ref().is_some_and(|i| i.controlled);\n            let recent_projects = recent_projects_for(&perch_path, owlery);\n            let last_active_ms = info.and_then(|i| i.last_active_ms);\n            for sub in &subnets.subnets {\n                let Ok(ep) = epoch.next_epoch() else { continue };\n                let instance = Instance {\n                    node: self.node_hex.clone(),\n                    status,\n                    epoch: ep,\n                    resources: resources.clone(),\n                    last_active_ms,\n                    shell_adapters: shell_adapters.clone(),\n                    // The node label rides every row this node authors\n                    // (REQ-SUBNET-3) — set from the OS hostname at daemon\n                    // startup, so a rename converges by re-advertisement.\n                    // [impl->REQ-SUBNET-3]\n                    node_label: self.node_label.clone(),\n                    // The hashed machine id rides beside it (M8 decision\n                    // 13/22) — the re-pair overwrite anchor.\n                    // [impl->REQ-SUBNET-7]\n                    machine_id: self.machine_id.clone(),\n                    // The endpoint's own type tag (REQ-MSG-6) — read by the\n                    // cross-node user-msg gate, keyed by the proven node.\n                    // [impl->REQ-MSG-6]\n                    endpoint_type: endpoint_type.clone(),\n                    // The unified subnet-display parity datums — gossiped so a\n                    // remote picker renders bound/controlled/harness-only with the\n                    // same fidelity as the home node (REQ-SUBNET-DISPLAY-PARITY).\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\n                    bound,\n                    controller_node: controller_node.clone(),\n                    harness_only,\n                    // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS).\n                    // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n                    adapter: adapter.clone(),\n                    recent_projects: recent_projects.clone(),\n                    controlled,\n                };\n                let reg = regs.entry(sub.name.clone()).or_default();\n                match advertise_if_visible(\n                    reg,\n                    &vis,\n                    &sub.name,\n                    sub.hide_new_endpoints,\n                    &id,\n                    instance.clone(),\n                ) {\n                    Ok(Some(_)) => out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n                        subnet: sub.name.clone(),\n                        endpoint_id: id.clone(),\n                        instance,\n                    })),\n                    Ok(None) => {} // hidden in this subnet — not advertised\n                    Err(collision) => {\n                        // Bare-id collision (another node owns the name in\n                        // this subnet): loud skip — resolution is the user's\n                        // rename (D3f), never a silent overwrite.\n                        eprintln!(\"REGISTRY_COLLISION:{}:{}\", sub.name, collision);\n                    }\n                }\n            }\n        }\n        // Ghost-heal (REQ-HAZARD-ROSTER-GHOST): a LOCAL perch that was ERASED\n        // (`api session-end <id> --erase` removes the owlery dir) is no longer in\n        // `live_ids`, but its last-advertised row lingers ACTIVE in the registry —\n        // this scan only refreshes perches that still exist, so the stale row is\n        // otherwise immortal (no re-advertise to bump it, and own rows are excluded\n        // from silent-peer eviction). Advertise such a row OFFLINE with a fresh\n        // epoch: the lease orders it, so it stops showing Active locally\n        // (resource_projection skips non-routable) AND peers converge to gone. Only\n        // still-routable local rows are touched (idempotent — an already-Offline\n        // ghost is left alone).\n        // [impl->REQ-HAZARD-ROSTER-GHOST]\n        for sub in &subnets.subnets {\n            let reg = regs.entry(sub.name.clone()).or_default();\n            let ghosts: Vec<String> = reg\n                .endpoint_ids()\n                .filter(|id| !live_ids.contains(*id))\n                .filter(|id| {\n                    reg.instances(id)\n                        .iter()\n                        .any(|i| i.node == self.node_hex && i.status.routable())\n                })\n                .map(str::to_string)\n                .collect();\n            for id in ghosts {\n                let Ok(ep) = epoch.next_epoch() else { continue };\n                let instance = Instance {\n                    node: self.node_hex.clone(),\n                    status: Status::Offline,\n                    epoch: ep,\n                    resources: None,\n                    last_active_ms: None,\n                    shell_adapters: Vec::new(),\n                    node_label: self.node_label.clone(),\n                    machine_id: self.machine_id.clone(),\n                    endpoint_type: None,\n                    // An erased ghost is gone — unbound, no controller, not a\n                    // harness seat (REQ-SUBNET-DISPLAY-PARITY); Offline is skipped\n                    // by the projection anyway, so these only round-trip the wire.\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\n                    bound: false,\n                    controller_node: None,\n                    harness_only: false,\n                    adapter: None,\n                    recent_projects: Vec::new(),\n                    controlled: false,\n                };\n                reg.merge_instance(&id, instance.clone());\n                out.push(RegistryFeedRecord::Instance(RegistryUpdate {\n                    subnet: sub.name.clone(),\n                    endpoint_id: id,\n                    instance,\n                }));\n                eprintln!(\"ROSTER_GHOST_HEAL:{}: erased perch advertised offline\", sub.name);\n            }\n        }\n\n        // Node-level label (REQ-SUBNET-3): advertise this node's hostname per\n        // served subnet INDEPENDENT of endpoint rows, so an endpoint-less node\n        // (zero Self perches, or all hidden) still names itself to peers rather\n        // than rendering a bare key-prefix. Rides the same epoch lease; merged\n        // into our own registry too so the snapshot carries it. Emitted every\n        // round regardless of endpoint count — redundant-but-robust when rows\n        // exist (the render prefers a trust/instance label anyway), the SOLE\n        // carrier when they don't.\n        // [impl->REQ-SUBNET-3]\n        if let Some(label) = self.node_label.clone() {\n            for sub in &subnets.subnets {\n                let Ok(ep) = epoch.next_epoch() else { continue };\n                let reg = regs.entry(sub.name.clone()).or_default();\n                reg.merge_node_label(&self.node_hex, label.clone(), ep);\n                out.push(RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\n                    subnet: sub.name.clone(),\n                    node: self.node_hex.clone(),\n                    label: label.clone(),\n                    epoch: ep,\n                }));\n            }\n        }\n        drop(epoch);\n        drop(regs);\n        self.write_snapshots();\n        out\n    }\n\n    /// Evict every registry row authored by a **silent** peer node\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\n    /// within `ttl` has its rows removed from every subnet registry and the\n    /// snapshots rewritten, so a dead node identity can no longer poison\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\n    /// node authors them fresh each pump tick and is never in its own heard\n    /// map). A row whose author has NO heard stamp is treated as silent —\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\n\n[Showing lines 359-643 of 2238. Use :644 to continue]"}],"details":{"truncation":{"content":"            .load(std::sync::atomic::Ordering::Relaxed)\r\n        {\r\n            0 => None,\r\n            ms => Some(ms),\r\n        }\r\n    }\r\n\r\n    /// Scan the owlery and (re-)advertise every self perch into every member\r\n    /// subnet it is visible in, each row stamped with a fresh epoch. Returns\r\n    /// the updates to push to peers (the node's own rows only — peers' rows\r\n    /// reconverge from their own re-advertisement, no transitive gossip in\r\n    /// v1: trust is pairwise per subnet, a relayed third-party row would\r\n    /// carry an origin the receiver can't gate honestly).\r\n    // [impl->REQ-INST-7]\r\n    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\r\n        let subnets = SubnetStore::load();\r\n        let mut vis = VisibilityStore::load();\r\n        // First-join home adoption (D9-5, REQ-INST-15): within one\r\n        // advertisement cadence of the node's first join (a pair lands the\r\n        // subnet record; no pairing-protocol threading needed), unhomed Self\r\n        // perches adopt the sole subnet + seed their sync scope. Strictly\r\n        // adopt-if-unset; a multi-subnet store adopts nothing.\r\n        // [impl->REQ-INST-15]\r\n        if !spt_store::home::adopt_for_unset(owlery, &subnets, &mut vis).is_empty() {\r\n            let _ = vis.save();\r\n        }\r\n        let vis = vis;\r\n        // The node-level resource-blurb seed (D9-4): an endpoint that has not\r\n        // authored its own blurb advertises this default (config seeds, the\r\n        // agent refines — CONTEXT §resource advertisement).\r\n        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\r\n        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\r\n        // §Discovery scope): registered kind=\"shell\" adapters that chose\r\n        // broadcast=subnet ride every row this node authors — the other-node\r\n        // discovery arm. Computed once per scan (the registration set is\r\n        // node-level).\r\n        // [impl->REQ-EP-5]\r\n        let shell_adapters: Vec<String> = spt_runtime::registry::registered(&perch::adapters_dir())\r\n            .iter()\r\n            .filter(|(r, m)| {\r\n                r.kind == spt_runtime::manifest::AdapterKind::Shell\r\n                    && m.shell.as_ref().and_then(|s| s.broadcast)\r\n                        == Some(spt_runtime::manifest::Broadcast::Subnet)\r\n            })\r\n            .map(|(r, _)| r.name.clone())\r\n            .collect();\r\n        let mut out = Vec::new();\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut epoch = self.epoch.lock().unwrap();\r\n\r\n        // The set of self perches that still EXIST on disk this round — the\r\n        // ghost-heal predicate below (a LOCAL registry row whose id is absent\r\n        // here was erased, REQ-HAZARD-ROSTER-GHOST).\r\n        let live_ids: std::collections::BTreeSet<String> =\r\n            perch::list_self_perch_ids(owlery).into_iter().collect();\r\n\r\n        for id in perch::list_self_perch_ids(owlery) {\r\n            let perch_path = perch::resolve_perch_path_in(owlery, &id, ParentHint::Infer);\r\n            // Advertisement follows the resting-state machine (D9-2,\r\n            // REQ-INST-3): a live perch with a resting record advertises its\r\n            // machine state (active / dormant / suspended); a recordless\r\n            // (pre-D9-2 / interim) or dead perch keeps the legacy\r\n            // liveness-derived mapping. Every advertisement stamps a fresh\r\n            // epoch below, so a transition reaches peers as an epoch-bumped\r\n            // update — the lease orders it (KH 4.8).\r\n            // [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\r\n            let status = advertised_status(&perch_path);\r\n            // Endpoint-authored blurb wins; the node seed fills the gap\r\n            // (both-authored, REQ-INST-14). Rides the same epoch lease as\r\n            // every instance update — a blurb edit reaches peers as an\r\n            // ordered, epoch-bumped row.\r\n            // [impl->REQ-INST-14]\r\n            let info = spt_store::info::read_info(&perch_path);\r\n            let resources = info\r\n                .as_ref()\r\n                .and_then(|i| i.resources.clone())\r\n                .or_else(|| node_blurb.clone());\r\n            // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat\r\n            // stamp (`lifecycle::touch_active` — Active seats only; a resting\r\n            // seat never stamps) rides the row like the blurb does — same\r\n            // epoch lease, same visibility gate, no new channel. Subnet-wide\r\n            // most-recently-active is a projection of these rows (D6b).\r\n            // [impl->REQ-PRES-1]\r\n            // The endpoint's own advertised type tag (M9, REQ-MSG-6): the\r\n            // info.json `state` IS the bound endpoint type\r\n            // (`EndpointType::as_tag` — `\"gateway\"` for a Gateway surface).\r\n            // A node advertises ONLY its own endpoints' types; the cross-node\r\n            // user-msg gate reads this at the receive funnel (T3). A recordless\r\n            // perch advertises no type → the funnel fail-closes to a re-stamp.\r\n            // [impl->REQ-MSG-6]\r\n            let endpoint_type = info.as_ref().map(|i| i.state.clone());\r\n            // The unified subnet-display parity datums (REQ-SUBNET-DISPLAY-PARITY):\r\n            // the SAME local facts the picker's `local_rows` reads off info.json,\r\n            // now GOSSIPED so a remote viewer renders bound/controlled/harness-only\r\n            // identically instead of reducing every remote row to plain green.\r\n            //  - bound: a bound live broker session (`is_perch_alive` is bound-gated,\r\n            //    so an UNBOUND perch reads false — exactly the fill duality the\r\n            //    picker keys on).\r\n            //  - controller_node: the broker-owned `driven_by` (the driving node),\r\n            //    if any → the remote blue \"controlled\" + `controlled by <node>` pin.\r\n            //  - harness_only: a LiveAgent with no broker PTY seat — mirrors the\r\n            //    picker's `controllable != Some(true)` for a `live_agent` (a legacy\r\n            //    None self-corrects on the next bind, so it advertises harness-only\r\n            //    until then, matching the local amber).\r\n            // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n            let bound = is_perch_alive(&perch_path);\r\n            // #3 (REQ-GOSSIP-CONTROLLED-ANY): gossip ANY controller, not just a\r\n            // remote one. `driven_by` is remote-only (KH 7.15); a LOCAL controller\r\n            // sets the separate `controlled` flag instead, and its driving node IS\r\n            // this node — so advertise `self.node_hex`. A remote controller keeps\r\n            // its own origin. This is what lets a remote viewer see a\r\n            // locally-controlled endpoint as CONTROLLED, not \"ready to control\".\r\n            // [impl->REQ-GOSSIP-CONTROLLED-ANY]\r\n            let controller_node = gossip_controller_node(\r\n                info.as_ref().and_then(|i| i.driven_by.as_deref()),\r\n                info.as_ref().is_some_and(|i| i.controlled),\r\n                &self.node_hex,\r\n            );\r\n            let harness_only = info.as_ref().is_some_and(|i| {\r\n                i.state == \"live_agent\" && i.controllable != Some(true)\r\n            });\r\n            // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS): the endpoint's\r\n            // real harness adapter + its recent projects + the EXPLICIT any-controller\r\n            // truth, gossiped so `from_resource_row` stops faking remote rows.\r\n            //  - adapter: the composite `<adapter>[:profile]` off info.json (the picker\r\n            //    used to show the resources blurb here).\r\n            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\r\n            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\r\n            //    a local controller has driven_by=None so that WHO datum undercounts it).\r\n            // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\n            let adapter = info.as_ref().and_then(|i| i.adapter.clone());\r\n            let controlled = info.as_ref().is_some_and(|i| i.controlled);\r\n            let recent_projects = recent_projects_for(&perch_path, owlery);\r\n            let last_active_ms = info.and_then(|i| i.last_active_ms);\r\n            for sub in &subnets.subnets {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let instance = Instance {\r\n                    node: self.node_hex.clone(),\r\n                    status,\r\n                    epoch: ep,\r\n                    resources: resources.clone(),\r\n                    last_active_ms,\r\n                    shell_adapters: shell_adapters.clone(),\r\n                    // The node label rides every row this node authors\r\n                    // (REQ-SUBNET-3) — set from the OS hostname at daemon\r\n                    // startup, so a rename converges by re-advertisement.\r\n                    // [impl->REQ-SUBNET-3]\r\n                    node_label: self.node_label.clone(),\r\n                    // The hashed machine id rides beside it (M8 decision\r\n                    // 13/22) — the re-pair overwrite anchor.\r\n                    // [impl->REQ-SUBNET-7]\r\n                    machine_id: self.machine_id.clone(),\r\n                    // The endpoint's own type tag (REQ-MSG-6) — read by the\r\n                    // cross-node user-msg gate, keyed by the proven node.\r\n                    // [impl->REQ-MSG-6]\r\n                    endpoint_type: endpoint_type.clone(),\r\n                    // The unified subnet-display parity datums — gossiped so a\r\n                    // remote picker renders bound/controlled/harness-only with the\r\n                    // same fidelity as the home node (REQ-SUBNET-DISPLAY-PARITY).\r\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n                    bound,\r\n                    controller_node: controller_node.clone(),\r\n                    harness_only,\r\n                    // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS).\r\n                    // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\n                    adapter: adapter.clone(),\r\n                    recent_projects: recent_projects.clone(),\r\n                    controlled,\r\n                };\r\n                let reg = regs.entry(sub.name.clone()).or_default();\r\n                match advertise_if_visible(\r\n                    reg,\r\n                    &vis,\r\n                    &sub.name,\r\n                    sub.hide_new_endpoints,\r\n                    &id,\r\n                    instance.clone(),\r\n                ) {\r\n                    Ok(Some(_)) => out.push(RegistryFeedRecord::Instance(RegistryUpdate {\r\n                        subnet: sub.name.clone(),\r\n                        endpoint_id: id.clone(),\r\n                        instance,\r\n                    })),\r\n                    Ok(None) => {} // hidden in this subnet — not advertised\r\n                    Err(collision) => {\r\n                        // Bare-id collision (another node owns the name in\r\n                        // this subnet): loud skip — resolution is the user's\r\n                        // rename (D3f), never a silent overwrite.\r\n                        eprintln!(\"REGISTRY_COLLISION:{}:{}\", sub.name, collision);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        // Ghost-heal (REQ-HAZARD-ROSTER-GHOST): a LOCAL perch that was ERASED\r\n        // (`api session-end <id> --erase` removes the owlery dir) is no longer in\r\n        // `live_ids`, but its last-advertised row lingers ACTIVE in the registry —\r\n        // this scan only refreshes perches that still exist, so the stale row is\r\n        // otherwise immortal (no re-advertise to bump it, and own rows are excluded\r\n        // from silent-peer eviction). Advertise such a row OFFLINE with a fresh\r\n        // epoch: the lease orders it, so it stops showing Active locally\r\n        // (resource_projection skips non-routable) AND peers converge to gone. Only\r\n        // still-routable local rows are touched (idempotent — an already-Offline\r\n        // ghost is left alone).\r\n        // [impl->REQ-HAZARD-ROSTER-GHOST]\r\n        for sub in &subnets.subnets {\r\n            let reg = regs.entry(sub.name.clone()).or_default();\r\n            let ghosts: Vec<String> = reg\r\n                .endpoint_ids()\r\n                .filter(|id| !live_ids.contains(*id))\r\n                .filter(|id| {\r\n                    reg.instances(id)\r\n                        .iter()\r\n                        .any(|i| i.node == self.node_hex && i.status.routable())\r\n                })\r\n                .map(str::to_string)\r\n                .collect();\r\n            for id in ghosts {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let instance = Instance {\r\n                    node: self.node_hex.clone(),\r\n                    status: Status::Offline,\r\n                    epoch: ep,\r\n                    resources: None,\r\n                    last_active_ms: None,\r\n                    shell_adapters: Vec::new(),\r\n                    node_label: self.node_label.clone(),\r\n                    machine_id: self.machine_id.clone(),\r\n                    endpoint_type: None,\r\n                    // An erased ghost is gone — unbound, no controller, not a\r\n                    // harness seat (REQ-SUBNET-DISPLAY-PARITY); Offline is skipped\r\n                    // by the projection anyway, so these only round-trip the wire.\r\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n                    bound: false,\r\n                    controller_node: None,\r\n                    harness_only: false,\r\n                    adapter: None,\r\n                    recent_projects: Vec::new(),\r\n                    controlled: false,\r\n                };\r\n                reg.merge_instance(&id, instance.clone());\r\n                out.push(RegistryFeedRecord::Instance(RegistryUpdate {\r\n                    subnet: sub.name.clone(),\r\n                    endpoint_id: id,\r\n                    instance,\r\n                }));\r\n                eprintln!(\"ROSTER_GHOST_HEAL:{}: erased perch advertised offline\", sub.name);\r\n            }\r\n        }\r\n\r\n        // Node-level label (REQ-SUBNET-3): advertise this node's hostname per\r\n        // served subnet INDEPENDENT of endpoint rows, so an endpoint-less node\r\n        // (zero Self perches, or all hidden) still names itself to peers rather\r\n        // than rendering a bare key-prefix. Rides the same epoch lease; merged\r\n        // into our own registry too so the snapshot carries it. Emitted every\r\n        // round regardless of endpoint count — redundant-but-robust when rows\r\n        // exist (the render prefers a trust/instance label anyway), the SOLE\r\n        // carrier when they don't.\r\n        // [impl->REQ-SUBNET-3]\r\n        if let Some(label) = self.node_label.clone() {\r\n            for sub in &subnets.subnets {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let reg = regs.entry(sub.name.clone()).or_default();\r\n                reg.merge_node_label(&self.node_hex, label.clone(), ep);\r\n                out.push(RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\r\n                    subnet: sub.name.clone(),\r\n                    node: self.node_hex.clone(),\r\n                    label: label.clone(),\r\n                    epoch: ep,\r\n                }));\r\n            }\r\n        }\r\n        drop(epoch);\r\n        drop(regs);\r\n        self.write_snapshots();\r\n        out\r\n    }\r\n\r\n    /// Evict every registry row authored by a **silent** peer node\r\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\r\n    /// within `ttl` has its rows removed from every subnet registry and the\r\n    /// snapshots rewritten, so a dead node identity can no longer poison\r\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\r\n    /// node authors them fresh each pump tick and is never in its own heard\r\n    /// map). A row whose author has NO heard stamp is treated as silent —\r\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\r","truncated":true,"truncatedBy":"lines","totalLines":1880,"totalBytes":16240,"outputLines":285,"outputBytes":16240,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn last_admit_ms(&self) -> Option<u64> {\n        match self\n…\n            .load(std::sync::atomic::Ordering::Relaxed)\r\n        {\r\n            0 => None,\r\n            ms => Some(ms),\r\n        }\r\n    }\r\n\r\n    /// Scan the owlery and (re-)advertise every self perch into every member\r\n    /// subnet it is visible in, each row stamped with a fresh epoch. Returns\r\n    /// the updates to push to peers (the node's own rows only — peers' rows\r\n    /// reconverge from their own re-advertisement, no transitive gossip in\r\n    /// v1: trust is pairwise per subnet, a relayed third-party row would\r\n    /// carry an origin the receiver can't gate honestly).\r\n    // [impl->REQ-INST-7]\r\n    pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {\r\n        let subnets = SubnetStore::load();\r\n        let mut vis = VisibilityStore::load();\r\n        // First-join home adoption (D9-5, REQ-INST-15): within one\r\n        // advertisement cadence of the node's first join (a pair lands the\r\n        // subnet record; no pairing-protocol threading needed), unhomed Self\r\n        // perches adopt the sole subnet + seed their sync scope. Strictly\r\n        // adopt-if-unset; a multi-subnet store adopts nothing.\r\n        // [impl->REQ-INST-15]\r\n        if !spt_store::home::adopt_for_unset(owlery, &subnets, &mut vis).is_empty() {\r\n            let _ = vis.save();\r\n        }\r\n        let vis = vis;\r\n        // The node-level resource-blurb seed (D9-4): an endpoint that has not\r\n        // authored its own blurb advertises this default (config seeds, the\r\n        // agent refines — CONTEXT §resource advertisement).\r\n        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\r\n        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\r\n        // §Discovery scope): registered kind=\"shell\" adapters that chose\r\n        // broadcast=subnet ride every row this node authors — the other-node\r\n        // discovery arm. Computed once per scan (the registration set is\r\n        // node-level).\r\n        // [impl->REQ-EP-5]\r\n        let shell_adapters: Vec<String> = spt_runtime::registry::registered(&perch::adapters_dir())\r\n            .iter()\r\n            .filter(|(r, m)| {\r\n                r.kind == spt_runtime::manifest::AdapterKind::Shell\r\n                    && m.shell.as_ref().and_then(|s| s.broadcast)\r\n                        == Some(spt_runtime::manifest::Broadcast::Subnet)\r\n            })\r\n            .map(|(r, _)| r.name.clone())\r\n            .collect();\r\n        let mut out = Vec::new();\r\n        let mut regs = self.regs.lock().unwrap();\r\n        let mut epoch = self.epoch.lock().unwrap();\r\n\r\n        // The set of self perches that still EXIST on disk this round — the\r\n        // ghost-heal predicate below (a LOCAL registry row whose id is absent\r\n        // here was erased, REQ-HAZARD-ROSTER-GHOST).\r\n        let live_ids: std::collections::BTreeSet<String> =\r\n            perch::list_self_perch_ids(owlery).into_iter().collect();\r\n\r\n        for id in perch::list_self_perch_ids(owlery) {\r\n            let perch_path = perch::resolve_perch_path_in(owlery, &id, ParentHint::Infer);\r\n            // Advertisement follows the resting-state machine (D9-2,\r\n            // REQ-INST-3): a live perch with a resting record advertises its\r\n            // machine state (active / dormant / suspended); a recordless\r\n            // (pre-D9-2 / interim) or dead perch keeps the legacy\r\n            // liveness-derived mapping. Every advertisement stamps a fresh\r\n            // epoch below, so a transition reaches peers as an epoch-bumped\r\n            // update — the lease orders it (KH 4.8).\r\n            // [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\r\n            let status = advertised_status(&perch_path);\r\n            // Endpoint-authored blurb wins; the node seed fills the gap\r\n            // (both-authored, REQ-INST-14). Rides the same epoch lease as\r\n            // every instance update — a blurb edit reaches peers as an\r\n            // ordered, epoch-bumped row.\r\n            // [impl->REQ-INST-14]\r\n            let info = spt_store::info::read_info(&perch_path);\r\n            let resources = info\r\n                .as_ref()\r\n                .and_then(|i| i.resources.clone())\r\n                .or_else(|| node_blurb.clone());\r\n            // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat\r\n            // stamp (`lifecycle::touch_active` — Active seats only; a resting\r\n            // seat never stamps) rides the row like the blurb does — same\r\n            // epoch lease, same visibility gate, no new channel. Subnet-wide\r\n            // most-recently-active is a projection of these rows (D6b).\r\n            // [impl->REQ-PRES-1]\r\n            // The endpoint's own advertised type tag (M9, REQ-MSG-6): the\r\n            // info.json `state` IS the bound endpoint type\r\n            // (`EndpointType::as_tag` — `\"gateway\"` for a Gateway surface).\r\n            // A node advertises ONLY its own endpoints' types; the cross-node\r\n            // user-msg gate reads this at the receive funnel (T3). A recordless\r\n            // perch advertises no type → the funnel fail-closes to a re-stamp.\r\n            // [impl->REQ-MSG-6]\r\n            let endpoint_type = info.as_ref().map(|i| i.state.clone());\r\n            // The unified subnet-display parity datums (REQ-SUBNET-DISPLAY-PARITY):\r\n            // the SAME local facts the picker's `local_rows` reads off info.json,\r\n            // now GOSSIPED so a remote viewer renders bound/controlled/harness-only\r\n            // identically instead of reducing every remote row to plain green.\r\n            //  - bound: a bound live broker session (`is_perch_alive` is bound-gated,\r\n            //    so an UNBOUND perch reads false — exactly the fill duality the\r\n            //    picker keys on).\r\n            //  - controller_node: the broker-owned `driven_by` (the driving node),\r\n            //    if any → the remote blue \"controlled\" + `controlled by <node>` pin.\r\n            //  - harness_only: a LiveAgent with no broker PTY seat — mirrors the\r\n            //    picker's `controllable != Some(true)` for a `live_agent` (a legacy\r\n            //    None self-corrects on the next bind, so it advertises harness-only\r\n            //    until then, matching the local amber).\r\n            // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n            let bound = is_perch_alive(&perch_path);\r\n            // #3 (REQ-GOSSIP-CONTROLLED-ANY): gossip ANY controller, not just a\r\n            // remote one. `driven_by` is remote-only (KH 7.15); a LOCAL controller\r\n            // sets the separate `controlled` flag instead, and its driving node IS\r\n            // this node — so advertise `self.node_hex`. A remote controller keeps\r\n            // its own origin. This is what lets a remote viewer see a\r\n            // locally-controlled endpoint as CONTROLLED, not \"ready to control\".\r\n            // [impl->REQ-GOSSIP-CONTROLLED-ANY]\r\n            let controller_node = gossip_controller_node(\r\n                info.as_ref().and_then(|i| i.driven_by.as_deref()),\r\n                info.as_ref().is_some_and(|i| i.controlled),\r\n                &self.node_hex,\r\n            );\r\n            let harness_only = info.as_ref().is_some_and(|i| {\r\n                i.state == \"live_agent\" && i.controllable != Some(true)\r\n            });\r\n            // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS): the endpoint's\r\n            // real harness adapter + its recent projects + the EXPLICIT any-controller\r\n            // truth, gossiped so `from_resource_row` stops faking remote rows.\r\n            //  - adapter: the composite `<adapter>[:profile]` off info.json (the picker\r\n            //    used to show the resources blurb here).\r\n            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\r\n            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\r\n            //    a local controller has driven_by=None so that WHO datum undercounts it).\r\n            // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\n            let adapter = info.as_ref().and_then(|i| i.adapter.clone());\r\n            let controlled = info.as_ref().is_some_and(|i| i.controlled);\r\n            let recent_projects = recent_projects_for(&perch_path, owlery);\r\n            let last_active_ms = info.and_then(|i| i.last_active_ms);\r\n            for sub in &subnets.subnets {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let instance = Instance {\r\n                    node: self.node_hex.clone(),\r\n                    status,\r\n                    epoch: ep,\r\n                    resources: resources.clone(),\r\n                    last_active_ms,\r\n                    shell_adapters: shell_adapters.clone(),\r\n                    // The node label rides every row this node authors\r\n                    // (REQ-SUBNET-3) — set from the OS hostname at daemon\r\n                    // startup, so a rename converges by re-advertisement.\r\n                    // [impl->REQ-SUBNET-3]\r\n                    node_label: self.node_label.clone(),\r\n                    // The hashed machine id rides beside it (M8 decision\r\n                    // 13/22) — the re-pair overwrite anchor.\r\n                    // [impl->REQ-SUBNET-7]\r\n                    machine_id: self.machine_id.clone(),\r\n                    // The endpoint's own type tag (REQ-MSG-6) — read by the\r\n                    // cross-node user-msg gate, keyed by the proven node.\r\n                    // [impl->REQ-MSG-6]\r\n                    endpoint_type: endpoint_type.clone(),\r\n                    // The unified subnet-display parity datums — gossiped so a\r\n                    // remote picker renders bound/controlled/harness-only with the\r\n                    // same fidelity as the home node (REQ-SUBNET-DISPLAY-PARITY).\r\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n                    bound,\r\n                    controller_node: controller_node.clone(),\r\n                    harness_only,\r\n                    // The #4 de-faking datums (REQ-GOSSIP-ADAPTER-PROJECTS).\r\n                    // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\n                    adapter: adapter.clone(),\r\n                    recent_projects: recent_projects.clone(),\r\n                    controlled,\r\n                };\r\n                let reg = regs.entry(sub.name.clone()).or_default();\r\n                match advertise_if_visible(\r\n                    reg,\r\n                    &vis,\r\n                    &sub.name,\r\n                    sub.hide_new_endpoints,\r\n                    &id,\r\n                    instance.clone(),\r\n                ) {\r\n                    Ok(Some(_)) => out.push(RegistryFeedRecord::Instance(RegistryUpdate {\r\n                        subnet: sub.name.clone(),\r\n                        endpoint_id: id.clone(),\r\n                        instance,\r\n                    })),\r\n                    Ok(None) => {} // hidden in this subnet — not advertised\r\n                    Err(collision) => {\r\n                        // Bare-id collision (another node owns the name in\r\n                        // this subnet): loud skip — resolution is the user's\r\n                        // rename (D3f), never a silent overwrite.\r\n                        eprintln!(\"REGISTRY_COLLISION:{}:{}\", sub.name, collision);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        // Ghost-heal (REQ-HAZARD-ROSTER-GHOST): a LOCAL perch that was ERASED\r\n        // (`api session-end <id> --erase` removes the owlery dir) is no longer in\r\n        // `live_ids`, but its last-advertised row lingers ACTIVE in the registry —\r\n        // this scan only refreshes perches that still exist, so the stale row is\r\n        // otherwise immortal (no re-advertise to bump it, and own rows are excluded\r\n        // from silent-peer eviction). Advertise such a row OFFLINE with a fresh\r\n        // epoch: the lease orders it, so it stops showing Active locally\r\n        // (resource_projection skips non-routable) AND peers converge to gone. Only\r\n        // still-routable local rows are touched (idempotent — an already-Offline\r\n        // ghost is left alone).\r\n        // [impl->REQ-HAZARD-ROSTER-GHOST]\r\n        for sub in &subnets.subnets {\r\n            let reg = regs.entry(sub.name.clone()).or_default();\r\n            let ghosts: Vec<String> = reg\r\n                .endpoint_ids()\r\n                .filter(|id| !live_ids.contains(*id))\r\n                .filter(|id| {\r\n                    reg.instances(id)\r\n                        .iter()\r\n                        .any(|i| i.node == self.node_hex && i.status.routable())\r\n                })\r\n                .map(str::to_string)\r\n                .collect();\r\n            for id in ghosts {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let instance = Instance {\r\n                    node: self.node_hex.clone(),\r\n                    status: Status::Offline,\r\n                    epoch: ep,\r\n                    resources: None,\r\n                    last_active_ms: None,\r\n                    shell_adapters: Vec::new(),\r\n                    node_label: self.node_label.clone(),\r\n                    machine_id: self.machine_id.clone(),\r\n                    endpoint_type: None,\r\n                    // An erased ghost is gone — unbound, no controller, not a\r\n                    // harness seat (REQ-SUBNET-DISPLAY-PARITY); Offline is skipped\r\n                    // by the projection anyway, so these only round-trip the wire.\r\n                    // [impl->REQ-SUBNET-DISPLAY-PARITY]\r\n                    bound: false,\r\n                    controller_node: None,\r\n                    harness_only: false,\r\n                    adapter: None,\r\n                    recent_projects: Vec::new(),\r\n                    controlled: false,\r\n                };\r\n                reg.merge_instance(&id, instance.clone());\r\n                out.push(RegistryFeedRecord::Instance(RegistryUpdate {\r\n                    subnet: sub.name.clone(),\r\n                    endpoint_id: id,\r\n                    instance,\r\n                }));\r\n                eprintln!(\"ROSTER_GHOST_HEAL:{}: erased perch advertised offline\", sub.name);\r\n            }\r\n        }\r\n\r\n        // Node-level label (REQ-SUBNET-3): advertise this node's hostname per\r\n        // served subnet INDEPENDENT of endpoint rows, so an endpoint-less node\r\n        // (zero Self perches, or all hidden) still names itself to peers rather\r\n        // than rendering a bare key-prefix. Rides the same epoch lease; merged\r\n        // into our own registry too so the snapshot carries it. Emitted every\r\n        // round regardless of endpoint count — redundant-but-robust when rows\r\n        // exist (the render prefers a trust/instance label anyway), the SOLE\r\n        // carrier when they don't.\r\n        // [impl->REQ-SUBNET-3]\r\n        if let Some(label) = self.node_label.clone() {\r\n            for sub in &subnets.subnets {\r\n                let Ok(ep) = epoch.next_epoch() else { continue };\r\n                let reg = regs.entry(sub.name.clone()).or_default();\r\n                reg.merge_node_label(&self.node_hex, label.clone(), ep);\r\n                out.push(RegistryFeedRecord::NodeLabel(NodeLabelUpdate {\r\n                    subnet: sub.name.clone(),\r\n                    node: self.node_hex.clone(),\r\n                    label: label.clone(),\r\n                    epoch: ep,\r\n                }));\r\n            }\r\n        }\r\n        drop(epoch);\r\n        drop(regs);\r\n        self.write_snapshots();\r\n        out\r\n    }\r\n\r\n    /// Evict every registry row authored by a **silent** peer node\r\n    /// (KNOWN-HAZARDS 4.10): a node not heard — no admitted inbound feed —\r\n    /// within `ttl` has its rows removed from every subnet registry and the\r\n    /// snapshots rewritten, so a dead node identity can no longer poison\r\n    /// bare-id resolution with phantom ambiguity. Own rows never decay (this\r\n    /// node authors them fresh each pump tick and is never in its own heard\r\n    /// map). A row whose author has NO heard stamp is treated as silent —\r\n    /// rows only enter via admitted feeds (which stamp) or `advertise_local`\r","startLine":356,"lineNumbers":[356,357,null,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2238,"totalBytes":16240,"outputLines":285,"outputBytes":16240,"shownRange":{"start":359,"end":643},"nextOffset":644}}},"isError":false,"timestamp":1784271778448}}
{"type":"message","id":"8da504cb","parentId":"f22d0aba","timestamp":"2026-07-17T07:03:35.483Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing I/O patterns and thread impact**\n\n**Analyzing broker thread and connection leaks**\n\n**Verifying proper BrokerConn drop sequence**"},{"type":"thinking","thinking":"**Verifying client disconnection cleanup**\n\n**Analyzing broker writer blocking causes**\n\n**Investigating recent_projects size impact**"},{"type":"toolCall","id":"call_3XIFFKWNJMfTmRsVK2Mr5gM2|fc_089095e18118ee7d016a59d3c76cb481908466f17d7e80b60a","name":"grep","arguments":{"i":"Checking project payload bounds","pattern":"fn recent_projects_for|recent_projects_for|take\\(|truncate|dedup","path":"crates/spt-daemon/src/registryhost.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_8ANhk96jY1tq7MXZ0JLYoc2y|fc_089095e18118ee7d016a59d3c76cc881908b864a2f0bed1fc8","name":"read","arguments":{"i":"Reading project gossip helper","path":"crates/spt-daemon/src/registryhost.rs","selector":"840-930"}},{"type":"toolCall","id":"call_04fIMGJxJ7wiu8jQTGGyH7m7|fc_089095e18118ee7d016a59d3c76cd08190a451a3b3ff7dc84c","name":"grep","arguments":{"i":"Checking registry snapshot size bounds","pattern":"recent_projects|resources_blurb|LABEL_CAP|MAX_.*REG|truncate|bounded","path":"crates/spt-net/src; crates/spt-daemon/src/registryhost.rs; crates/spt-store/src/info.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_bN3rhy255r9A1nvtX2VlHHrj|fc_089095e18118ee7d016a59d3c76cd481909d902a4d625693c3","name":"grep","arguments":{"i":"Finding stream info API exposure","pattern":"NetStreamInfo|NetStreamsReply|stream_infos","path":"crates/spt-daemon/src/msg.rs; crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":13600,"output":1201,"cacheRead":228864,"cacheWrite":0,"totalTokens":243665,"reasoningTokens":925,"cost":{"input":0.068,"output":0.03603,"cacheRead":0.11443199999999999,"cacheWrite":0,"total":0.218462}},"stopReason":"toolUse","timestamp":1784271778494,"responseId":"resp_089095e18118ee7d016a59d3a2bfe481908e0145dec507da80","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_089095e18118ee7d016a59d3a5e75081908a30ed9b504d4952","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdO07ZZ7UpVboPcIOKSsFfMpU-9oCKyQHGzXSBDkQJ7CQPwP4cXu86SdaNg3cEl9g2jfGswnxCOpdZYvzxCkDobqZsHKxrFl6fF6VtIXScXiDUX1dla8C9W7im1MWuOHTeEwMv458MHL-znhcv-CgmSsWBFlQxwLqcpFXMke-1qAbFnnrLAT8dZJWZ0MR2Drt2oIQkHTLWkXktJu4IHVA2UaW8wPzqNCtkzhsHSOn3BrkFfOsY85i3IZ1A_YRsraHeIinrQPdex78e3PkBZwm1aoxx3YuuSgnUg5g-BD6QMXs_CvkYJFlFqUJ0LebM-eW9aSXH5FVYOBPCSBtcnRmEY_69Bk-6j-Eddo1m83LGMfbbO4qxYa1XPoq0gm0qsCJ3f4CFe0h5D9_D2ajRi-9qtDqTa-W1345F-lv0hQTPUR0NsvogvD6o2quaDEAe8G2F649ydfhihVdrqZquaZ7bth9f2CepxuAlHK2UTGNDm-6VlvoDAIoP8Qmup0NR__gfEnyJpxuZ_iVXSZDV71Fy2-5wOHlLbynytBHdmBNMFqXYxbCe8AoRTfLle9Yd1PDrcRRGxlElEtXX_i31EvEKAupOUz8a-A4kuTcgo2-FWt5VMzC4mhnNABg2qDReyjkEBqGsGqL3qXWR8TMw87KfyIehwA1CdGkNtx-bZ-0C3bUpk0YBMSpxk4GAWvgwwYGpW2bIT77GLV_OAlYAzv-akePBjrNALUmXoyZPGBXkwOQBind31nHgAI0rqe8t8mSFFOX9-n38wRUP_seBP7iUxkbDHlzop2utIQ40juIox0j-HAIm4dqymYWoyz8qsk5C-mznHziHzUwNZo1ChMBHiyuid2HFA1uGTKyaqsGAtKIuOHMborMfxcq2EWobrlPpYJ0U9ZTCqQcGDms-xMwxdE4KmQqd9rmCRd7cQmh_cJHndAdga10lv6zLYOkVWTfZ1lCqbbdlcaCaCuyJGNmlClEHHpZfZU-PQ8FP07NlBskCn0uwggR0BuDTv5pawYwr1zYiFxgNoNlrtOTGnFCLFu5yJbZS9eVcTrTjxW4zFGGvnoSLfI7TpliYReqZbOtgRrNWz97My82fJ157wtkp_Q7IsvnTtfESOtaz3cqB1Bops6zVz5R457ClQLCCgfxLQ0tqLZE8Vu8lAOxL5CjdAMtGMxHVQTOPd3FadA_TKIvDAkSZjNZtTm-MOmcaziVVVcWOMiPqlrFWwt7K7s3wIxy5T5texxUi_O9CoXQlZEsWA7izAYCTkF60l8UdsDQ1CkzurjGxBoP3J8eYywK02mQFPCW8DRj0gMZifjnFc-lEnNjqfZz-ox7RhQZcV9OC4-Rq4tqo_RfjlTLvNJUpXDs-im4pSxmb8K9dd_ujhm5zDK5sEYcRu2VQL5xvvYlnUKxnbBCowgQ2kc-ztORSBemFCllhL58nG3zEAZeREMiL7PaFrVNMzllOpo1Q3d0TJncbgSlPU-kbMjSl3aFhX4RYwKDgFUPJrZ0xgLGYUIpQ9JQdXo381FPz16Le9BGdTT2afNS8VEfL8cEteJldwZP9XzIeUlk53qmfo89SN8Fbm47pVy3z4m8L2BIq-yVC_NSG0qHn1FoWOrCgog05IsPbCUlcIDzWlHWo2ZslBq8Xlt2V00dCPACtbpykSZrHyh4htNhLw-mzCJILFCQvLM4rLEHAlf0dbFIcHfhye6xQ9XyCJfEIEekwblGe92VikgmZLS_zjt4ZskyMp4NEPPy4bh9tAb0kUwFkBDV1Y6q2e5e-FebnErygqzqEgyq8H5yAAFcbStb1I9B69Ont7zw6BMmuslrwCl5bkxBteO2NR-ORwvvzwZXfWsLGQPodcH3bJus6Gi-HT-H-sMgP8YqnCWeaNKmrIbUu1Fnq5xwJpKwZRAJDr9y3VUSs4WyFODWXbteRLfDZn6e6CmV3wu3uN1Ri9Mcjn_M_hDwXsNcwmm-4h3OYhc6PktiyOihp8xpDCwIfI0VQEBHSId8f_ESowqOig_qsWLcxYew1SldP7L4arSY0groRWdkqbv2JVOnuof7f7HMcsvrW3-dygBQiKfXv3__71nBPpa7gNZqyGnorVG3eArfhiYhikn6kaRmjDf_1WEILFWSLs_oiX_8crMXGG_ijwyw_aYd92aGcwryjD2jNR0uCU10Q67LWVIDf72APi608V1G6pguelJdse3pHXtaxny0LZvh51MFcfOWs7PU_0m6c-7zlcosLZeWDmOZa9UBkCHIt0uDFimwq62cZhaYSE8v7oq95E0jmXFG8VmPBaKdX5MnWoRIozPJKLsHxeRamL7YbXSY56FrGZL8NqwgOgFUjCv0609zvWLCSaX0RfBKAlouF6Q7Rq21lgjFSEk_Hb0PB26SeYMJGcenbHCBlxN6fsac7oygUaZlN5NJQ7vLVaXfBs9TnHycvjAUgsJz2hOIJ8tu9g24UaWipqI9ybQZyrS_RRARWkXJhMWUjVU43PdQ-TsR9z6OkZz_9G3uPn1U0EEiwZbJocLbwlk9Wr1o7rV9htmczZracGNBLvMcqSMrzE2sc6mTk9r-sxOhbCku7bJmJxkGbo3EplVOOoD2wONp6CxCu8SLjQ3rvDsF9dqhbYdhdvyqugxWb2EWPhhK6HR-wtpjiFNI3cW0FjL5OZt0lTEj9MbRCL3htg8m7k-uZjPCD_RxGPcj4_pbG_PpB1oTqkWixvVZgd1MQqFAbrbBVqXuTZkJxbWxxwSw3vKbpW7BALBtXCCtRZjIjWVRjH5-TVxcbSTiwjo_2EZqVb96ZimbVn_IGlNCcBbpqV7fxGZvOf-2omsb4ry_ISOuuCqNDs214_ch7gvHseGVqerz9mwnSVPn-bZ6vDwMizmnQyCz_FkYiWnj5dZY9HFbzXu-MntmfHLQJ96HTNR61I38eMAgcz8Wsvp61_JzmKnUdXbDEzwfHUU3sBVEI-cNtZ24s4xcQ8-WitT_aT6nm8MMSs9SyeaKwlNeOiQRTaTKVdNk5Qf6xadwU4e_G9jZT-tDY8wQXh70ZRm27wSjS9mBY82_yu53f7-5t3ECdzS_OZUFi7Z0MWQPIuvy0-yvbI61DHV1Kci11Oi1tSQYxVE30PoA4zzjfGeSsoVGXf2OB1veBvNQus2rqQnHv-pEjRszBX5NdATibn9sAUNBLrTz9kXDa9XlaDkX-th2bXAADmZbCw1Y6KC3_bSpNSJ6ATp7C2E66bXTza1rybt8Fjt2Rj2ARZpMs1jQcUMiU765swRAYmRb6HTEf1vRGgmVaqZgUVYRzajhlBW-vIjDM6Ba1mK3Qd4ThAbH43lfT-728siTrYpQ5V2DVdP-BLsy6ecDCMeQE2wYMWEB4ySMg4MP5uEznkTG6608MsZfUqZ7hzUcgmacQrsH5tt9tZdMTYFUveS2vaJ10pqbPBmBNmzx2_C0Ou2Jm86Hg_vx3-HyR8gV6tN1VA2Dmnc2tWHM0EErHLAhwUam1-tiAgPbwB3Hn94mcdZWHZNTd7yPeKOXSnShtw1hvKBg3CobhhX5su25dJc6syZNiBksgU_ZFJdF1lT-3UVd8V7LU0MmqWCZc87HTGFNnl-k6c3V7v2Tfyj_GCZsynzV6Ff7ulgHumiB4Ay8d1kE19RLmOsu241OMoaUafa1DqtfVJ1L5sjCqxFBC--HckW23qJRJGqLZ2E8g640EFwWRac9WUM9GROFHK-0GCid1jktpJxNR4OQerCe-cPTKdiBAT5u6TUUVIUG8GCgLgVmRXl4NUPAcAK9H8vxW9K4BbsKI3bvqZvrxm50aXJ93b1n56lSqperY-I18P1Uo4_06bzAsgDVjA9exjFPNlZEZj_6b7e2uU6J_i3eA6t8EhDq2p3Cthte5UIFL64uN_R23olzL4XpRq3jKONKxlZP6MRKKE9dkm8f45urRbMFVUnoInND7AkI-7C9T2IR20NX-3Vwnbzvyvvro0HCS3G3HiXK-xpI3s3-0eeHcuEQ6ufuVyS_SQMFFFdN-sjfjo=","summary":[{"type":"summary_text","text":"**Assessing I/O patterns and thread impact**"},{"type":"summary_text","text":"**Analyzing broker thread and connection leaks**"},{"type":"summary_text","text":"**Verifying proper BrokerConn drop sequence**"}]},{"id":"rs_089095e18118ee7d016a59d3b4d4b48190a681f7dbd396d08d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdPHQ4k9_BPANFq8SKqlpEDpnq7ppxM6X0W5eiT0z3aGuPoEb6W9AwlUEcOYChGIffLYeUmPonhOjVD-oKq3EosvX4wWLLs20dkPO48KPQRRkC0mOQQhDkQr-zxJIXetm3cjBof5n35EKh27cr-YcUk8o29X_a8z3jY_mnbeQnUmx_uN_cJBnXHmk0W8SfUZegB2HnUNjoil2-Bs2Bpp6FijxNm3J3GGzY4_mfUrI9R3PBmOXCxk7YFECE-ZPqMLLOJNVAgMQ7lKjNfXB7TyfwEalkl7ms0PLYUCaLrjAjN-P7yYcruuPY2Ma4Zs49PNU4e7vzt0vJcMmd-LEwSNkcqynxIZHZUPhXXmIfe0YrjagegoMJpLPKDp4uf0HL2wPEjZJq4-DKkuK7cHDa8aeDyzwBbYeCnqNPmhWqR0oVjQDuSW5MfG5WMRf1-y8-x3sq2mnSgs5uIozvskLITAsqS7M-yOj7fHQHUlDr3eHOCxj2uSMrWkdO3y6-zh1RvaR9dIUC06Jk1vooovQ0PzmnDU3DPBvwfxrO7lM2MDIzsfoLN0_cDsGZXA-rCv8HGZPthbgBX_K40xhrFCc7BhJ0eIirC6mLp7b0oBodtoijz_Zt255R8-ypPnwCtJst4pyy74wZE6gSStlPseHEFoX4y494baRI_VOzGFP0NNuCquQGvkL-mFkXYbtOMNhoyIpjkD-7EtOLfkljxRBDJ-x_0SGGNkQzcXflOZGLgZ6chhTP9UVQk1i3wkvs6jCL8fCr_lYoubFH9383oM66fpfNkSEaJfUTS9t22vYQ4DcBBgksBMxgSPGW7l7_mtUdvNh3j9jY1BaKIZuRWjwggtTOktlrrmZoTntu8bLL4iadjvfwmZ0w7EK3doyo1OTJNTjGjcCvkKtnp6iLRFyz-9iUdm8G13F832Dwcm0gHky6bJ5WG5GZ-jtnIgGXcXrHi-g-aK1G0e6wkY7neqXngBjHg6V4tq4sNTL__UKvWUgCgo7PBI8TMLZgHN93LPzcopd_eFS3MYlunLJxrCG5SO4RS_SR6tEPUbXR353rtVAedPl6PJhf2FaCO04NW-HwKROsAK9yzvQ-GYL7WRa-My6U00fKV016dgdtrYyC6uJ4uBMQwaE4mh5vf_f747LbHqHXbLBHXRVPnS8yhb5Geu-f4zsTm9cOH-G2Zzt1r1FFBXzSdB9ljO4PWIhGGcRi-_pV9W-D2FD0RiDfC4aab7KaRtSSJxJlD6cmcjiX3k3INyqSta3ya0ZYyCwzCDNWt2lkifIDrX_XnSbX_iSyrr0RofEsdjfxgStUBDnfLqjYRJsasKTyWvDu-Hr_EWWlgEsgCmeH_hb262VqVXglMzYKLMDHPw3fKfzPx1udaaZPksmJMFduramnxEbT0vfref34znCOUyQ1Y8qaFcY8W2jD5uwpP2-IEzU-q2BPzXvfvRpBKEAi-WgCnM7KMUQ_bbiXK9whFj-gwyminnWTbNfsdCmJ4alnKQk3hcwmxXCSt24z565h_R4QatAVsbN5jI-DDn2LDJcuWVlpbI7YgwBzHcbN-RUd8XBe36yv3tXJ2jpTyPosNleqskuo0St7vz-Wt0lJbBPxOE4tf8XMb1JJZ8B7dNoawziLFIo1BDDKyRhIXD_eAQKyYuRneoO-gXqtbmD2LYr6jZshrJ_8jtPqTgU4LGySOPdx7IyVmQLtrgkDnEuTFZrQZecLAXMOuafYFkzv_00AemQIClZTrUL6D4qQbPiIqzRwtclqVXSS-ZsqucadwwDlLZbj1bF6IT2emNtdhc5CN59gMf34um4-7ELu9MAVPOlekOOmoZswaGpFJAfo62H7QhY6pwWVk9_YrMg1ZapSmyKCXaXxVUKdxkqmZK_EoTw4uBhY7z3R_dwkugQ5F0r7ePBfWTLwr0nRrHGw8aPt5wj5tHRuHG2L09ZEz-bh8ElJElnuUDvdjEIAt5WA39Zh8LJREBjMRgX9gxbKSktAkcllhXN5I53lpioaqqI5P4YQBmqBsbAnE8FN_jpolk6oswwhIFlhH_fgI6rgUjnOVOMJtrKhXfrCwT3VgjxB_dLHkrBrSgPGjXsS5dKphBHtRPdCEBKw4nmgxWoOgnbDHO95Sf-rHpo_rta-f9jbyNX1qq3LQfNhMijWkubfWfQaLTrTpLtgrhFA6JhC8vGM9oKc7dH5ZxwSjJ868rRyQrOwkLtmqB6GvBk-mSE__Gmg7gVzHdrIfp96cs4hsUp1505IOH_viJ9yAHqYYvp8v0qseH3qlAD7Oe2hqj6Y6d6Oj-fSjdr_3tWlv8-sN2gwIBDN3MpkgqXR71XFQjl5v1sQ5ka3heZpd2kEZA28xXGieQiWLIOAby5qTZph1LqVSsObgp-pkdhfHkZ3rkcrauWylvjekl3uWH5Sz_Lrj_9DXQeCWxT3phS8FA0M0Nwiy7b1VOCF2Ts1KEgXrYcV1CQL2Za2oDzO8JOkA_xwIfpMt2Rp3gDovhG-N--SuwDK4fet2YayA67_shFpNbO7_V06H7HSn8KLFO3ZEf6Ef3yQ_XWUmPCNpgV76Cn4Hud2I_6uu14sqYxWvdIC76LEShy2xMpjrHdWh6OdVP3PqGz2IbF56Flw-UnrDQHHIcSMIKG1tze-eTsyT12zDHGLgoGrsZYGfaeIltH5i8bNTcAEjRQ6cdMlMa-J7Wh1wf5l8WQv8SG-0fCBn2UOkR8L1HgUW5QSV5mtBhyUybgmomiPW4kJZwMHXwObMXk3tPUFAZ05ums1pdpbXtfiaY0hycxUPduNJ1OtP_uZFTLlEUOTJaQNhnbNhtDaZQMfdBX1_-XuimUWwgcl0TrCUW2aJTSpFV2jJkTI-JlpLfI64LN_lxfP2GJhZhJbyBUmOfnASr8_uRruICR0UEuVjm70BIxK15ux-1QWsfPjJwDTeMJ2GFcJ-zIrMcHbKRWkg6QQWzjI0ERxPCsOq4ttPCxmuyRpUoRQgOITsEM5hzTpsvWBwZzNZ4qrFG4q05W8CAYR4hTpPG9JwsHddh_mncjiGiqTzVz4QRLaprkg4Yq8XkF_v5tBZ9cKUNpuICZ-AJWIM2JXwfblYQXihlSW2tLFJcxqkANktvp4B2njKc3sMq6kiGjpL2H7TJu9PAuWK5m_C_LmrQaF6VD66CbhLGDpT0I_BsVAVS8t0xcoDu-fH9FMFIexE9H68c9LxXNP7KpP16g58JBcfJoieca8_vPn5q7tEo81f3Zyeg8GIQEINRFF-6d0cTammF3aeb555Z3gFq1VlfXSO-PH1dtkI33Mm4HnkD_1rE3iZvH77k43nOKwrOy1rpt7fqgwunOB7_XdwNVKX0IMvorSctm7EBKMUXmiKTGyxEHvt308AGbAhORfV5nhn_79VXmRDO3vWxUGEMyg6rQ0MPeoY5lp4X1H2egAb5ZcK_5i7Zi7eDx3jouX-yTstAssYy6lOZtZL9I4O8yl6-ggBMrhbnR9jpANnIpxmj_6rvj0lQixdNcqa4vq0-YgWWHM__HjJmPgBdxEbjbOSrf9HE9DPO6KgKD1Dj1p1US82wg8LIfth9bRoKEoiZkYX7KzcK8OynpR2E5npplG-p5_0CPGvw3HFKo21m4iLFwXAKv9EM6TAe-1Q96zs5ybPpuFW801Rt","summary":[{"type":"summary_text","text":"**Verifying client disconnection cleanup**"},{"type":"summary_text","text":"**Analyzing broker writer blocking causes**"},{"type":"summary_text","text":"**Investigating recent_projects size impact**"}]},{"id":"fc_089095e18118ee7d016a59d3c76cb481908466f17d7e80b60a","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking project payload bounds\",\"pattern\":\"fn recent_projects_for|recent_projects_for|take\\\\(|truncate|dedup\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_3XIFFKWNJMfTmRsVK2Mr5gM2","name":"grep"},{"id":"fc_089095e18118ee7d016a59d3c76cc881908b864a2f0bed1fc8","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading project gossip helper\",\"path\":\"crates/spt-daemon/src/registryhost.rs\",\"selector\":\"840-930\"}","call_id":"call_8ANhk96jY1tq7MXZ0JLYoc2y","name":"read"},{"id":"fc_089095e18118ee7d016a59d3c76cd08190a451a3b3ff7dc84c","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking registry snapshot size bounds\",\"pattern\":\"recent_projects|resources_blurb|LABEL_CAP|MAX_.*REG|truncate|bounded\",\"path\":\"crates/spt-net/src; crates/spt-daemon/src/registryhost.rs; crates/spt-store/src/info.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_04fIMGJxJ7wiu8jQTGGyH7m7","name":"grep"},{"id":"fc_089095e18118ee7d016a59d3c76cd481909d902a4d625693c3","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding stream info API exposure\",\"pattern\":\"NetStreamInfo|NetStreamsReply|stream_infos\",\"path\":\"crates/spt-daemon/src/msg.rs; crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_bN3rhy255r9A1nvtX2VlHHrj","name":"grep"}]},"duration":36986.45949999988,"ttft":3494.381899997592,"contextSnapshot":{"promptTokens":242464,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_3XIFFKWNJMfTmRsVK2Mr5gM2|fc_089095e18118ee7d016a59d3c76cb481908466f17d7e80b60a","toolName":"grep","startedAt":"2026-07-17T07:03:35.483Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Checking project payload bounds"},"id":"e99cb668","parentId":"8da504cb","timestamp":"2026-07-17T07:03:35.483Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_8ANhk96jY1tq7MXZ0JLYoc2y|fc_089095e18118ee7d016a59d3c76cc881908b864a2f0bed1fc8","toolName":"read","startedAt":"2026-07-17T07:03:35.483Z","args":{"path":"crates/spt-daemon/src/registryhost.rs"},"intent":"Reading project gossip helper"},"id":"9097e8e3","parentId":"e99cb668","timestamp":"2026-07-17T07:03:35.483Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_04fIMGJxJ7wiu8jQTGGyH7m7|fc_089095e18118ee7d016a59d3c76cd08190a451a3b3ff7dc84c","toolName":"grep","startedAt":"2026-07-17T07:03:35.483Z","args":{"path":"crates/spt-net/src; crates/spt-daemon/src/registryhost.rs; crates/spt-store/src/info.rs"},"intent":"Checking registry snapshot size bounds"},"id":"6e30f6ac","parentId":"9097e8e3","timestamp":"2026-07-17T07:03:35.483Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bN3rhy255r9A1nvtX2VlHHrj|fc_089095e18118ee7d016a59d3c76cd481909d902a4d625693c3","toolName":"grep","startedAt":"2026-07-17T07:03:35.484Z","args":{"path":"crates/spt-daemon/src/msg.rs; crates/spt-daemon/src"},"intent":"Finding stream info API exposure"},"id":"8cb3ff88","parentId":"6e30f6ac","timestamp":"2026-07-17T07:03:35.484Z"}
{"type":"message","id":"f03e92de","parentId":"8cb3ff88","timestamp":"2026-07-17T07:03:35.487Z","message":{"role":"toolResult","toolCallId":"call_3XIFFKWNJMfTmRsVK2Mr5gM2|fc_089095e18118ee7d016a59d3c76cb481908466f17d7e80b60a","toolName":"grep","content":[{"type":"text","text":" 490|            let controlled = info.as_ref().is_some_and(|i| i.controlled);\n*491|            let recent_projects = recent_projects_for(&perch_path, owlery);\n 492|            let last_active_ms = info.and_then(|i| i.last_active_ms);\n 493|            for sub in &subnets.subnets {\n 494|                let Ok(ep) = epoch.next_epoch() else { continue };\n...\n 888|/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\n*889|/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\n 890|/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\n 891|/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\n 892|// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n*893|fn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\n 894|    let entries = spt_store::sessions::read_all(perch_path);\n 895|    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\n 896|    let mut out: Vec<String> = Vec::new();\n...\n 1098|\n*1099|    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: recent_projects_for derives the gossiped\n 1100|    // project IDs from the session ledger newest→oldest, excludes owlery-internal cwds\n*1101|    // (a psyche-host session is not a project), dedups (a project's newest occurrence\n 1102|    // wins its slot), and bounds the count. Project IDs only — no dirs on the wire.\n 1103|    #[test]\n*1104|    fn recent_projects_for_dedups_newest_first_excludes_owlery() {\n 1105|        let tmp = tempfile::tempdir().unwrap();\n 1106|        let root = tmp.path();\n 1107|        let owlery = root.join(\"owlery\");\n...\n 1134|\n*1135|        let got = recent_projects_for(&perch, &owlery);\n*1136|        // newest-first, owlery excluded, alpha deduped to its newest slot.\n 1137|        assert_eq!(got, vec![\"gamma\", \"alpha\", \"beta\"], \"got {got:?}\");\n 1138|    }\n 1139|"}],"details":{"scopePath":"crates/spt-daemon/src/registryhost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":8,"fileCount":1,"files":["crates/spt-daemon/src/registryhost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/registryhost.rs","count":8}],"truncated":false,"displayContent":"  490│            let controlled = info.as_ref().is_some_and(|i| i.controlled);\n *491│            let recent_projects = recent_projects_for(&perch_path, owlery);\n  492│            let last_active_ms = info.and_then(|i| i.last_active_ms);\n  493│            for sub in &subnets.subnets {\n  494│                let Ok(ep) = epoch.next_epoch() else { continue };\n     │...\n  888│/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\n *889│/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\n  890│/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\n  891│/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\n  892│// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n *893│fn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\n  894│    let entries = spt_store::sessions::read_all(perch_path);\n  895│    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\n  896│    let mut out: Vec<String> = Vec::new();\n     │...\n 1098│\n*1099│    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: recent_projects_for derives the gossiped\n 1100│    // project IDs from the session ledger newest→oldest, excludes owlery-internal cwds\n*1101│    // (a psyche-host session is not a project), dedups (a project's newest occurrence\n 1102│    // wins its slot), and bounds the count. Project IDs only — no dirs on the wire.\n 1103│    #[test]\n*1104│    fn recent_projects_for_dedups_newest_first_excludes_owlery() {\n 1105│        let tmp = tempfile::tempdir().unwrap();\n 1106│        let root = tmp.path();\n 1107│        let owlery = root.join(\"owlery\");\n     │...\n 1134│\n*1135│        let got = recent_projects_for(&perch, &owlery);\n*1136│        // newest-first, owlery excluded, alpha deduped to its newest slot.\n 1137│        assert_eq!(got, vec![\"gamma\", \"alpha\", \"beta\"], \"got {got:?}\");\n 1138│    }\n 1139│"},"isError":false,"timestamp":1784271815487}}
{"type":"message","id":"bc807760","parentId":"f03e92de","timestamp":"2026-07-17T07:03:35.509Z","message":{"role":"toolResult","toolCallId":"call_8ANhk96jY1tq7MXZ0JLYoc2y|fc_089095e18118ee7d016a59d3c76cc881908b864a2f0bed1fc8","toolName":"read","content":[{"type":"text","text":"///   node never self-gossips Offline; node-down is only ever remote-inferred via\n///   eviction, and `RestState` has no Offline).\n/// - **Corrupt** (info.json present but destroyed — a hard-reset NUL zero-fill):\n///   `is_perch_alive` now reads false for a corrupt record and `is_perch_unbound`\n///   reads false too (unparseable ⇒ no status), so a corrupt perch lands in the\n///   cold arm → `Suspended`, NEVER Active/Dormant. Without this the daemon\n///   self-gossiped a wiped endpoint as ONLINE forever\n///   (REQ-HAZARD-CORRUPT-PERCH-COHERENCE, counter-39 bug #2).\n// [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\n// [impl->REQ-HAZARD-CORRUPT-PERCH-COHERENCE]\n// [impl->REQ-EFFECTIVE-INSTANCE-STATE]\nfn advertised_status(perch_path: &Path) -> Status {\n    let intent = crate::resting::read_rest(perch_path).map(|r| r.state);\n    match crate::resting::effective_rest_state(\n        is_perch_alive(perch_path),\n        is_perch_unbound(perch_path),\n        intent,\n    ) {\n        crate::resting::RestState::Active => Status::Active,\n        crate::resting::RestState::Dormant => Status::Dormant,\n        crate::resting::RestState::Suspended => Status::Suspended,\n    }\n}\n\n/// The controlling node to gossip on an Instance (#3, REQ-GOSSIP-CONTROLLED-ANY).\n/// A REMOTE controller keeps its own `driven_by` origin. A LOCAL controller (the\n/// separate `controlled` flag, with `driven_by` = None per KH 7.15) is driven from\n/// THIS node, so it gossips `self_node`. An undriven endpoint gossips `None`. Pure\n/// over its inputs so the any-controller mapping is unit-tested.\n// [impl->REQ-GOSSIP-CONTROLLED-ANY]\nfn gossip_controller_node(\n    driven_by: Option<&str>,\n    controlled: bool,\n    self_node: &str,\n) -> Option<String> {\n    match driven_by {\n        Some(remote) => Some(remote.to_string()),\n        None if controlled => Some(self_node.to_string()),\n        None => None,\n    }\n}\n\n/// Bound on the recent-project IDs a single instance gossips — newest-first, so the\n/// cap keeps the freshest projects. Keeps the wire compact (a long-lived endpoint can\n/// accumulate many cwds); the picker's project pane never needs more.\nconst MAX_GOSSIPED_PROJECTS: usize = 8;\n\n/// The endpoint's recent project IDs newest→oldest for gossip (#4,\n/// REQ-GOSSIP-ADAPTER-PROJECTS): the session ledger's cwds mapped to project IDs,\n/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\n/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\n/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\n/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\n// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\nfn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\n    let entries = spt_store::sessions::read_all(perch_path);\n    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\n    let mut out: Vec<String> = Vec::new();\n    for cwd in entries.iter().rev().filter_map(|e| e.cwd.as_deref()) {\n        let dir = Path::new(cwd);\n        if crate::lifecycle::is_owlery_internal(dir, owlery) {\n            continue;\n        }\n        let pid = spt_store::project::project_id_for_dir(dir);\n        if pid.is_empty() {\n            continue;\n        }\n        if seen.insert(pid.clone()) {\n            out.push(pid);\n            if out.len() >= MAX_GOSSIPED_PROJECTS {\n                break;\n            }\n        }\n    }\n    out\n}\n\n/// The superseded identities a completed ceremony's intro reveals (pure,\n/// M8 decision 13 / REQ-SUBNET-7): rows in `reg` advertising the SAME node\n/// label AND machine id under a DIFFERENT node key than the re-paired\n/// joiner. Both must match — a label alone re-pairs nothing (hostnames\n/// collide), a machine id alone is never gossiped without its label.\n// [impl->REQ-SUBNET-7]\npub fn superseded_identities(\n    reg: &SubnetRegistry,\n    intro: &spt_net::net::pairing::wire::NodeIntro,\n    joiner_hex: &str,\n) -> Vec<String> {\n    let mut out = Vec::new();\n    for id in reg.endpoint_ids() {\n        for inst in reg.instances(id) {\n            if inst.node != joiner_hex\n                && inst.node_label.as_deref() == Some(intro.label.as_str())\n                && inst.machine_id.as_deref() == Some(intro.machine_id.as_str())\n                && !out.contains(&inst.node)\n…\n            }\n        }\n    }\n…\n}\n\n[Showing lines 839-933 of 2238. Use :934 to continue]"}],"details":{"truncation":{"content":"///   node never self-gossips Offline; node-down is only ever remote-inferred via\r\n///   eviction, and `RestState` has no Offline).\r\n/// - **Corrupt** (info.json present but destroyed — a hard-reset NUL zero-fill):\r\n///   `is_perch_alive` now reads false for a corrupt record and `is_perch_unbound`\r\n///   reads false too (unparseable ⇒ no status), so a corrupt perch lands in the\r\n///   cold arm → `Suspended`, NEVER Active/Dormant. Without this the daemon\r\n///   self-gossiped a wiped endpoint as ONLINE forever\r\n///   (REQ-HAZARD-CORRUPT-PERCH-COHERENCE, counter-39 bug #2).\r\n// [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\r\n// [impl->REQ-HAZARD-CORRUPT-PERCH-COHERENCE]\r\n// [impl->REQ-EFFECTIVE-INSTANCE-STATE]\r\nfn advertised_status(perch_path: &Path) -> Status {\r\n    let intent = crate::resting::read_rest(perch_path).map(|r| r.state);\r\n    match crate::resting::effective_rest_state(\r\n        is_perch_alive(perch_path),\r\n        is_perch_unbound(perch_path),\r\n        intent,\r\n    ) {\r\n        crate::resting::RestState::Active => Status::Active,\r\n        crate::resting::RestState::Dormant => Status::Dormant,\r\n        crate::resting::RestState::Suspended => Status::Suspended,\r\n    }\r\n}\r\n\r\n/// The controlling node to gossip on an Instance (#3, REQ-GOSSIP-CONTROLLED-ANY).\r\n/// A REMOTE controller keeps its own `driven_by` origin. A LOCAL controller (the\r\n/// separate `controlled` flag, with `driven_by` = None per KH 7.15) is driven from\r\n/// THIS node, so it gossips `self_node`. An undriven endpoint gossips `None`. Pure\r\n/// over its inputs so the any-controller mapping is unit-tested.\r\n// [impl->REQ-GOSSIP-CONTROLLED-ANY]\r\nfn gossip_controller_node(\r\n    driven_by: Option<&str>,\r\n    controlled: bool,\r\n    self_node: &str,\r\n) -> Option<String> {\r\n    match driven_by {\r\n        Some(remote) => Some(remote.to_string()),\r\n        None if controlled => Some(self_node.to_string()),\r\n        None => None,\r\n    }\r\n}\r\n\r\n/// Bound on the recent-project IDs a single instance gossips — newest-first, so the\r\n/// cap keeps the freshest projects. Keeps the wire compact (a long-lived endpoint can\r\n/// accumulate many cwds); the picker's project pane never needs more.\r\nconst MAX_GOSSIPED_PROJECTS: usize = 8;\r\n\r\n/// The endpoint's recent project IDs newest→oldest for gossip (#4,\r\n/// REQ-GOSSIP-ADAPTER-PROJECTS): the session ledger's cwds mapped to project IDs,\r\n/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\r\n/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\r\n/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\r\n/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\r\n// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\nfn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\r\n    let entries = spt_store::sessions::read_all(perch_path);\r\n    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\r\n    let mut out: Vec<String> = Vec::new();\r\n    for cwd in entries.iter().rev().filter_map(|e| e.cwd.as_deref()) {\r\n        let dir = Path::new(cwd);\r\n        if crate::lifecycle::is_owlery_internal(dir, owlery) {\r\n            continue;\r\n        }\r\n        let pid = spt_store::project::project_id_for_dir(dir);\r\n        if pid.is_empty() {\r\n            continue;\r\n        }\r\n        if seen.insert(pid.clone()) {\r\n            out.push(pid);\r\n            if out.len() >= MAX_GOSSIPED_PROJECTS {\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    out\r\n}\r\n\r\n/// The superseded identities a completed ceremony's intro reveals (pure,\r\n/// M8 decision 13 / REQ-SUBNET-7): rows in `reg` advertising the SAME node\r\n/// label AND machine id under a DIFFERENT node key than the re-paired\r\n/// joiner. Both must match — a label alone re-pairs nothing (hostnames\r\n/// collide), a machine id alone is never gossiped without its label.\r\n// [impl->REQ-SUBNET-7]\r\npub fn superseded_identities(\r\n    reg: &SubnetRegistry,\r\n    intro: &spt_net::net::pairing::wire::NodeIntro,\r\n    joiner_hex: &str,\r\n) -> Vec<String> {\r\n    let mut out = Vec::new();\r\n    for id in reg.endpoint_ids() {\r\n        for inst in reg.instances(id) {\r\n            if inst.node != joiner_hex\r\n                && inst.node_label.as_deref() == Some(intro.label.as_str())\r\n                && inst.machine_id.as_deref() == Some(intro.machine_id.as_str())\r\n                && !out.contains(&inst.node)\r","truncated":true,"truncatedBy":"lines","totalLines":1400,"totalBytes":4477,"outputLines":95,"outputBytes":4477,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"///   node never self-gossips Offline; node-down is only ever remote-inferred via\r\n///   eviction, and `RestState` has no Offline).\r\n/// - **Corrupt** (info.json present but destroyed — a hard-reset NUL zero-fill):\r\n///   `is_perch_alive` now reads false for a corrupt record and `is_perch_unbound`\r\n///   reads false too (unparseable ⇒ no status), so a corrupt perch lands in the\r\n///   cold arm → `Suspended`, NEVER Active/Dormant. Without this the daemon\r\n///   self-gossiped a wiped endpoint as ONLINE forever\r\n///   (REQ-HAZARD-CORRUPT-PERCH-COHERENCE, counter-39 bug #2).\r\n// [impl->REQ-INST-3] [impl->REQ-PRESENCE-LIVENESS-TRUTH]\r\n// [impl->REQ-HAZARD-CORRUPT-PERCH-COHERENCE]\r\n// [impl->REQ-EFFECTIVE-INSTANCE-STATE]\r\nfn advertised_status(perch_path: &Path) -> Status {\r\n    let intent = crate::resting::read_rest(perch_path).map(|r| r.state);\r\n    match crate::resting::effective_rest_state(\r\n        is_perch_alive(perch_path),\r\n        is_perch_unbound(perch_path),\r\n        intent,\r\n    ) {\r\n        crate::resting::RestState::Active => Status::Active,\r\n        crate::resting::RestState::Dormant => Status::Dormant,\r\n        crate::resting::RestState::Suspended => Status::Suspended,\r\n    }\r\n}\r\n\r\n/// The controlling node to gossip on an Instance (#3, REQ-GOSSIP-CONTROLLED-ANY).\r\n/// A REMOTE controller keeps its own `driven_by` origin. A LOCAL controller (the\r\n/// separate `controlled` flag, with `driven_by` = None per KH 7.15) is driven from\r\n/// THIS node, so it gossips `self_node`. An undriven endpoint gossips `None`. Pure\r\n/// over its inputs so the any-controller mapping is unit-tested.\r\n// [impl->REQ-GOSSIP-CONTROLLED-ANY]\r\nfn gossip_controller_node(\r\n    driven_by: Option<&str>,\r\n    controlled: bool,\r\n    self_node: &str,\r\n) -> Option<String> {\r\n    match driven_by {\r\n        Some(remote) => Some(remote.to_string()),\r\n        None if controlled => Some(self_node.to_string()),\r\n        None => None,\r\n    }\r\n}\r\n\r\n/// Bound on the recent-project IDs a single instance gossips — newest-first, so the\r\n/// cap keeps the freshest projects. Keeps the wire compact (a long-lived endpoint can\r\n/// accumulate many cwds); the picker's project pane never needs more.\r\nconst MAX_GOSSIPED_PROJECTS: usize = 8;\r\n\r\n/// The endpoint's recent project IDs newest→oldest for gossip (#4,\r\n/// REQ-GOSSIP-ADAPTER-PROJECTS): the session ledger's cwds mapped to project IDs,\r\n/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\r\n/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\r\n/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\r\n/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\r\n// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\r\nfn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\r\n    let entries = spt_store::sessions::read_all(perch_path);\r\n    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\r\n    let mut out: Vec<String> = Vec::new();\r\n    for cwd in entries.iter().rev().filter_map(|e| e.cwd.as_deref()) {\r\n        let dir = Path::new(cwd);\r\n        if crate::lifecycle::is_owlery_internal(dir, owlery) {\r\n            continue;\r\n        }\r\n        let pid = spt_store::project::project_id_for_dir(dir);\r\n        if pid.is_empty() {\r\n            continue;\r\n        }\r\n        if seen.insert(pid.clone()) {\r\n            out.push(pid);\r\n            if out.len() >= MAX_GOSSIPED_PROJECTS {\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    out\r\n}\r\n\r\n/// The superseded identities a completed ceremony's intro reveals (pure,\r\n/// M8 decision 13 / REQ-SUBNET-7): rows in `reg` advertising the SAME node\r\n/// label AND machine id under a DIFFERENT node key than the re-paired\r\n/// joiner. Both must match — a label alone re-pairs nothing (hostnames\r\n/// collide), a machine id alone is never gossiped without its label.\r\n// [impl->REQ-SUBNET-7]\r\npub fn superseded_identities(\r\n    reg: &SubnetRegistry,\r\n    intro: &spt_net::net::pairing::wire::NodeIntro,\r\n    joiner_hex: &str,\r\n) -> Vec<String> {\r\n    let mut out = Vec::new();\r\n    for id in reg.endpoint_ids() {\r\n        for inst in reg.instances(id) {\r\n            if inst.node != joiner_hex\r\n                && inst.node_label.as_deref() == Some(intro.label.as_str())\r\n                && inst.machine_id.as_deref() == Some(intro.machine_id.as_str())\r\n                && !out.contains(&inst.node)\r\n…\n            }\n        }\n    }\n…\n}","startLine":839,"lineNumbers":[839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,null,936,937,938,null,940]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\registryhost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2238,"totalBytes":4477,"outputLines":95,"outputBytes":4477,"shownRange":{"start":839,"end":933},"nextOffset":934}}},"isError":false,"timestamp":1784271815509}}
{"type":"message","id":"f143cafc","parentId":"bc807760","timestamp":"2026-07-17T07:03:35.514Z","message":{"role":"toolResult","toolCallId":"call_bN3rhy255r9A1nvtX2VlHHrj|fc_089095e18118ee7d016a59d3c76cd481909d902a4d625693c3","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## msg.rs\n 783|#[derive(Debug, Clone, Serialize, Deserialize)]\n*784|pub struct NetStreamInfo {\n 785|    /// The broker-minted stream id.\n 786|    pub stream_id: u64,\n 787|    /// The connection it rides.\n...\n 811|#[derive(Debug, Clone, Serialize, Deserialize)]\n*812|pub struct NetStreamsReply {\n 813|    /// Held streams (opened + peer-initiated), unordered.\n 814|    #[serde(default)]\n*815|    pub streams: Vec<NetStreamInfo>,\n 816|}\n 817|\n 818|/// `net-stream-retire` payload (ADR-0038 A).\n## attach.rs\n 274|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*275|/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 276|/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n 277|/// is being attached, for the gate. `net_from_seq` is this brain's resume\n 278|/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n## brain.rs\n 42|    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,\n*43|    NetStreamSubscribeReq, NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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...\n 151|    /// The broker's held-streams listing (D4b).\n*152|    NetStreams(NetStreamsReply),\n 153|    /// The broker's hosted-sessions listing (D9-1: session → endpoint).\n 154|    Sessions(SessionsReply),\n 155|    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).\n...\n 788|                KIND_NET_STREAMS_REPLY => {\n*789|                    let ev: NetStreamsReply = serde_json::from_value(env.payload)\n 790|                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n 791|                    return Ok(BrokerEvent::NetStreams(ev));\n 792|                }\n...\n 1435|    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n*1436|    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n 1437|        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438|        loop {\n 1439|            match self.read_event()? {\n## broker.rs\n 58|    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n*59|    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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...\n 4369|            KIND_NET_STREAMS_REPLY,\n*4370|            serde_json::to_value(NetStreamsReply {\n*4371|                streams: host.stream_infos(),\n 4372|            })\n*4373|            .expect(\"NetStreamsReply serializes\"),\n 4374|        );\n 4375|        send_frame(send, &frame);\n 4376|        Ok(())\n## nethost.rs\n 50|    net_presence_event_envelope, net_stream_data_envelope, net_stream_eof_envelope,\n*51|    NetPresenceEvent, NetStreamInfo, PRESENCE_CONNECTED, PRESENCE_DIAL_FAILED, PRESENCE_DISCONNECTED,\n 52|};\n 53|\n 54|/// The reserved [`crate::effect::EffectKey`] session namespace for net-scoped\n...\n 1804|    /// enumeration is exactly what a future dispatcher generation would replay.\n*1805|    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806|        let streams = self.shared.streams.lock().unwrap();\n 1807|        streams\n 1808|            .iter()\n...\n 1811|                let log = e.log.lock().unwrap();\n*1812|                NetStreamInfo {\n 1813|                    stream_id: *id,\n 1814|                    conn_id: e.conn_id,\n 1815|                    remote_id_hex: e.remote_id_hex.clone(),\n...\n 1828|    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n*1829|    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n 1830|    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n 1831|    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n 1832|    /// reply sends when it removed the entry (see the `retired` field note).\n...\n 2164|        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n*2165|        let infos = host.stream_infos();\n 2166|        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167|        let op = infos\n 2168|            .iter()\n...\n 2189|            if host\n*2190|                .stream_infos()\n 2191|                .iter()\n 2192|                .any(|s| s.stream_id == peer_id && s.next_seq > 0)\n 2193|            {\n...\n 2387|    // named row from dispatch eligibility: the retired stream leaves the\n*2388|    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389|    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390|    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391|    // teardown — the transport half of the contract is the sibling test\n...\n 2414|        assert!(host.retire_stream(shell), \"first retire hides the row\");\n*2415|        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416|        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417|        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418|        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n...\n 2484|        for _ in 0..400 {\n*2485|            if !b.stream_infos().is_empty() {\n 2486|                saw = true;\n 2487|                break;\n 2488|            }\n...\n 2496|        for _ in 0..600 {\n*2497|            if b.stream_infos().is_empty() {\n 2498|                swept = true;\n 2499|                break;\n 2500|            }\n## propagate.rs\n 99|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*100|/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 101|/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against the `roster`\n 102|/// **here**, fail-closed: a non-member peer gets the up-to-date shape and\n 103|/// learns nothing (not even whether a release is staged).\n## sync.rs\n 177|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*178|/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 179|/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against\n 180|/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull\n 181|/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient\n## wan.rs\n 227|/// the QUIC handshake-proven remote node id (the broker's\n*228|/// `NetStreamInfo::remote_id_hex` for the stream the record arrived on) —\n 229|/// never anything read out of record bytes (REQ-HAZARD-WAN-ORIGIN-AUTH).\n 230|/// `registry` is the node's [`RegistryHost`]: the user-msg identity gate\n 231|/// resolves the origin endpoint's advertised type from it, keyed by\n## xfer.rs\n 214|/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*215|/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 216|/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint being reached,\n 217|/// for the gate. `root` is the transfer root all wire paths resolve under;\n 218|/// `progress_dir` is where this end's progress records land"}],"details":{"scopePath":"crates/spt-daemon/src/msg.rs, crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":26,"fileCount":9,"files":["crates/spt-daemon/src/msg.rs","crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/propagate.rs","crates/spt-daemon/src/sync.rs","crates/spt-daemon/src/wan.rs","crates/spt-daemon/src/xfer.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/msg.rs","count":3},{"path":"crates/spt-daemon/src/attach.rs","count":1},{"path":"crates/spt-daemon/src/brain.rs","count":4},{"path":"crates/spt-daemon/src/broker.rs","count":4},{"path":"crates/spt-daemon/src/nethost.rs","count":10},{"path":"crates/spt-daemon/src/propagate.rs","count":1},{"path":"crates/spt-daemon/src/sync.rs","count":1},{"path":"crates/spt-daemon/src/wan.rs","count":1},{"path":"crates/spt-daemon/src/xfer.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## msg.rs\n 783│#[derive(Debug, Clone, Serialize, Deserialize)]\n*784│pub struct NetStreamInfo {\n 785│    /// The broker-minted stream id.\n 786│    pub stream_id: u64,\n 787│    /// The connection it rides.\n    │...\n 811│#[derive(Debug, Clone, Serialize, Deserialize)]\n*812│pub struct NetStreamsReply {\n 813│    /// Held streams (opened + peer-initiated), unordered.\n 814│    #[serde(default)]\n*815│    pub streams: Vec<NetStreamInfo>,\n 816│}\n 817│\n 818│/// `net-stream-retire` payload (ADR-0038 A).\n## attach.rs\n 274│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*275│/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 276│/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n 277│/// is being attached, for the gate. `net_from_seq` is this brain's resume\n 278│/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n## brain.rs\n   42│    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,\n  *43│    NetStreamSubscribeReq, NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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     │...\n  151│    /// The broker's held-streams listing (D4b).\n *152│    NetStreams(NetStreamsReply),\n  153│    /// The broker's hosted-sessions listing (D9-1: session → endpoint).\n  154│    Sessions(SessionsReply),\n  155│    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).\n     │...\n  788│                KIND_NET_STREAMS_REPLY => {\n *789│                    let ev: NetStreamsReply = serde_json::from_value(env.payload)\n  790│                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n  791│                    return Ok(BrokerEvent::NetStreams(ev));\n  792│                }\n     │...\n 1435│    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n*1436│    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n 1437│        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n 1438│        loop {\n 1439│            match self.read_event()? {\n## broker.rs\n   58│    BrokerImageReply, StallEvictsReply, NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,\n  *59│    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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     │...\n 4369│            KIND_NET_STREAMS_REPLY,\n*4370│            serde_json::to_value(NetStreamsReply {\n*4371│                streams: host.stream_infos(),\n 4372│            })\n*4373│            .expect(\"NetStreamsReply serializes\"),\n 4374│        );\n 4375│        send_frame(send, &frame);\n 4376│        Ok(())\n## nethost.rs\n   50│    net_presence_event_envelope, net_stream_data_envelope, net_stream_eof_envelope,\n  *51│    NetPresenceEvent, NetStreamInfo, PRESENCE_CONNECTED, PRESENCE_DIAL_FAILED, PRESENCE_DISCONNECTED,\n   52│};\n   53│\n   54│/// The reserved [`crate::effect::EffectKey`] session namespace for net-scoped\n     │...\n 1804│    /// enumeration is exactly what a future dispatcher generation would replay.\n*1805│    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n 1806│        let streams = self.shared.streams.lock().unwrap();\n 1807│        streams\n 1808│            .iter()\n     │...\n 1811│                let log = e.log.lock().unwrap();\n*1812│                NetStreamInfo {\n 1813│                    stream_id: *id,\n 1814│                    conn_id: e.conn_id,\n 1815│                    remote_id_hex: e.remote_id_hex.clone(),\n     │...\n 1828│    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n*1829│    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n 1830│    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n 1831│    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n 1832│    /// reply sends when it removed the entry (see the `retired` field note).\n     │...\n 2164│        let op_stream = host.open_stream(c1).expect(\"open loopback stream\");\n*2165│        let infos = host.stream_infos();\n 2166│        assert_eq!(infos.len(), 2, \"operator + peer rows\");\n 2167│        let op = infos\n 2168│            .iter()\n     │...\n 2189│            if host\n*2190│                .stream_infos()\n 2191│                .iter()\n 2192│                .any(|s| s.stream_id == peer_id && s.next_seq > 0)\n 2193│            {\n     │...\n 2387│    // named row from dispatch eligibility: the retired stream leaves the\n*2388│    // dispatch enumeration (stream_infos) while the active sibling stays\n 2389│    // claimable, a second retire is a no-op, and the pinned-opener query\n 2390│    // answers no-such-stream afterwards. Retirement is visibility, not\n 2391│    // teardown — the transport half of the contract is the sibling test\n     │...\n 2414│        assert!(host.retire_stream(shell), \"first retire hides the row\");\n*2415│        let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();\n 2416│        assert!(!ids.contains(&shell), \"retired row left the enumeration\");\n 2417│        assert!(ids.contains(&owner), \"the active sibling row stays\");\n 2418│        assert!(!host.retire_stream(shell), \"second retire is a no-op\");\n     │...\n 2484│        for _ in 0..400 {\n*2485│            if !b.stream_infos().is_empty() {\n 2486│                saw = true;\n 2487│                break;\n 2488│            }\n     │...\n 2496│        for _ in 0..600 {\n*2497│            if b.stream_infos().is_empty() {\n 2498│                swept = true;\n 2499│                break;\n 2500│            }\n## propagate.rs\n  99│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*100│/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 101│/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against the `roster`\n 102│/// **here**, fail-closed: a non-member peer gets the up-to-date shape and\n 103│/// learns nothing (not even whether a release is staged).\n## sync.rs\n 177│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*178│/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 179│/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against\n 180│/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull\n 181│/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient\n## wan.rs\n 227│/// the QUIC handshake-proven remote node id (the broker's\n*228│/// `NetStreamInfo::remote_id_hex` for the stream the record arrived on) —\n 229│/// never anything read out of record bytes (REQ-HAZARD-WAN-ORIGIN-AUTH).\n 230│/// `registry` is the node's [`RegistryHost`]: the user-msg identity gate\n 231│/// resolves the origin endpoint's advertised type from it, keyed by\n## xfer.rs\n 214│/// `origin_node` MUST be the handshake-proven remote id from the broker's\n*215│/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n 216│/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint being reached,\n 217│/// for the gate. `root` is the transfer root all wire paths resolve under;\n 218│/// `progress_dir` is where this end's progress records land"},"isError":false,"timestamp":1784271815514}}
{"type":"message","id":"7889989e","parentId":"f143cafc","timestamp":"2026-07-17T07:03:35.526Z","message":{"role":"toolResult","toolCallId":"call_04fIMGJxJ7wiu8jQTGGyH7m7|fc_089095e18118ee7d016a59d3c76cd08190a451a3b3ff7dc84c","toolName":"grep","content":[{"type":"text","text":"# crates/\n\n## spt-net/src/net/\n### endpoint.rs\n 48|/// stays open indefinitely; a real partition or peer death tears it down within\n*49|/// this bound (then the next dial re-proves membership). Bounded, not infinite:\n 50|/// a dead peer must eventually leave the broker conn table (the D4c presence seam).\n 51|const MESH_MAX_IDLE: Duration = Duration::from_secs(60);\n 52|\n*53|/// The spt QUIC transport config: keep-alive on, bounded idle timeout (see\n 54|/// [`MESH_KEEP_ALIVE`] / [`MESH_MAX_IDLE`]). Factored out so the keep-warm\n 55|/// invariant (`keep_alive < max_idle`) is assertable without reading back iroh's\n 56|/// opaque config type.\n...\n 165|/// WAN reachability probe targets — Cloudflare's public anycast resolver on each\n*166|/// family (no DNS step; a literal address per family). A bounded TCP connect to\n 167|/// `:443` is the same discriminator the field incident used by hand\n 168|/// (`Test-NetConnection dns.iroh.link -Port 443`): it catches a half-broken family\n 169|/// whose route exists but whose path is dead, which a mere route-table check\n...\n 235|\n*236|/// One family's WAN reachability: a bounded blocking TCP connect (tokio's `net`\n 237|/// feature is off in this crate, so `std::net` on a blocking thread), `true` iff\n 238|/// the handshake completes within [`FAMILY_PROBE_TIMEOUT`].\n 239|async fn probe_family(target: &str) -> bool {\n### registry.rs\n 204|    /// The endpoint's **recent projects**, newest-first, project IDs ONLY\n*205|    /// (owlery-internal excluded, bounded) — gossiped so a remote picker/list renders\n 206|    /// real project history instead of the hardcoded empty `from_resource_row` used to\n 207|    /// fake (#4). Project IDs only: dirs leak usernames/dir structure across nodes, so\n 208|    /// they never cross the wire (the local Choose-project step keeps its own dirs).\n...\n 211|    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n*212|    pub recent_projects: Vec<String>,\n 213|    /// Whether ANY operator (local OR remote) currently drives this instance — the\n 214|    /// broker-owned any-controller marker ([`spt_store::info::Info::controlled`]).\n 215|    /// Gossiped **explicitly**, NOT derived from [`controller_node`](Self::controller_node):\n...\n 349|    /// it and — since the author node stays alive and whole-node eviction never\n*350|    /// fires — the Offline row is otherwise immortal and accumulates unbounded\n 351|    /// under purge/erase churn.\n 352|    ///\n 353|    /// **Self-pruning.** The receiver-observed [`offline_since`](Self::offline_since)\n...\n 532|    pub adapter: Option<String>,\n*533|    /// The advertised recent-project IDs newest-first ([`Instance::recent_projects`]) —\n 534|    /// threaded so a remote picker/list renders real project history, not a faked empty\n 535|    /// (#4). Empty on a pre-field remote row.\n 536|    // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n*537|    pub recent_projects: Vec<String>,\n 538|    /// The advertised any-controller marker ([`Instance::controlled`]) — threaded so a\n 539|    /// remote row reads controlled-ness from the raw truth, not the WHO datum (#4;\n 540|    /// undercounts the local-controller case otherwise).\n...\n 610|                adapter: instance.adapter.clone(),\n*611|                recent_projects: instance.recent_projects.clone(),\n 612|                controlled: instance.controlled,\n 613|            });\n 614|        }\n...\n 1144|            adapter: None,\n*1145|            recent_projects: Vec::new(),\n 1146|            controlled: false,\n 1147|        }\n 1148|    }\n...\n 1188|            adapter: None,\n*1189|            recent_projects: Vec::new(),\n 1190|            controlled: false,\n 1191|        };\n 1192|        // Full round-trip preserves every new field.\n...\n 1223|    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: the de-faking gossip fields\n*1224|    // (adapter / recent_projects / controlled) round-trip on the wire AND an N-1 row\n 1225|    // omitting all three parses clean to the conservative defaults (None / empty /\n 1226|    // false), so `from_resource_row` degrades to \"-\" on a mixed-version subnet instead\n 1227|    // of failing — the same ADR-0001 additive discipline as the W5 parity fields.\n...\n 1231|            adapter: Some(\"claude-spt:doyle\".to_string()),\n*1232|            recent_projects: vec![\"spt-core\".to_string(), \"owl\".to_string()],\n 1233|            controlled: true,\n 1234|            ..inst(\"n1\", Status::Active, 7)\n 1235|        };\n...\n 1238|        assert_eq!(back.adapter.as_deref(), Some(\"claude-spt:doyle\"));\n*1239|        assert_eq!(back.recent_projects, vec![\"spt-core\", \"owl\"]);\n 1240|        assert!(back.controlled);\n 1241|\n 1242|        // N-1 row (pre-#4 shape) → clean defaults.\n...\n 1245|        assert_eq!(old.adapter, None, \"N-1 → no adapter (renders '-')\");\n*1246|        assert!(old.recent_projects.is_empty(), \"N-1 → no projects\");\n 1247|        assert!(!old.controlled, \"N-1 → not controlled\");\n 1248|\n 1249|        // Common case skip-serializes — a forward node's output stays N-1-readable.\n...\n 1252|        assert!(!pjson.contains(\"adapter\"), \"None adapter skip-serializes\");\n*1253|        assert!(!pjson.contains(\"recent_projects\"), \"empty projects skip-serializes\");\n 1254|        assert!(!pjson.contains(\"controlled\"), \"false controlled skip-serializes\");\n 1255|    }\n 1256|\n...\n 1421|    // an age-out, and a whole-node eviction all drop their stamps), so it can never\n*1422|    // grow unbounded — the very bug this fix closes, one level down.\n 1423|    // [unit->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 1424|    #[test]\n 1425|    fn offline_since_side_map_is_self_pruning() {\n...\n 1458|    #[test]\n*1459|    fn purge_churn_offline_ghosts_stay_bounded() {\n 1460|        let own = \"here\";\n 1461|        // A live remote endpoint + three purged (Offline) ghosts on a STILL-ALIVE\n 1462|        // author node `farm` — exactly what purge/erase + ghost-heal leaves behind.\n...\n 1484|        let evicted = reg.evict_aged_offline(1_000 + GRACE + 1, GRACE, own);\n*1485|        assert_eq!(evicted, 3, \"all three aged Offline ghosts evicted (bounded snapshot)\");\n 1486|        assert!(\n 1487|            reg.instances(\"keep\").iter().any(|i| i.node == \"farm\" && i.status == Status::Active),\n 1488|            \"the live routable row is untouched\"\n...\n 1557|            adapter: None,\n*1558|            recent_projects: Vec::new(),\n 1559|            controlled: false,\n 1560|        };\n 1561|        reg.merge_instance(\"ling\", with(Some(\"rust builds\"), 1));\n...\n 1611|            adapter: None,\n*1612|            recent_projects: Vec::new(),\n 1613|            controlled: false,\n 1614|        };\n 1615|        reg.merge_instance(\"ling\", with(Some(\"OLDHOST\"), 1));\n...\n 1789|            adapter: None,\n*1790|            recent_projects: Vec::new(),\n 1791|            controlled: false,\n 1792|        };\n 1793|        reg.merge_instance(\"ling\", with(Some(1_000), 1));\n...\n 1838|                adapter: None,\n*1839|                recent_projects: Vec::new(),\n 1840|                controlled: false,\n 1841|            },\n 1842|        );\n### replicate.rs\n 170|                adapter: None,\n*171|                recent_projects: Vec::new(),\n 172|                controlled: false,\n 173|            },\n 174|        }\n### wanmsg.rs\n 149|                adapter: None,\n*150|                recent_projects: Vec::new(),\n 151|                controlled: false,\n 152|            },\n 153|        }\n\n### mesh/\n#### seedproof.rs\n 547|    // round-trips for the empty list, a multi-name list, and a unicode name; and\n*548|    // every malformed shape (truncated count, short name, name overrun, non-UTF-8,\n 549|    // trailing bytes) decodes to None — the connect-time negotiation can't be\n 550|    // tricked into a partial read.\n 551|    #[test]\n...\n 570|\n*571|        // Truncated: tag + short-of-(nonce+count).\n 572|        assert_eq!(\n 573|            SeedProofFrame::decode(&[FRAME_HELLO, 0, 0, 0]),\n 574|            None,\n...\n 625|        short.extend_from_slice(&1u32.to_be_bytes());\n*626|        assert_eq!(SeedProofFrame::decode(&short), None, \"truncated proof body\");\n 627|        // One full proof + one trailing byte.\n 628|        let mut trailing = SeedProofFrame::ProofSet {\n 629|            proofs: vec![(1u64, [9u8; PROOF_TAG_LEN])],\n\n### pairing/\n#### meet.rs\n 237|/// connect timeout. Found live in the first real two-machine join (M7\n*238|/// acceptance): the sequential, unbounded walk burned the live window\n 239|/// dialing the dead one's ghost, every attempt.\n 240|const MEET_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(7);\n 241|\n...\n 243|/// `(subnet_name, totp_step)` and probe ALL THREE **concurrently**, each\n*244|/// bounded by [`MEET_PROBE_TIMEOUT`] — first answering listener wins (the\n 245|/// losers are dropped; racing pre-trust meet ids is harmless, module docs).\n 246|/// Each derived id is dialed by **id alone** (relay + n0 DNS / mDNS resolve\n 247|/// it — no address ticket); the winner's reply is the responder's real\n#### wire.rs\n 68|/// not a DoS lever) while leaving generous room for a real subnet's roster; each *field*\n*69|/// stays individually bounded by this same cap, and counts are never pre-allocated from the\n*70|/// wire (a huge count hits \"frame truncated\" immediately, not a huge allocation).\n 71|const MAX_FRAME: usize = 65536;\n 72|\n 73|// Frame tag bytes.\n...\n 661|/// hostile count exhausts the buffer on the first short field and errors\n*662|/// \"truncated\", not a huge allocation. Every field is bounds-checked by [`Cursor`].\n 663|fn dec_roster(c: &mut Cursor) -> Result<(Vec<RosterEntry>, Vec<Tombstone>), PairWireError> {\n 664|    let member_count = u32::from_be_bytes(c.take(4)?.try_into().expect(\"took 4\"));\n 665|    let mut roster = Vec::new();\n...\n 854|/// A minimal big-endian field reader over a frame body — every accessor bounds-checks so\n*855|/// a truncated or oversized field is a [`Protocol`](PairWireError::Protocol) error, never\n 856|/// a panic (the input is pre-trust attacker-reachable).\n 857|struct Cursor<'a> {\n 858|    buf: &'a [u8],\n...\n 871|            .filter(|e| *e <= self.buf.len())\n*872|            .ok_or_else(|| PairWireError::Protocol(\"frame truncated\".into()))?;\n 873|        let slice = &self.buf[self.pos..end];\n 874|        self.pos = end;\n 875|        Ok(slice)\n...\n 886|\n*887|    /// A u32-BE length-prefixed field, bounded by [`MAX_FRAME`].\n 888|    fn field(&mut self) -> Result<&'a [u8], PairWireError> {\n 889|        let len = u32::from_be_bytes(self.take(4)?.try_into().expect(\"took 4\")) as usize;\n 890|        if len > MAX_FRAME {\n...\n 1457|    // [unit->REQ-PAIR-1] the frame codec round-trips every variant and rejects a\n*1458|    // truncated body without panicking (the codec is pre-trust attacker-reachable).\n 1459|    #[test]\n 1460|    fn frame_codec_round_trips_and_rejects_garbage() {\n 1461|        let cases = [\n...\n 1483|        }\n*1484|        // A Hello claiming an 8-byte step but missing it is truncated, not a panic.\n 1485|        let mut bad = vec![T_HELLO];\n 1486|        push_field(&mut bad, b\"home\");\n 1487|        bad.extend_from_slice(&[0u8; 3]); // only 3 of the 8 step bytes\n...\n 1576|\n*1577|        // Malformed trailing: claims one member but no entry follows ⇒ truncated.\n 1578|        let mut bad = vec![T_SEED];\n 1579|        bad.extend_from_slice(&[4u8; TOTP_SEED_LEN]);\n 1580|        bad.extend_from_slice(&1u64.to_be_bytes());\n\n## spt-daemon/src/\n### registryhost.rs\n 388|        // agent refines — CONTEXT §resource advertisement).\n*389|        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\n 390|        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\n 391|        // §Discovery scope): registered kind=\"shell\" adapters that chose\n 392|        // broadcast=subnet ride every row this node authors — the other-node\n...\n 484|            //    used to show the resources blurb here).\n*485|            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\n 486|            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\n 487|            //    a local controller has driven_by=None so that WHO datum undercounts it).\n 488|            // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n...\n 490|            let controlled = info.as_ref().is_some_and(|i| i.controlled);\n*491|            let recent_projects = recent_projects_for(&perch_path, owlery);\n 492|            let last_active_ms = info.and_then(|i| i.last_active_ms);\n 493|            for sub in &subnets.subnets {\n 494|                let Ok(ep) = epoch.next_epoch() else { continue };\n...\n 524|                    adapter: adapter.clone(),\n*525|                    recent_projects: recent_projects.clone(),\n 526|                    controlled,\n 527|                };\n 528|                let reg = regs.entry(sub.name.clone()).or_default();\n...\n 594|                    adapter: None,\n*595|                    recent_projects: Vec::new(),\n 596|                    controlled: false,\n 597|                };\n 598|                reg.merge_instance(&id, instance.clone());\n...\n 687|    /// re-advertises Offline once, whole-node eviction never fires) stops\n*688|    /// accumulating unbounded on a remote viewer under churn. Own rows never decay.\n 689|    /// Snapshots are rewritten iff a row was evicted. The per-row receiver-observed\n 690|    /// clock is the model's self-pruning [`SubnetRegistry::evict_aged_offline`]\n 691|    /// side-map (re-derived each call — no leak, no explicit whole-node purge).\n...\n 888|/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\n*889|/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\n 890|/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\n 891|/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\n 892|// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n*893|fn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\n 894|    let entries = spt_store::sessions::read_all(perch_path);\n 895|    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\n 896|    let mut out: Vec<String> = Vec::new();\n...\n 1098|\n*1099|    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: recent_projects_for derives the gossiped\n 1100|    // project IDs from the session ledger newest→oldest, excludes owlery-internal cwds\n 1101|    // (a psyche-host session is not a project), dedups (a project's newest occurrence\n 1102|    // wins its slot), and bounds the count. Project IDs only — no dirs on the wire.\n 1103|    #[test]\n*1104|    fn recent_projects_for_dedups_newest_first_excludes_owlery() {\n 1105|        let tmp = tempfile::tempdir().unwrap();\n 1106|        let root = tmp.path();\n 1107|        let owlery = root.join(\"owlery\");\n...\n 1134|\n*1135|        let got = recent_projects_for(&perch, &owlery);\n 1136|        // newest-first, owlery excluded, alpha deduped to its newest slot.\n 1137|        assert_eq!(got, vec![\"gamma\", \"alpha\", \"beta\"], \"got {got:?}\");\n 1138|    }\n...\n 1183|                adapter: None,\n*1184|                recent_projects: Vec::new(),\n 1185|                controlled: false,\n 1186|            },\n 1187|        }\n...\n 1271|            adapter: None,\n*1272|            recent_projects: Vec::new(),\n 1273|            controlled: false,\n 1274|        }\n 1275|    }\n...\n 1474|            let cfg = crate::config::DaemonConfig {\n*1475|                resources_blurb: Some(\"general node services\".to_string()),\n 1476|                ..crate::config::DaemonConfig::default()\n 1477|            };\n 1478|            cfg.save_to(&crate::config::DaemonConfig::default_path())\n...\n 1588|                    adapter: None,\n*1589|                    recent_projects: Vec::new(),\n 1590|                    controlled: false,\n 1591|                },\n 1592|            }\n\n## spt-store/src/\n### info.rs\n 12|//! A reader may hit the file mid-write. Two layers guarantee it never observes a\n*13|//! truncated record:\n 14|//! 1. **Writers go through [`write_info`]** → [`crate::atomic::atomic_write_string_durable`]\n 15|//!    (tmp + rename): a same-volume rename is atomic, so a concurrent reader\n 16|//!    sees the *complete* old file or the *complete* new file, never a torn one.\n 17|//! 2. **Readers tolerate failure**: [`read_info`] / [`read_pid`] parse via a\n*18|//!    bounded retry and return `None` on persistent failure — never panic.\n 19|\n 20|use serde::{Deserialize, Serialize};\n 21|use std::path::Path;\n...\n 37|/// budget (`note_turn_outcome`, F-030) stamps this on a Self perch when a\n*38|/// bounded Psyche turn fails, so the failure is **harness-reachable** — a\n 39|/// harness (and a human via `spt endpoint list` / `spt whoami`) reads PERCH\n 40|/// STATE, never the brain's invisible stderr. It is a CURRENT-STATE stamp\n 41|/// (overwritten each retry, `attempts` incremented; cleared to `None` on a\n...\n 203|    /// Whether this session's PTY input-writer thread is currently DROPPING input\n*204|    /// because its bounded FIFO saturated — a genuinely wedged harness that has\n 205|    /// stopped draining its stdin (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\n 206|    /// The BROKER is the single writer (it owns the per-session input-writer):\n 207|    /// `Some(true)` is stamped on a full-queue DROP, cleared (`None`) on the next\n...\n 499|        .write(true)\n*500|        .truncate(false)\n 501|        .open(perch_path.join(\".info.lock\"))?;\n 502|    lock.lock_exclusive()?; // RAII: released when the handle drops (incl. `?`) or on exit\n 503|    Ok(lock)\n...\n 1152|\n*1153|    // [unit->REQ-HAZARD-INFO-JSON-TORN-READ] corrupt / truncated content is\n 1154|    // tolerated: None, never a panic.\n 1155|    #[test]\n 1156|    fn corrupt_content_returns_none() {"}],"details":{"scopePath":"crates/spt-net/src, crates/spt-daemon/src/registryhost.rs, crates/spt-store/src/info.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":61,"fileCount":9,"files":["crates/spt-net/src/net/endpoint.rs","crates/spt-net/src/net/mesh/seedproof.rs","crates/spt-net/src/net/pairing/meet.rs","crates/spt-net/src/net/pairing/wire.rs","crates/spt-net/src/net/registry.rs","crates/spt-net/src/net/replicate.rs","crates/spt-net/src/net/wanmsg.rs","crates/spt-daemon/src/registryhost.rs","crates/spt-store/src/info.rs"],"fileMatches":[{"path":"crates/spt-net/src/net/endpoint.rs","count":4},{"path":"crates/spt-net/src/net/mesh/seedproof.rs","count":3},{"path":"crates/spt-net/src/net/pairing/meet.rs","count":2},{"path":"crates/spt-net/src/net/pairing/wire.rs","count":9},{"path":"crates/spt-net/src/net/registry.rs","count":20},{"path":"crates/spt-net/src/net/replicate.rs","count":1},{"path":"crates/spt-net/src/net/wanmsg.rs","count":1},{"path":"crates/spt-daemon/src/registryhost.rs","count":15},{"path":"crates/spt-store/src/info.rs","count":6}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/\n\n## spt-net/src/net/\n### endpoint.rs\n  48│/// stays open indefinitely; a real partition or peer death tears it down within\n *49│/// this bound (then the next dial re-proves membership). Bounded, not infinite:\n  50│/// a dead peer must eventually leave the broker conn table (the D4c presence seam).\n  51│const MESH_MAX_IDLE: Duration = Duration::from_secs(60);\n  52│\n *53│/// The spt QUIC transport config: keep-alive on, bounded idle timeout (see\n  54│/// [`MESH_KEEP_ALIVE`] / [`MESH_MAX_IDLE`]). Factored out so the keep-warm\n  55│/// invariant (`keep_alive < max_idle`) is assertable without reading back iroh's\n  56│/// opaque config type.\n    │...\n 165│/// WAN reachability probe targets — Cloudflare's public anycast resolver on each\n*166│/// family (no DNS step; a literal address per family). A bounded TCP connect to\n 167│/// `:443` is the same discriminator the field incident used by hand\n 168│/// (`Test-NetConnection dns.iroh.link -Port 443`): it catches a half-broken family\n 169│/// whose route exists but whose path is dead, which a mere route-table check\n    │...\n 235│\n*236│/// One family's WAN reachability: a bounded blocking TCP connect (tokio's `net`\n 237│/// feature is off in this crate, so `std::net` on a blocking thread), `true` iff\n 238│/// the handshake completes within [`FAMILY_PROBE_TIMEOUT`].\n 239│async fn probe_family(target: &str) -> bool {\n### registry.rs\n  204│    /// The endpoint's **recent projects**, newest-first, project IDs ONLY\n *205│    /// (owlery-internal excluded, bounded) — gossiped so a remote picker/list renders\n  206│    /// real project history instead of the hardcoded empty `from_resource_row` used to\n  207│    /// fake (#4). Project IDs only: dirs leak usernames/dir structure across nodes, so\n  208│    /// they never cross the wire (the local Choose-project step keeps its own dirs).\n     │...\n  211│    #[serde(default, skip_serializing_if = \"Vec::is_empty\")]\n *212│    pub recent_projects: Vec<String>,\n  213│    /// Whether ANY operator (local OR remote) currently drives this instance — the\n  214│    /// broker-owned any-controller marker ([`spt_store::info::Info::controlled`]).\n  215│    /// Gossiped **explicitly**, NOT derived from [`controller_node`](Self::controller_node):\n     │...\n  349│    /// it and — since the author node stays alive and whole-node eviction never\n *350│    /// fires — the Offline row is otherwise immortal and accumulates unbounded\n  351│    /// under purge/erase churn.\n  352│    ///\n  353│    /// **Self-pruning.** The receiver-observed [`offline_since`](Self::offline_since)\n     │...\n  532│    pub adapter: Option<String>,\n *533│    /// The advertised recent-project IDs newest-first ([`Instance::recent_projects`]) —\n  534│    /// threaded so a remote picker/list renders real project history, not a faked empty\n  535│    /// (#4). Empty on a pre-field remote row.\n  536│    // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n *537│    pub recent_projects: Vec<String>,\n  538│    /// The advertised any-controller marker ([`Instance::controlled`]) — threaded so a\n  539│    /// remote row reads controlled-ness from the raw truth, not the WHO datum (#4;\n  540│    /// undercounts the local-controller case otherwise).\n     │...\n  610│                adapter: instance.adapter.clone(),\n *611│                recent_projects: instance.recent_projects.clone(),\n  612│                controlled: instance.controlled,\n  613│            });\n  614│        }\n     │...\n 1144│            adapter: None,\n*1145│            recent_projects: Vec::new(),\n 1146│            controlled: false,\n 1147│        }\n 1148│    }\n     │...\n 1188│            adapter: None,\n*1189│            recent_projects: Vec::new(),\n 1190│            controlled: false,\n 1191│        };\n 1192│        // Full round-trip preserves every new field.\n     │...\n 1223│    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: the de-faking gossip fields\n*1224│    // (adapter / recent_projects / controlled) round-trip on the wire AND an N-1 row\n 1225│    // omitting all three parses clean to the conservative defaults (None / empty /\n 1226│    // false), so `from_resource_row` degrades to \"-\" on a mixed-version subnet instead\n 1227│    // of failing — the same ADR-0001 additive discipline as the W5 parity fields.\n     │...\n 1231│            adapter: Some(\"claude-spt:doyle\".to_string()),\n*1232│            recent_projects: vec![\"spt-core\".to_string(), \"owl\".to_string()],\n 1233│            controlled: true,\n 1234│            ..inst(\"n1\", Status::Active, 7)\n 1235│        };\n     │...\n 1238│        assert_eq!(back.adapter.as_deref(), Some(\"claude-spt:doyle\"));\n*1239│        assert_eq!(back.recent_projects, vec![\"spt-core\", \"owl\"]);\n 1240│        assert!(back.controlled);\n 1241│\n 1242│        // N-1 row (pre-#4 shape) → clean defaults.\n     │...\n 1245│        assert_eq!(old.adapter, None, \"N-1 → no adapter (renders '-')\");\n*1246│        assert!(old.recent_projects.is_empty(), \"N-1 → no projects\");\n 1247│        assert!(!old.controlled, \"N-1 → not controlled\");\n 1248│\n 1249│        // Common case skip-serializes — a forward node's output stays N-1-readable.\n     │...\n 1252│        assert!(!pjson.contains(\"adapter\"), \"None adapter skip-serializes\");\n*1253│        assert!(!pjson.contains(\"recent_projects\"), \"empty projects skip-serializes\");\n 1254│        assert!(!pjson.contains(\"controlled\"), \"false controlled skip-serializes\");\n 1255│    }\n 1256│\n     │...\n 1421│    // an age-out, and a whole-node eviction all drop their stamps), so it can never\n*1422│    // grow unbounded — the very bug this fix closes, one level down.\n 1423│    // [unit->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 1424│    #[test]\n 1425│    fn offline_since_side_map_is_self_pruning() {\n     │...\n 1458│    #[test]\n*1459│    fn purge_churn_offline_ghosts_stay_bounded() {\n 1460│        let own = \"here\";\n 1461│        // A live remote endpoint + three purged (Offline) ghosts on a STILL-ALIVE\n 1462│        // author node `farm` — exactly what purge/erase + ghost-heal leaves behind.\n     │...\n 1484│        let evicted = reg.evict_aged_offline(1_000 + GRACE + 1, GRACE, own);\n*1485│        assert_eq!(evicted, 3, \"all three aged Offline ghosts evicted (bounded snapshot)\");\n 1486│        assert!(\n 1487│            reg.instances(\"keep\").iter().any(|i| i.node == \"farm\" && i.status == Status::Active),\n 1488│            \"the live routable row is untouched\"\n     │...\n 1557│            adapter: None,\n*1558│            recent_projects: Vec::new(),\n 1559│            controlled: false,\n 1560│        };\n 1561│        reg.merge_instance(\"ling\", with(Some(\"rust builds\"), 1));\n     │...\n 1611│            adapter: None,\n*1612│            recent_projects: Vec::new(),\n 1613│            controlled: false,\n 1614│        };\n 1615│        reg.merge_instance(\"ling\", with(Some(\"OLDHOST\"), 1));\n     │...\n 1789│            adapter: None,\n*1790│            recent_projects: Vec::new(),\n 1791│            controlled: false,\n 1792│        };\n 1793│        reg.merge_instance(\"ling\", with(Some(1_000), 1));\n     │...\n 1838│                adapter: None,\n*1839│                recent_projects: Vec::new(),\n 1840│                controlled: false,\n 1841│            },\n 1842│        );\n### replicate.rs\n 170│                adapter: None,\n*171│                recent_projects: Vec::new(),\n 172│                controlled: false,\n 173│            },\n 174│        }\n### wanmsg.rs\n 149│                adapter: None,\n*150│                recent_projects: Vec::new(),\n 151│                controlled: false,\n 152│            },\n 153│        }\n\n### mesh/\n#### seedproof.rs\n 547│    // round-trips for the empty list, a multi-name list, and a unicode name; and\n*548│    // every malformed shape (truncated count, short name, name overrun, non-UTF-8,\n 549│    // trailing bytes) decodes to None — the connect-time negotiation can't be\n 550│    // tricked into a partial read.\n 551│    #[test]\n    │...\n 570│\n*571│        // Truncated: tag + short-of-(nonce+count).\n 572│        assert_eq!(\n 573│            SeedProofFrame::decode(&[FRAME_HELLO, 0, 0, 0]),\n 574│            None,\n    │...\n 625│        short.extend_from_slice(&1u32.to_be_bytes());\n*626│        assert_eq!(SeedProofFrame::decode(&short), None, \"truncated proof body\");\n 627│        // One full proof + one trailing byte.\n 628│        let mut trailing = SeedProofFrame::ProofSet {\n 629│            proofs: vec![(1u64, [9u8; PROOF_TAG_LEN])],\n\n### pairing/\n#### meet.rs\n 237│/// connect timeout. Found live in the first real two-machine join (M7\n*238│/// acceptance): the sequential, unbounded walk burned the live window\n 239│/// dialing the dead one's ghost, every attempt.\n 240│const MEET_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(7);\n 241│\n    │...\n 243│/// `(subnet_name, totp_step)` and probe ALL THREE **concurrently**, each\n*244│/// bounded by [`MEET_PROBE_TIMEOUT`] — first answering listener wins (the\n 245│/// losers are dropped; racing pre-trust meet ids is harmless, module docs).\n 246│/// Each derived id is dialed by **id alone** (relay + n0 DNS / mDNS resolve\n 247│/// it — no address ticket); the winner's reply is the responder's real\n#### wire.rs\n   68│/// not a DoS lever) while leaving generous room for a real subnet's roster; each *field*\n  *69│/// stays individually bounded by this same cap, and counts are never pre-allocated from the\n  *70│/// wire (a huge count hits \"frame truncated\" immediately, not a huge allocation).\n   71│const MAX_FRAME: usize = 65536;\n   72│\n   73│// Frame tag bytes.\n     │...\n  661│/// hostile count exhausts the buffer on the first short field and errors\n *662│/// \"truncated\", not a huge allocation. Every field is bounds-checked by [`Cursor`].\n  663│fn dec_roster(c: &mut Cursor) -> Result<(Vec<RosterEntry>, Vec<Tombstone>), PairWireError> {\n  664│    let member_count = u32::from_be_bytes(c.take(4)?.try_into().expect(\"took 4\"));\n  665│    let mut roster = Vec::new();\n     │...\n  854│/// A minimal big-endian field reader over a frame body — every accessor bounds-checks so\n *855│/// a truncated or oversized field is a [`Protocol`](PairWireError::Protocol) error, never\n  856│/// a panic (the input is pre-trust attacker-reachable).\n  857│struct Cursor<'a> {\n  858│    buf: &'a [u8],\n     │...\n  871│            .filter(|e| *e <= self.buf.len())\n *872│            .ok_or_else(|| PairWireError::Protocol(\"frame truncated\".into()))?;\n  873│        let slice = &self.buf[self.pos..end];\n  874│        self.pos = end;\n  875│        Ok(slice)\n     │...\n  886│\n *887│    /// A u32-BE length-prefixed field, bounded by [`MAX_FRAME`].\n  888│    fn field(&mut self) -> Result<&'a [u8], PairWireError> {\n  889│        let len = u32::from_be_bytes(self.take(4)?.try_into().expect(\"took 4\")) as usize;\n  890│        if len > MAX_FRAME {\n     │...\n 1457│    // [unit->REQ-PAIR-1] the frame codec round-trips every variant and rejects a\n*1458│    // truncated body without panicking (the codec is pre-trust attacker-reachable).\n 1459│    #[test]\n 1460│    fn frame_codec_round_trips_and_rejects_garbage() {\n 1461│        let cases = [\n     │...\n 1483│        }\n*1484│        // A Hello claiming an 8-byte step but missing it is truncated, not a panic.\n 1485│        let mut bad = vec![T_HELLO];\n 1486│        push_field(&mut bad, b\"home\");\n 1487│        bad.extend_from_slice(&[0u8; 3]); // only 3 of the 8 step bytes\n     │...\n 1576│\n*1577│        // Malformed trailing: claims one member but no entry follows ⇒ truncated.\n 1578│        let mut bad = vec![T_SEED];\n 1579│        bad.extend_from_slice(&[4u8; TOTP_SEED_LEN]);\n 1580│        bad.extend_from_slice(&1u64.to_be_bytes());\n\n## spt-daemon/src/\n### registryhost.rs\n  388│        // agent refines — CONTEXT §resource advertisement).\n *389│        let node_blurb = crate::config::DaemonConfig::load().resources_blurb;\n  390│        // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT\n  391│        // §Discovery scope): registered kind=\"shell\" adapters that chose\n  392│        // broadcast=subnet ride every row this node authors — the other-node\n     │...\n  484│            //    used to show the resources blurb here).\n *485│            //  - recent_projects: sessions.log-derived, owlery-excluded, project IDs only.\n  486│            //  - controlled: the raw any-controller marker (NOT controller_node.is_some();\n  487│            //    a local controller has driven_by=None so that WHO datum undercounts it).\n  488│            // [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n     │...\n  490│            let controlled = info.as_ref().is_some_and(|i| i.controlled);\n *491│            let recent_projects = recent_projects_for(&perch_path, owlery);\n  492│            let last_active_ms = info.and_then(|i| i.last_active_ms);\n  493│            for sub in &subnets.subnets {\n  494│                let Ok(ep) = epoch.next_epoch() else { continue };\n     │...\n  524│                    adapter: adapter.clone(),\n *525│                    recent_projects: recent_projects.clone(),\n  526│                    controlled,\n  527│                };\n  528│                let reg = regs.entry(sub.name.clone()).or_default();\n     │...\n  594│                    adapter: None,\n *595│                    recent_projects: Vec::new(),\n  596│                    controlled: false,\n  597│                };\n  598│                reg.merge_instance(&id, instance.clone());\n     │...\n  687│    /// re-advertises Offline once, whole-node eviction never fires) stops\n *688│    /// accumulating unbounded on a remote viewer under churn. Own rows never decay.\n  689│    /// Snapshots are rewritten iff a row was evicted. The per-row receiver-observed\n  690│    /// clock is the model's self-pruning [`SubnetRegistry::evict_aged_offline`]\n  691│    /// side-map (re-derived each call — no leak, no explicit whole-node purge).\n     │...\n  888│/// owlery-internal cwds excluded (a psyche-host session is not a project — mirrors the\n *889│/// picker + `REQ-STORE-CONTEXT-BRANCH-FILL` exclusion), deduped newest-first, bounded.\n  890│/// Project IDs ONLY — dirs never cross the wire (they leak usernames/dir structure).\n  891│/// Degrade-to-empty on any IO error (best-effort gossip, never a crash).\n  892│// [impl->REQ-GOSSIP-ADAPTER-PROJECTS]\n *893│fn recent_projects_for(perch_path: &Path, owlery: &Path) -> Vec<String> {\n  894│    let entries = spt_store::sessions::read_all(perch_path);\n  895│    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();\n  896│    let mut out: Vec<String> = Vec::new();\n     │...\n 1098│\n*1099│    // [unit->REQ-GOSSIP-ADAPTER-PROJECTS] #4: recent_projects_for derives the gossiped\n 1100│    // project IDs from the session ledger newest→oldest, excludes owlery-internal cwds\n 1101│    // (a psyche-host session is not a project), dedups (a project's newest occurrence\n 1102│    // wins its slot), and bounds the count. Project IDs only — no dirs on the wire.\n 1103│    #[test]\n*1104│    fn recent_projects_for_dedups_newest_first_excludes_owlery() {\n 1105│        let tmp = tempfile::tempdir().unwrap();\n 1106│        let root = tmp.path();\n 1107│        let owlery = root.join(\"owlery\");\n     │...\n 1134│\n*1135│        let got = recent_projects_for(&perch, &owlery);\n 1136│        // newest-first, owlery excluded, alpha deduped to its newest slot.\n 1137│        assert_eq!(got, vec![\"gamma\", \"alpha\", \"beta\"], \"got {got:?}\");\n 1138│    }\n     │...\n 1183│                adapter: None,\n*1184│                recent_projects: Vec::new(),\n 1185│                controlled: false,\n 1186│            },\n 1187│        }\n     │...\n 1271│            adapter: None,\n*1272│            recent_projects: Vec::new(),\n 1273│            controlled: false,\n 1274│        }\n 1275│    }\n     │...\n 1474│            let cfg = crate::config::DaemonConfig {\n*1475│                resources_blurb: Some(\"general node services\".to_string()),\n 1476│                ..crate::config::DaemonConfig::default()\n 1477│            };\n 1478│            cfg.save_to(&crate::config::DaemonConfig::default_path())\n     │...\n 1588│                    adapter: None,\n*1589│                    recent_projects: Vec::new(),\n 1590│                    controlled: false,\n 1591│                },\n 1592│            }\n\n## spt-store/src/\n### info.rs\n   12│//! A reader may hit the file mid-write. Two layers guarantee it never observes a\n  *13│//! truncated record:\n   14│//! 1. **Writers go through [`write_info`]** → [`crate::atomic::atomic_write_string_durable`]\n   15│//!    (tmp + rename): a same-volume rename is atomic, so a concurrent reader\n   16│//!    sees the *complete* old file or the *complete* new file, never a torn one.\n   17│//! 2. **Readers tolerate failure**: [`read_info`] / [`read_pid`] parse via a\n  *18│//!    bounded retry and return `None` on persistent failure — never panic.\n   19│\n   20│use serde::{Deserialize, Serialize};\n   21│use std::path::Path;\n     │...\n   37│/// budget (`note_turn_outcome`, F-030) stamps this on a Self perch when a\n  *38│/// bounded Psyche turn fails, so the failure is **harness-reachable** — a\n   39│/// harness (and a human via `spt endpoint list` / `spt whoami`) reads PERCH\n   40│/// STATE, never the brain's invisible stderr. It is a CURRENT-STATE stamp\n   41│/// (overwritten each retry, `attempts` incremented; cleared to `None` on a\n     │...\n  203│    /// Whether this session's PTY input-writer thread is currently DROPPING input\n *204│    /// because its bounded FIFO saturated — a genuinely wedged harness that has\n  205│    /// stopped draining its stdin (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\n  206│    /// The BROKER is the single writer (it owns the per-session input-writer):\n  207│    /// `Some(true)` is stamped on a full-queue DROP, cleared (`None`) on the next\n     │...\n  499│        .write(true)\n *500│        .truncate(false)\n  501│        .open(perch_path.join(\".info.lock\"))?;\n  502│    lock.lock_exclusive()?; // RAII: released when the handle drops (incl. `?`) or on exit\n  503│    Ok(lock)\n     │...\n 1152│\n*1153│    // [unit->REQ-HAZARD-INFO-JSON-TORN-READ] corrupt / truncated content is\n 1154│    // tolerated: None, never a panic.\n 1155│    #[test]\n 1156│    fn corrupt_content_returns_none() {"},"isError":false,"timestamp":1784271815526}}
{"type":"compaction","id":"2a737e5d","parentId":"7889989e","timestamp":"2026-07-17T07:03:35.678Z","summary":"You are resuming a prior conversation. Its earlier turns were archived to reclaim context and are reproduced under HISTORY below, oldest to newest. Read HISTORY in full, then continue from the live conversation that follows it.\n\nThe archived transcript is compact: each turn opens with a heading — `# User ¶`, `# Assistant ¶`, or `# Tool call ¶` — assistant reasoning is wrapped in _italics_, and tool output sits inside `<out>…</out>`.\n\nReading HISTORY:\n- Plain-text sections are the verbatim transcript — rely on them exactly.\n- Some middle sections are attached as images instead of text. Each image is a page of that same transcript and belongs at its place in the reading order, between marked delimiters. Within an image, a solid black cell marks a newline and runs of spaces collapse to one.\n  - A frame is one grid 196 characters wide and up to 71 rows tall: read left to right, top to bottom — there is no word wrap, so a word may break across rows.\n  - Text inside `<out>` is dim gray — that gray is archived tool output, not conversation.\n- When an exact earlier detail matters and a section reads unclearly, re-derive it from the workspace (re-read files, re-run commands) rather than guessing.\n\nFILES\n===================\nCHANGELOG.md (Read)\n# .claude/reports/\n2026-07-16-redispatch-stall (Read)\n2026-07-16-redispatch-truth (Read)\n## 2026-07-16-redispatch-stall/\nrca-summary.md (Read)\n## 2026-07-16-redispatch-truth/\nhertz-refresh-rca.txt (Read)\n# .git/\nHEAD (Read)\npacked-refs (Read)\n## logs/\nHEAD (Read)\n### refs/remotes/origin/\n#### build/\nredispatch-stall-w1 (Read)\nredispatch-truth-w1 (Read)\n#### fix/\nredispatch-stall-w1-replay-order (Read)\n#### release/\nv0.36.0 (Read)\n## refs/\n### heads/\nmain (Read)\n### remotes/origin/build/\nredispatch-stall-w1 (Read)\n### tags/\nv0.36.0 (Read)\n# crates/spt-daemon/src/\nattach.rs (Read)\nbrain.rs (Read)\nbroker.rs (Read)\nconn.rs (Read)\ndispatch.rs (Read)\n[…11 files elided…]\n\nHISTORY\n===================","shortSummary":"Archived 141,799 chars of history onto 9 snapcompact frames (+27,833 chars as text)","firstKeptEntryId":"8da504cb","tokensBefore":243665,"details":{"readFiles":[".claude/reports/2026-07-16-redispatch-stall",".claude/reports/2026-07-16-redispatch-stall/rca-summary.md",".claude/reports/2026-07-16-redispatch-truth",".claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt",".git/HEAD",".git/logs/HEAD",".git/logs/refs/remotes/origin/build/redispatch-stall-w1",".git/logs/refs/remotes/origin/build/redispatch-truth-w1",".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order",".git/logs/refs/remotes/origin/release/v0.36.0",".git/packed-refs",".git/refs/heads/main",".git/refs/remotes/origin/build/redispatch-stall-w1",".git/refs/tags/v0.36.0","CHANGELOG.md","crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/conn.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/peerloop.rs","crates/spt-daemon/src/pump","crates/spt-daemon/src/pump.rs","crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/registry.rs","crates/spt-daemon/src/registryhost.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs","docs/REDISPATCH-STALL-TRIAGE.md","docs/adr/0038-broker-stream-lifecycle-truth.md"],"modifiedFiles":[]},"fromExtension":false,"preserveData":{"snapcompact":{"frames":[{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"iVBORw0KGgoAAAANSUhEUgAABiAAAAYaAgMAAAAgkEnVAAAACVBMVEX///8AAACAgIBJX6m2AAElwElEQVR42uxdS47juBKkBRVAaKUGqvaEV4JP4Qf0AWrRfR+hVj6Glsac8ikjMvnRz65Pd7tn5MF02RJFJhkSaStDEc79wdfPn+7BX18ZYVmXfbqjherObR8/cgdiqQUv/zT6AX97Ny/T56VXX/3StvyQoEE0QyV1+Sq22yxW2Liy3TCJeyWiybZ/rjyuGmb1phNmKIerclVfxjitNWyPv7dWUJezuhIQp6sL1TToavvM9/m76gYa04OqbEvAp58/gy87Wa20XM2AqKZx+7KIj1HmtXDQvHcL/awm++Tw8VRxPtwGotIa/GL3KzvtRkjmQIyfh08B4T4FhEtAhHRG3A/E+hXhVwOMQDi32k/vSiCObhrj1hWxDkSFuo7O6sqACJXU3QzOHYdmaC7yrho/y39s9DjIHu4PYfz3gst54LvGneLeo9WCd/n7ZjjZ1p5Hu/G4cf9YZgSiq2S7XPyO7fJdCHY0/620xuYSvEag0WlMTuK/YgvbO8lxFylndUnv2B/v2B/tL3oudRx1H9sM4TTIdAIgYoxHlGd9Teql478ckWbovXy+MuKAVvo4NaGuDIge15x0rPPBhy740yDNhngZdt7LJyf/hjCW6ogx38mFaEf78VjU4nlpp/fjv+P/nTcg5Azx4/5q/H+8WC8MTtpju3wXgtbEI8ftiESjlPrkeMat5/q45w1b2J4cNoY8lot1uQpxyYkgLXicq2zL9so+aVNLOAMixegRA+tDRIiQx9qIBN9j6xvHNbAOA4L/JSCqIeCKYMfH/8ZFg4G55kogJFbZ6jVcBtxc+U6a5dGnq9e/fJe/H+fYsY4QgWi0PmlJ5kepxbOTaNeA0KNxpGyXWqzlRo+XVmwQ5f0b4Ud7XuMXICwqlrL+eGwZy4Ym7sUVceVg5kDEGAGz1YeIXPB2bBqRN8TJeGSYpVUDgnXlawSAcIMBEZwBEdIVgRmwBIKldWkacPZjbpS/QWfJ7P0gdXRxyWJncyCCLr1Bzz10t+fRbIlDEdiyXAFDsCvCJSCCi1eEy4GwuqxUCcQIf++GHAhrMwcixgggrL6go5UDwfGcAhGyb02sKwfidNWpKejUpKF6zNyY6X0KLtg0ICV1akJzx4Hdlr9Bv3Sk97iYvc7HvRzNqU6BGIPsdc5iuxEInVwQX8eLfvzUyZoQcLysEbJNarWpCbGkabFTILzXBZODXACBodQ6uK+LQHgZPF/EyP2efcGoFUAoiJdLWJ2aWJd9ki8szhbrMRBdsk66JJ4GIu6dLW6yTOrCqO/GFQVL03hODZ5Ty8B3+fsGpXgeNjhavqtzph/XiJ6B6WI9tnvSxVmP5uI8bmcM49FYrHE8W7lIrSfE38u0yvZ8hcXaS5Soy4Y69ce72N+BdejUpF8IxvVrXKzH44sYPb4WaF/Qqq06aURkoZfPPcf1glZ6qUuiYV3Sd/lPfkuVvyMWXvxi9s/1nlLl1zh7b8eGWQm9LN/9Cj61EVbb3fgCvFjj2nHrEYZ3xl3NflmXi/ZdQ/xxILZr+ggQ/KqZA/GZ4Slr/JoI76vrHfeafv1rv9e0A7ED8au6uQPxIN3cgXiQbu5APEg3dyAepJs7EA/SzR2IB+nmDsR/FKaHi6565/bPdbX6RW29r4+V+zwQS0lazT6E2a6QvWvilkYPk9d4n6ZfqFFe/amfV6osjSLzXPImUhU/f/og6aWUul9tbbGtLJbJ57DS6vLBRauxb8K+qHIOSpgHkLfB982sznA/ELzFncpUGaJ+qde2sdf/M/SVHFAA4f3y+TLen+zHZIBLnfXroxXbuhcI729DsNDH2DdJ6PgsNmk9TMp7n2pgYraa1Rmyd+FjQFR3A+GmQIRp/ctACOMnuMSh+FIg3KeBKGJbBCL76/P+LwKR8xMG40rw/VHTHfK+inyPCiwF10uqhakf8ieE9YC0yEV4EwyNebQGfAYmfCxoSZqwnHex3cHikBK4Inpp2WJkHnDKLEltWT2JWWKxlDyUI5JW2hL+MtM4Y570LrZK1kfqm7AvgkuxVXEET4OxTzzYHQ22IBnF8lrnFaVHNMktSfwEsjGYZR0vJE+WBGkXna8iY6MCSyENjRyBbHFQDkOnvIie7Aa5QCVpKPyISlOWIP750MvWU0xvGkMjeAIxJimHLuNQMDM+ZZaktnhsYpakWEoeSqctsSan/VxgnvRpYpGj874J++I4pNgqrUcqYC+Yj/OkMwyx97HOt3EExr09pi2fcSgSA0LaImMi8RrIRAhIdMvApwqDJsvlKJYR3kQaHKn/DewJY2kwjOZ6ihwRSfifrsYoGbf2+r3E5zwPIwSUzBKjh2q+PcapJ0mY81C412oybsgC8yQB4QhA6pusEV0WW6X1+Iz04JXXwRq09xkQzdjaSfcVHApjQDS9t7OrM14DmQgBi6cMfOVKIPSK0M6HOG93CgT36NXnNLHec7bWdhOjJH1B9DnPQ6+IKbNksLZ4bHCJWWKxzHgosVUXuSFz5km21GZAhLhY5/wObX0BCDdoDb5crN843j335RwKJWdpmJ3MnDKnCqVqOjVdLpUrpyaZ7ZTDkE1NMscrEMrGqHTH5RKnDRfbNUaJF8aHAMH3feKmLTBLUltKC8qYJXkbLC0rm9HQ4lSKfs6ZJ+SdFEBkU1MZ29h6x3q9L4HgdOe196nOcUw6qZf7Mn6CMBy80gjBZQCTTb4By+Kd6JUnLHzkRXCxU/7ERTkMF2WESIRBQD0NvecesjS4A2wQXcas3SwOXBGkYaYYl5glqS0emzNLLJasdLZYsyawNvwS88S4Ghm3I/ZN2Bd5bGPrF9ZbaS8ceu5RG2uQ3qc6xzG52EnFLxHzr63xS17Y+JZXLXzVnXM+gvvYK6z8dg2LrSyXun9PzuTImSdl6bzVj/+ynkaw0RfyGoLbZjjc1/Xw0Xg3gfhoNFs9Du+q4y+4WfdVr/2m397Vvz66HYgdiD26HYgdiD26vas7EHt0e1f/nuj+hrPkq2N8Py1mteSMo9BvVbPF2vidQHyUz/HAQCQORMDHMU3g5zdaEvtghf+gzIacdREfMnVad7yZ6NJzmRDpCItVhtXOSBwnjaPParW+IGEk94T5TGo/RjWWP/WNPBV3zRVAkOZZuLcZ7oQ419RohjuIPVeyU1iWDZ/iiPbsjw1Rz2RLkTYv+QjlXn2HZInzBbPBpyFPQFSxvikQ8zu6y13z2egVQFhmAlAYEJJgkbyVbLWEToxRjp4NoC9Ol0qzcJP+KuknAuKDrxZP3xJQZad4A8Jn50EBRDV2owobQLgNIMhpSMyGRSBcAcTQvf+KuAlE33l7/h9JmmDPkoN9katrjAdWYRsIdx8QYRmIyuVKI5WxU1C2S0CYSkZFhYxBpqaA58KZAmIqJGc0mFoFmQ6yl0kjMhtAKkjMBt3bRN0McDec1SdAXC6JQQHWw4UcDzIcGpdYGMaxiJnBIOwRK5sUPyp3dEcAcTQg+gyIPlfXkPLj5t6mPCaWbCz4t3GnISXBJEnGeGVrrqmB7GXI2Sh8sLfpqcoh70A76CWfJ60ffQPyxBh5FzkQXpUqBAgZAqQN+UGSfREIqlWQ3eGpmuESs0G6l5gNfDicihWSqITGRQ/ligjEm+p4OPA9JPctQARlOKQUbdLnsDhCkFz5m5bVvTimc+ECSoNkxa5M756Qv5MYm4x9AT4SgJBhTYQJY7B4pS8kaYCO6VBNq+ZASL2WGUc03jLVb6pAUCHzKMCLColDlMjj2RCaoAf0OHrf81wEfyOolkV+RVyN3eFV6aKJa0RSmzDtC5R3ppsxnl0c3AwIMiikNoqUWGL9xKvKcX/S54ikhfDmySCRsqqQoVcElQiEFMAYTtcjUr82l6cYDQhcEU6+brg4Fl7ZF+R2JIoF+1QCoWIRoSGPRMcHXwaEseHIOsAogXyDljvwSZCp9dkaAa6F0JZwONL0cjmFOEcaHyEl/bnXDTkQBbOB5aNqhRt4RUwWax9MwgRyDInhUBm3wyV9jhwIvSJcMMUNXSOCN0UM5XbgmklAhOxLw+ka5/YhJI6IIxBBrwjEEYEwRY45EA46CzlPBBymoeltsc6A4PhMgLDLcgTChSiJ42yIMkZDp6StTgVIfGI2eFcyG0xYx0grvESPgxFrCAQnLh9lSy64ZOUcquKUkPQ5ciCOg5VVHZDsWxMVMSIQgd+apswQWbgNiDg1xb9kX3ifqZZ4b4ocmcLGOOEoEHFqgkxRNjVlAkFe22RtEQh+964GsiqgYReUy3AxVsec0VBBzwJchiExGwwIYzZYLdT7SDoaZIHIRNAb3fFCjhzyx6FR2gy/CMjWpM9hOebmIuwRK8u9+e+IyEXCtDj+i98RJfsCccTvj7ZYx7EYyL6ossVadD6oyFFBYcM0NTw1RC62WHvltcheqHLgnfyOYKsEQmoJ92jBGSlrjdGQ/xa6/9fl+i+EbebIck3vec1j/MzTFdUdmhpVFEiqPnP/gfhu7f8oEPfU+Lma7gPic6/7b3FYv37DjaD9pt+DjNIOxIOM0g7Eg4zSDsSDjNIOxIOM0g7Eg4zSDsSDjNIOxIOM0g7Ev2WUVgP+FcH/jgH5i6RPdiAWXqfiISJ54kvYBnLnUv61e49I4gz/jPdNcadzodypF/3ggBqFPSGP3G6V5T3Pac3wWxnvUZ6cea/ce3suFL2Y7PNpkJJBw43X1e4TIWWa7Si3N0VrJ1dmK8b72+rNklL/rKtZ64Ap3qeETtDHcy3zq6mVsFIuiKI23kFZO9woG+stakZ2I/DJZHnfuG2ORBKJCEUvJvuiw4dlJrbqtJrjHWhfmo1Mt4fcTqTPgWgk0RZyIBKXZXaKLQKBJ46DPtoXvGrUMw/nV8p5S/DEgd0uu1hzHKieUuZHt82RcDMgZkOTAVFtAFFNZFyqpPY8Mcspt0+ByDXxx+gjeySJgWQCM03iWAxB3UdUoj0O2jED4uiPq0Ac3wHEcQbEtGYbKD5zzXfLHAl1hAHv5AhOBDkWSPP6ch8fAD4NrNOt8i4aqwfMC8Q3ML1VgVliT97adlIDgk+KHPasuE1NPhh7RK4VxsSHlpGX55RAHYoTUoydMsgwn48BjVsvAZkvTCiXcDlBDuTUG09tVi4CcSLF5EZZnAKzmmUOR+hDFdeIZY6EN5cGbLTe8Io4DeW+09X2mU79cp02KmReNKoRchrSXp7GcXuw1kyRg3EnIEIovFk65q2tRihgIDj5F5wM84AIwoUA6YeZawWi46fsipiXg3IHj4B2x42yzi/VnK4Is4Va40hAOUOcfDTZT4UOdbJY3Yc6+7U6m7iKORDl4rVs9Aj1lIrbAcTRnaIiRzVZrAmELRE4Aa9GXWByetDl05EpkYCAVoYtrgaENpwDMSu3PjUtlnV+qea0RuRALHEkPHxRKvBOmNYXhQ7tRb+2z6gOy3WO2zoDAhF2EyA6BaLLgZDWOlPk6EsglF/l8jEigYJA+Pw7i+/JK2g4EYVb35pWyoUFILbLLtScvjXla8QSR8JHsRaZEoTawF7EIxb2sc6wWqfIRdgVmT5FIERMAhNr3B6sNeqAeM+4LaPNoa5sicDURLafXvX2HR5L2BXzta8GI1Ot/46o8NtguZz8NiBBC/uG03W7bPk7wmpOvyP4/rTKkSCX5AQ+CflAyi2xxXppH+u8rtU5bmFZMC/sU8CiD2aJV1aLbb9QeYplRJFD41aOx2ng7wgDAj4rF1I5T8P0187sa1n+WtKM2C5XHnFvnVY6/RoNd3Ekpt/tt/fdrtN+gUzrsJrC4vZSpe9dvimP+nrfLY7E/ZizQJb33aqzrGdaq32etxYW+rDfa7qz/l/dhx2IO+vfgdiB2IHYgdiB2IFYq38HYgdiB2IHYgdif/3KAVm2rvmdEexAfBUQ5pdi902qAf/1dt8kuqDoDS7JrDXvjzSkt5ZxCbeOeddjXPZqZn4ppp+Mm3C9cjdCKhsWo2kosB4WuzALM6/7ZLf71BIi8Maz8yUQU/ZDmNhLqIp+SHINVan+0Lt1G4/1bX3axgBLOYhFLx//EVSqwi/FjEg0/Wq54+j24v0aEJrW7G8BYdm4vG751IDDondqe0oByOPudhomgYxYfQlEiDm1bKC+AIjkTnS6bp9hZQXNDSCaybuqKJmAqMCmiNyNPpV9PxDNBIhp3fLpaDm4e4AQNoH5pUyBMO0LpM8vzRClxHs6iEg6w3xYyP2QASangtwIJj7IdJBE4hW+K3JFmMYGtS6goqG6G426ueAzWgw99TaYPrH2zLdE9tsjl3xXRacX42VA+8MzY2aDVblUVlksoYm1HpUOIMOXvFbI2GBLRc/7WHfHujk1RSC0LqdSSpKZzHVMxrqliJkSGBBHtbUJqu5SqaqG5mkziw4XWRM+zgbgVHRMQJqtgdod9G+eqv+gnXn6ozgocOgkGfUuOk8tDwPCRXYJa2NknQ5KdIboOcymq2G8DGh/hEr1ATiPGyRSlrwPaBNorZ1PQCSvFfQ0sKWi532sGxobbpgA0ScgeEWov4uKcOCJ/cqZX4oNZae2NpyeyDIwLY0EBLUvfMZbsOuNrizCD6GGhZlzNM6ASINOrYuQAxGf/7ftDdxHIqnBWyyR1JK+bkBNw7QzjJcRolHG5IqIZdVYx5mnS34WV5nXChxlerZU9DzWbQy+EoiTm18R4JNczR8GDAfzS4lTU5bKN5ZB1NJIV4QJQiTegpnO0PBCvUQiH6IPlyUggr8FBI7zIb8iXMiviEugGsj4ThgZpp2ReBmJDVLla0Qsq/3tzdMlXREVXGHMawUnVM+W8p6rXVsEwvXTK2K6RuCK8CH6w2CY09RknAUUieyGNDWl5atKTiGJt6BXBLgNIVknkekgChlv3iQzGGSnWheUQqmMiJOAgANKCPBfMXaJ57TJd9yPsyu+o+KYdrPz3oae83j6ZlOWpRCRkcdsjajgCpNPs558lJD3vIp1m2QYgYjfmsIciKOcoMoa8U6piGpcRv5CT1ZF1XNhUt7CxaSfGujmkRdBGS3jLRgQ4DhcGi1hTAdR5hAXEZYjtfGoWhe0bFK6ozI68BkOKFj6L8YuYXuMjFOSKobEd/At6c0ZBu4omMeFVXEa0u+IVJbcDmVtDLwuzQFFiE/mtQKWBvkbl7zndIBB3b39joDrlv2O8NPfEeJYI6JkZI34KQuhctvk9+X9YeN76z/XdXWNJTpv2kaGR1hoKaSaC87EWt2pxPLv2mr2/p9rWIjtn9mX7rBa9+1f1qFs833KF8uvLSC+qo3l9sI7j/yYWki4q9THgfgPvvabfjsQvyGCHYgdiB2IHYgdiB2IHYgdiB2IHYjfB8Svpq08/hA+SIxbQCzdsfmMTuof6+SnXtWdZT4NRHIYodtHYcUxf022BfxLjQ3HZ+Z6TfyPN/Fx/zUv7mfHDXju2kOFI6Yipy2IP0OKq9cyUnupj8E7tEsRpo9hdX9+bMYE6af7wuKoSNInG7spVLPIwhSI5DBCt4+1ypbpAawuppE89e4b2TUwSVEtVJIdp5mA8lHynFOiQAxTIJJmd7on7CdxV5kph+7p14HIJBkKJohzy/eKy7fjSLr52K1F5ieSGwZEYE5X3D4yv5GPASGJFVFjUS2UO4CIIuaWdTNVjARElcVlJ7Wp51dZ9bfcUXIgqkUglntbrZ/LJRCLYzePbAKEyJa76DBCt4/MbyRjMEQ+hGP+6lh4j0yBqODGwU6DL3FJzIVjedwEiCMt7VUVw0rKpJniomZIo1oCuT6GV3aGsTwaHH8lmwM35Km+oSLsLrFCjioadFRWiUffr4ONQqWPQpuIPGtwcXwgfTLMYkSrrC9FhgebdQSUyQH7o1BFSf2xu7nfSMZgCMaHwGXGnGvyHjlhnj8N9j8f/67QJbPnIHPBUqzxuKEEIgAyU8WwkuPZFjIli8AH2q29NKlJyxYxPVrk+Ddv7hSO+gDeSmvmW+kS9P0hq0R8TsSDQvvLdDJJBkOqgcr8HJWfPzs/jZHHddPIXBoBes4gxScJPa/SOc65zG/EZQwG4y3oZRWYMjTvkaOLV4R2Fw5LjrQY0cOwjLKxP+yR+KObANHhbMqUNqQkgbC4xGrER82BXB8jZauda2KZN8/yvD5P6Eej0w0YXLQ1uJoSgNrauAgE+B7i+kLdjlTDmHCN6xrnFovRvGaqaHySInPZCNC5BgwcH11QDIjoieISgwEOK8pAIhBuSN4jokJxLIBQmRV9n1L7IfmyOFXnKIEAt6RQ2oBJk8mrEAi6vHiKq2T6GHl3eRVwOFk+cI3oUXZIQKQIXEalyIGIGiGD9ZY1iG1YCUQZoxuWgDD7nGDj6jE1heiCIkDkfiNYDVK3PPyhIhC59wg0Ni4KRHCoUbxpjBYT9GJV+6d0HLdOvjWFqIphJTmBRh+U3gZZkvBJH2MCBDxavPkQGW0HYkg0I4lfF9TzhWvEIhDkd3Sxt6ihcmas5qKqVBlj4oXMrgif5IlGUOlVaA4jAkTuNwI2gzIY6JxiZ6mEkXuPUEeDzAqyPAgEF3RVu7hATAS1xePAjDgpVdJ+R8jiXuliXalblgFhdEm4VYFYnvQxMIkMMWJ4tMjxPZxL1KYtoGWftAOM8EOKaMDKo0ZPQ69ARDO46PDiwPkgtZQ2VvxFVsbI48IkMpeNAD1n3K1bHPYV6597aMMbdO13+qT46VfGtV+teb3vaWO9bHhHDVX26zD8ylsc87DuDXP6eh+TY156rZN5yfe0sV42vKsGemltx/glQDzK6z9+028HYgfiazu5A7EDsQOxA7EDsQOxA7EDsQOxx/i3BzeVLLkv1u1SD2tx85Hmqk/s/buB0BtgS/oaIb5r8MBsuc0Ve1MdUoIsiDHtNKwzG1jeF201wyJ3JNM9OPVlm8XeBNjgVqONzBAdEurmS36S6shupuaRDsQ91gUnFqp6mL7y0sv0jk+DtTp1dJnd60w7QnYmVhMgfHGe5gwIKeE1WeJnzIYpx6Eq2vIr458JJvRlm8tA+MKH2y/0KwEhCR9m7C095P3KeRB97KcNk1NirhTOzYe4vKe8ZCTya4Cw5jaYDb8UCPceICrlsEQg3BYQLNNMgTBQQy7z0twHhGlsTHkKTcHR4N4qalYkngVi82bFZHVICQ/2ggKhzAbV5UDej/yJiqoALrUVtTEyPY9SuULKR60MpzwM1Qkx/oaxMHIGitRgkwOZIQkIH+yKABMDRy9xWNQSh0nWqC2SVD0ABPvqmNhKpfgQsSmfJCAwXkxfTnkKDrryiWlgYv4d9/eJZ0EgnDdhc314vHfIc3lPiklkNkhW0DN9Sf5EAiK25Rb0PArlCikftTJwKPYmTgYqIQvDVDuQywvG3DBmSFojQlwjOgzOaVBFjgmHhU4sBCtpiyRVD6w3vWl9lKXIV7GUbVojwHGhh8qcpyB/E9MgGb401wBjr5xnIUoZOtCanz7psV4pJsZsECULPmlvHKgcCGtrrudRKlcgHZ4BQQ6GTkTMH1+Z6pTjmcYUpoTUF6cmMEPSFUEgXBRxsAinHBZeEQQraYskVQ9zuiiVRBQux9qq0qhEvgQJu8JoKBOeQsHRyJ13hHdR8izeogWNASElDIiQMRtEyYLAgDRDILq4RnhbW6Z6HqVyxfSKiBwjlyRNNPkvihyyB0yJpk+rG5khCQgZyDkQCxyWPg1gpi0SVT3mQKRS6lozA8JhalIm0Yyn0OUcDQOCBDFwlJRnIbOgKGVMpqZQAmHMBpmajgPZFwaECOtILamtBT2PTLmiXCMwC2cSRha9yzyFjCmRZF2MGWK0Oi6wBCJkQCxwWHIgoraIqXo02XeqODWlUi5EoluT1+NVZYI0lJKnwIXNmAZemRJcMgueBaepy2k4pUVRWQvCWCiZDVDZUPYFP51AVAbZK7IaFvQ8MuUKpw5gbFO1OnxarBHdxVgYFm0Ve+ZcYoaY34n9jrBlN05Ncw5LSHN70haJqh7or07DykJJpaw27MnWCCDwPnZFeoXiW/r6a42OULqcLNZS6nks1vI+MsO0r1O/k/s4LFu/nadMjurukh/Wybj3uLXObTmgTIFYGvDwBTHfvsWx1Op/6F7T745tv+m3A7EDsQOxA/FnRmYH4kFGZgfiQUZmB+JBRmYH4kFGZgfiUyXfb8lyT8mtMh8F7P46f0Xrv7quHYjfD0S4AQTdxycp7jA/lg+kV26NweB8vMk2VIst5keTp8H6qz6shB59TFzOhmjyXtTjf3i12qefP+TfJy3xhHdPP1jy6bXVsvY6vOJPnRp9eg8Q7XRf3aYdVveB74KNUhqvAghQVoaSs7DkhyL5XhJu5gwG80ZRlxG3DARa0mQ6n0zT2EJYpDUwIcnjQsERqdKQtBxEwePnTxmGAwfYBhrvXloC0dYGhO2v6ykQB3w85IM7+ZQBcba2l4Cwug/ju4MrBEOaBSDEFwQqGG7bD0UYEPAQcVtXxHoJ7nPakua29MQPzs2ksBMQybmkyU4M7cWZZ6UM1g0gWte2bnpF1G4ZiOIsn3y6GwgXgZC/R3Md6elHcgJnoZyaVPN90w+F+gWVY1p92RtFpfwLBQzjXegkSBWLnkofVLMg74Pi8bLdXEySEUFK0ysXRdNDP3+Ow3YeJ5z25dWdf/58+SED+fT69CrDyb8RiLM7t+7bq2xt2yd5910GSz7LO/n74/Xbqxz/8jpOaeN7h/Ly6Rv2yraX158/n75/w/uD7n/Bp3HfDzlirB3HWt0CxBNNOyy5F3qzHyiBCKFkIyz5odga0fmUr516o7A2ZUBMfFFKIE7wK0E8nql6zZjDwCTAXSUHQtkQfVc8Nj9eA+NAjxNOO55zrV0R42eZCsa/mJ8FklYgG/e4Z5me6vZ8kHdj6ZdXfH52OOp/9XMtx7PW51qmF36qsVe2yRUxtof3UovMQLUcg/IO9fJYq/swvjuYP4nXZa5xXlPgcyC2/VDkCLpzZUBMLDlYW2d52sIXxbJlxmoAzwKuLeROcKoKwSubogQisiF8rsshw1sbEPGKiECMizSBOAhIUnLc9/KjdS8Kn5y1Lz/GLYKj41CPw/ZD4a25H0CMf/8nxxoQTt5LqacftZZqAYTU++TsWJbCKpPUMehHsgSEioVs+qHcD4QlzEtflByIcGHNoQ9KACiBCOQp9ZWb1HnpfK7LIRNOq0CkNSIB0dYJCFkfUFb+PWMIz47guAQErgiXA8FP8vd/ciyAeDrXuLrc+RlXDEsJyLwi3KsdyxgAhFGokrCNV3eQKjrlmFjVlh+KARF8YjBMvVFKIEpfFF2NTGAHVwTicZOpSaHx4NNN6hSuR9TlQEztmUDU9RwITAsGxNmhJP9rUQoLajk1ybwvlWVA4FOt14t8ElDl87gOnMcVYZytOIHxipA1Ih3LGADEEXyGSvkG8k65CMpvADsjlJyFJT8UAYKKGInBMPVGYW3GXCh9UbhPv71dxjWCGhkDDWROgzq9XMjNEHcVuZwmdV5yXQ7EVJ8dFsRX+x3BxXpcvLH9Be8MiHGZrrFY1y94N12szyM4svAesPxyMPlJ/p7ld0h9kMUan0cgx5Nf2j68yhadmsYt6VhbrF9e82/62Y+8Kb9h6TU9Yn2/eaOUZULcF+7Qsaju8kgJ2a+98Mlf1m29sM3dfo1XRG1l29Ua+MPyjspuB3+7K9tlQvHua25xpDpJ8/0MEE+vHwWCR24Dcedrv9f08dd+028HYgfiV9W1A7EDsQOxA7EDsQOxA/ELBm8H4t8IxP7aX//tV34H5FDsOSyUJv9gfY8krMq66/IWS73wbv390uvuGtpfV0PWqzm342m2TcZmkwNycEUyWwe4ztLs9aQ0+QeHfIuFVk+BqAsg8mR8Pet47Ra3L43f3TW0v6CGORCHGYqH2YkqVR+KEvX8ZF4Cwi0CkfYd5p2LnbwBRLs4CG02CO38ZPlYDe2X1fBpINx0FinG9ZsyEsb86SsZBoeCm0DWAtkNP3BxfXutlQGBrd+ftESe7Ih1Y4+kCJ9etdbxP2FWfGNy5vtT5D+4M/e+oITE8iMyJSRt+d4afuD9F9ZAfof2VLboJKSj9KRsjZcfErsB8aRHStbaymH7OG5ggyjjw5GXAEaCnMgtcqgZN4GsBXnnxmSgYPpck3/A/UwraomM9SAvchieNWmOQpqoJN9hLP3sIv9BE/0oQxbF/yJT4vCBGpi6/LoamERta/aU/7JnraZOmThlXtqAaElcw9i4mGKV5B2nprZFXpsNGqtg/NS+/DgU3ATZSx7C0w8CwQafdL9UyBI5M8FCfErsBblafqAe8HhazMZSQvgPrSbZbS+5F//T3O6T+0gNwqqwnn2+BmF3SEzGx6gz2qaMkuyvY40ZEGOLUgJHYDRffpRAsF0AIayClx8EolVOgnETZK9RTXIg3Cv3P51jieec9cArgmWlaQuzldbG/58t5LrVM6/lejLWJSWyK2L8bMP4nhqEVWF8ic/XwH7V5zb21FkG77UFJ8pqlxMxB0LGqlVCzYEtC3xnAvF0zoBQVkENIPTyNG4CWQs4Q59zIKxc66wEmXE2NcmcTA6D8XmkA7zEz+A4oPXn8T/lP5x1AqjxXuZZsiZqpba8twYd2vrzNcR+CRDo6UFnANYap6a6vCJ073Os45nlsN2R89q6CIQxEg6yCH9/QtcTN+GA91iEv591FbDFuo5LlJRwxWL9xMtS+BM1Fy10AAsX+BL8Xv1dWiT/4ey494AScqywJoQjNG79QA1nZWR8vobYL4yGfGSd7OVT/AJAtgYmO/BDdO93LtDSNstx+0tkg/Cod73m3IPEdShZD63bTpTfSqKXvOz2AzWUXy0/U8O011z6b9fQbmxP66j7mlfiOsxZD18BxMdruP36aA1L/I6P1vEVde2v/bW/9tf+2l/7a3/tr/21v/bX/nr01+fNb5aPSlsfnYLyIPHtQPyR+PI7ME9IkEAgQfcthOQX3k3eLz/Xk56R5lPa47ueph9y9D/XsN5S8dryVWlWeqmOKXw0c2imocl+n8fPOFN0W/EE3Td3ZQnbQ19PgEgp7gP+g0CCM0ULHya99uU/ttNnO+WodFwCwnQ0qORBFwfralVofeQ+HlOjjS07j6l7i18KLyl59NwId5QCCImuWQDCbwAxd2UJWZj+NhBuDsSZ5Q5LQIRFIELW07AGhOloUMlDn6imqHwEImstLI/4EhDVAhB5gfIkmgIxaS1IpMEfXYruLiDcO4B4yvgb/EwlioyLcJYn+JOiBdwlTMFDHqTtqY2h7izDlRobPffiUVt5TPgqx12jQwkdTfBMdFepkocjED3F9enfIpOLqW+4nk4rjaNmSOHKonLzjEzl0eO++XE+licQzdB7a1Ok3+WBevV/Qfx8lD9FV3q8NNF5hvohSfj+GPeKlD0/UdHkFL1q5BijDYCfgOtBlSgyLsJZBBKSooWc2Xjg3PtoxSGPvp8G8TwJ/k3VM/TBdD3jeUW8RY8TH20DxLaAa4QICyT9gUodVvjXe04YtNygZoho7UdXFrrBdNT4SOoG3Dc9ztxNaM5wAlx9akvUFoZKtUR06sRj/5k6Qunx4llrh4f0EQEdXtSNBVuCHOP0sf3MoYJOL+QXkL/B5ZlKFBkXAVdEUrQ44cloUeGgY4nHPE+PhWY879/UciPt9c6uiDdvHid8WN1EI1S3QN1WOAlX6rDCv163n64yoI26Q2SuLGIKcKW+R1Aho7RvfpyHu4nM+06tNd5UqoJAeO0XBTIQaV+5FF3p8WJ/ZVAbZyojHCNHdxgAEWZAoKw4vZBfQP4GXq+8IhIXgWtEUrTAFaE2HQFeIm7ovDntBD3rg/qZeJTIrgj1OJHtUyBU2UMn4SoqNbE2z1ZdI5MYNENyV5ao/EFHF5cBQZuSyXGe7ahvjAHh+gQEIu1Ym0Tq1XeMtZceL/ZXrgg3BB9darw6qQAIcaGZAzH2SjTacv6Gsg5a4fClK4LfmpKihU5N6qgyXtIQafB0Yek4NclWryof8r6cmsxlKCl9EIgAASKdhKPYj52lXup0dLxSzZDMlcXRDaYLsYV83/w4dVuhbwz2XC4RCMTX09VF46fOVIqu9HiROUcdWIJGQLC7eILKFeEiEDpdcp8XgGSJNv4G2QVQonCJi8DfEaWiRQMVDjiqDD3UNOh7IsuXmKFJ8NxboYT9jug99TkaaG2ojkZfmZLHVS7/HAhasFGToxp6XWKhvTFxZaEvCf1e0mJt++bHlYv1SS3bZJWSMqIK0sPVRT73VPy4noYUXenxIhoh8hfbGAEX6wvdWLgl96LBr5eBlmjV1NmiXfwKe88v6+C2X9OjqsnW9/xyLb1Ypu/mX2mTDdR66VttLcUX3j0OH3Cv+dVAuE8AseTFsuXvYvvuK7187NcA8VH3mncA8Z4aPgvEn3jtN/12IHYgdiB2IHYgdiB2IHYgdiB2IHYg/lIgHi3Er2jzzwim7ED8HUAY26C8JxUmf5fSs0zuqC9znlZ3Ex7EO15hpa20lW2KYnnGk0hH6L01qeek+3GXJ6/zuuS2cjjPm4TbCh6V5t5STWPuuKJHpX3lMfIU4o+nVz5nyy3ZXmMxVEWft4GwI8hvwP3/DIiSB+Fn/y5V6d8BBLJ8vsp5Eumd+r14N7nzmp8ki24rlHRIohfyL7wlRogO57TXLTquTAUg6jp9yoFoVXfcgMjEInwxuPcBkTo0ZV9Y0cSD+HogpE1JKHm3DkTlJvbnUyBmbivpwcIpEOkx/kU1jQUg8s+3gMBLeAVemRIkk9CQ/qS8BHM+YaqjGeJnnZDIb3AZ+6LkQZB/oSkax+QMeRb2CXmqoKXCvK0msh3IEnHWZi9AkBeR8zcqJcWAazFmw67C23B5Cdm35LZCiXZqi5hSR7oi5LnsJ6jlix7B3HElVyyhGoM+XoyHglUJ5HsCghoc8sy21IaLWzJPSNz1vIyZPux8dHdAwpMZYqdOKJVODuQ3JIaDW+BBIHcbOFXgvTfDAg8KDTPAZlRgHAjnrWRkO3TJ9GNscZA2uiX+Ro/VISC7F97gVJGX4No2d1uxB+TxAHpNXYKn1tYIqHJgr2uXHFdKxZKD7dMaHWqUki8jpC9nKpU8q3XC+N7bjC4sBF4RMfENfoIzrkJiSmhSHm4qym/oM35DxoMI+kmSoEgaakJduBz81LBedYpgXpdtjXtQGm5cmvflcWzTaeZ7yt8orgi6XvgZw6NfcluRM/3JzuYfVBE4tPatSSYnUdMYDzjPHVemiiWHpN8hoIg+zw8qEMSpqeaWA1kzCYjohjUYEEE/kaOQe6Qo20KvCM/0u/EbMh6EATHWDSDckNgf9glXWgFESPwG8iyGmIDHcWwzB6Lgb+RrhLMromR4VItuK6KaIRoj7tU0anIg4tn9zKErHVemiiUH2/dMpQ47PgfCapwCIYyHPk5NcfqQoYoD5o0ngWmHHD0MeMZvKHgQxuQAHjoteRenprEOMDCCTkMZB0Ja8tEcJ8SpKbaZgCj4G8W3JgAB75a8hH5rmrqtZFMTFDSgOVYCMYLw9F0nNVc6rpSKJSY49PTdpiZocjwXQDybq0sEQlgLYGbExboayKEAAwEcBV1AL8Fz6ZX3p0H5DdcciJwHQfcT9UBxZHV4Z4t1JbV6cCJQKudAyHvlWgyslYu1+rxcZVn2RrbM+Bv574gAH5gwLaG/I6ZuK1ysqS2CAXydAiG8FlcLSXXuuFIqlryovJjwXyDD5aRGKSm/I4wlY0yZmRBEcO99ff5353u5DdW72ww3497+ZV1qcbxPRePLtDZ+PRDv5zb8biDKz+9T0fhtWhv7vaYHee1A7EDsQOxA7EDsQOxA7EDsQOxA/Kke7kD86R7+6sZ+fWd+RQt/4CTbgfgzURcvuY8rt4Hr9uVd90USg6GNIujb9S+xJF7m2xZrnNbdIgLGUPSgnsSHl9zxmdY1vQv0VOydRv00a/1e7dq8X/Pxy+qVfOyYrxr/zc0uSl+VWNbN3VNyveJFu5tYf25wE7WE2/zTeo1TQw5kA2r1dsl7UFvcuYMJEzJWQxu3LaX428WoDwtlloDIR2wZiINRE+qJ3Yck+yRVKOL7U/DqxaaKv/W2cHRe/6JVd718RRzuuCKMmFT0IA5vPSndLp7Pa0BMoz7MWl8Dwt28IjIOTqp3TIxk5xM1OeinIg/8qq/Kd7fpnvJNnUKo4mHG9KxpTJjE+pUloULlymlov+mn6FuyWGP0MVEvE4m0RgzfXvMeSNTfEDddLiJzgu28Wg3j8SqvbnwK6I3UrDkflSX2xjcwNuivwt5+Uw7HizrUkLUhrX7LGBxnqcu9vMakEhwk0CP4SNhsCG5Cq34q46SBv+QrbLmnPKtsPzUL2lZ9RFqV+o/1W5aWCUnlQZxrcCPMt8St1PiMNp95nCqFMLdV13kPJP34jLhlnx0tg/ashgSsoYY6PufXWt1UXKw5H5Ul9sYzGz0znUrfFa3fmUON/CuJ2OeMwXGOdhDmsCLjjH5zjlbKoXqGPKnXx9MPo5y4TfcU9U9QFQ8AIZyF9gm8iFQ/WRLGldCSLZ1GrMantRrrtJ0cJF4RImBR9uDlhxrzWC10mxJlBTUooW2JpDyffnASk2MOztpu23xUltgbalCirbea7aarivlxGDUBHA9lcPyvNqcac1gBEPJZuAh5N8QzRBgJ9PqoC77CmnuKDVt7ttIMgr4lqX6yJLDt9ZCba9QGRFsMeFFjvFKeLfXe1ur9MjmVXATi2Y4mENK2Jf5rMCzJ1JBa9Bp8Zs35qCyxN9TM5MzWDYjWrQFhDA4DwogHBkRrC0oBhF7ALdxHCr7CintKnEjaQ85QaK35skuJK4GSrQ2Y1vi8XONzbWsHV4tnA6KeAPFs8dP5JMbTpqnDLHektVrblZrbZ2kDU5ObRD1hbzzz+LZN5jj6Xx0dahh/XTA4vn83Ey1zWFEgav02rRchZlV6g8D74/vhteQrLLunfFNHEah41KQX0qfEzL5yloTagWnJlzOdRuLy/325xm+v/Db1gkUWC+x3LtY5EC/wLxH+xQuizOPhYio+W9/w90l7yHZbHOv4heB7GfWcvUELOPNCYaTsnbmkUNVEYsR7ZXBgGoLzijmsvKAfslhnZ+zaF8s5b2GLm7C0b/k36r0MB5P02vJpWe/BnWyLhZ6WdX65B0r5yrFf2pO/23JPWTruFhD3MhxM5O69QKTj7gWi7GlZ52/xQNnvNf2ZqHcgHiTqHYgHiXoH4kGi3oF4kKh3IB4k6h2IB4l6B+JBot6BeJCof2uTv6ruP1HvR9u8+7gdiAcAIuNA/JjvJctixnu488bXNoNjKfN721G3dfcyT1buCtVb0dqg/VTJeq2JztU/qODx9Hqvt3A7BaK2TexFEVOWndWtBzdlWZT3JqedsWT4ITte320zOG4AMa2XR7buXuYJ3d3zdsksmHNNDjFap0+YlqwNsi2gQlArXm6NtVLP+lbnQJwTHsYPqVVtIvEVlpCtF4dsEYiF62mbwXEnEPOj7mOetLOj67VAY7Q3gRAewiav6b1AGJErZ1kgdfODPIeCZUG+QuvIVpBEB5kPZECYckXkL0QWxzaDo+RCiLH9t/hpqd5veFfWu8U8EZrai7IsfqBf+PT9SXkX6X9usStC4NAEmXP6NwJxlgflmWTKWSvka5iCh9aKyITk8POnMVQO2rNvr+rX8R1RySPf2XwrE0j9P+SFc5aF8RVaR7aCQxKVXIVa047PGX/B0py3GBwlF+KMRKN9WqpX05Dntr6PedLqZCR7/4d+4dMz49MegH2hW3SN4BXBSMlIIRCiy4HkpmPaNWettEonedbkqZVgunSsUxkq4KuMZZ4tzdQeOJnmLIsx8LNoSRwy/gMzquQrtEnGQHO+TnkUxpzgtgMWN/No2WBwFFyI/2Ew7dNSvaA0jCXymLeYJ+2PQ0zuZ0BIOhhpfvIzTC3jKS6svCIiEMq24MPvJvQgE3nOWjFOiWaxY4kIhDJUQD/4Uad8H4CAhkQJRFsnIKZ8hSkQz8q84J4cCLA1Jin4NQaHcSEIhH1aqhdnzmt7zmPeYp7YYj0HgnyOeGaThvlqQEyuCGVbEIhWuSv4N2ettHWM12UlEhDKUDkbdUGnprOyOgqWhbBOnrMrwtkVQb6CTS2YmsB8IAOinvAX0tSUAzFncJRciO/fW3V2USBm9dq5nte7xTyRicAIYApEnU1N6AFZH9bCIhCql0EgzpjO9b+ctaKckkQ5szGQDqldljBUztJmXRuRE9NnfS6zs7hUv5/BfShYFspXMLaCc2Qe8FsJlCuUOUH+wosuTLcYHCUXQhY20CRf65V6a/eiUlRZzBvME2FJHHSxPtfSr4NufVIeR0t+hXJGXDHlGSFT6nzBOwOCMUxZK+SUUMEjlWBEAoQyVFpp86DqG09naePwenbTHxwLX11v/Wi59focg+N99d7HM9nqwc8f7/1lfU9L+XGl4eLk6I+xLD4DxOe5EPcxT97fg/cCcU9La0DMjt7vNX0ciC+NdQdiB2IH4nd0agdiB2IHYgdiB2IHYgdiB+LfBcT7q/v1BJzfIVvyu07AHYi/Bwi5L344v2TJ/VJ1IuZVW/pa61FadXI0wTuqiZt9hAnrigR0f7pWQ9VPW28W3mVAzDxP3sutGHv247B0L6ieDNAWu8LqNAmH17zEe0aLr5CPWNqGR11x2zwGn0eQVU2nd+6zdHhyNMG7VSBEb90XuzUYl7xZFoCYeZ68l1tRt/pI+xQDJU7cxa6YAFHXORD3jpb1PQFhY0c5e30uWfK1d1Wt4R4MiDiAqDVkIx1yixTxPwlRfLoEwq0DMfM8eS+3wp7o/wgQyq6YA+HuBiIbrQUgYkQFEHqhvT69HuKDvGQafENaxcFlAXoV6kFypNx6T7cTWM336oAiBva9SqqP70QDf/wXQKjfirqkwCgKrigVvVsu4qkiRwoQc8+T93ErkMsDW+tFH0T+oT1Ttscd7IrExjBFkjp7rPH+0RLXCo6OnK1xxFzvG7jXCJfnnIBIaUOmKvVSrK3qOvMgEZsBcU2hxcFJ9PDpwgJ7gcplBgdBtPCP0Njvom2BvK/gAtGxHvF5EAMJmBcIEHPPk3dyK1rJlzGh67CFeXHofeDvbXbFMyc9GaPnl1cpnbTt3zVaQVxhxCUm9KfBRkyAqOBeQ8WLSP8SnsXBUVOijYJMdAkhxsmDRHxRzMBJ3jXqwiLeJXwPJxN5F8wSx0WHFbqkVHBloaMDvFrG1/g3nK68IqaeJ+/kViCRTIqDaIMIEMINefpBJoUs87fYFeB5gFuSOCvpy8L9oyVXhHjTBHfKRsy5N3PBKKcmYTcgBa9VU7zDXELgQhI9SHxcBvTdYG4o4oJiHibyzveB50IEIsAZhU5FBoTsaTCNjf/qGjH1PHkft0JIBc7kFrDlf3Vr0hU4i2+zK56psXFuo1pIHfke7xqtoD5JLvRpxOZAiJTOubjY6oRx+/z9u1bdJg8SmtRUBALTTW++Jx3e69TUj0B4OKX4YH4rBEXWCHFlUSA6hBnGv45T09zz5H3cinGOaJX/IQwKp1MT2BRgUjzfZlcoz6OllkatFKAPjBZWBvQxpBFz7nJRoy3K6gjn+0mXHzIaDq+mciGaERQKkq+JyYNEPViAL5ee3nxPLvLeFute3LhghCa/I+i3oi4p4zIlriwBzl/0auHRXKznnifv41YchDAHdsZBOSVC6zxQJ+S7/L3Nrmgd1T2eot0ZF+v3jha/L9HVJY2YfNEc18ehMDO5TQVY+q1YuY+/wnyLt38/+8t6zq24pRGyzK5YG5WPjFZwX/T66lsc88DorkLTv88BMedW3NIIWWZX3MdU+cuB2G5rv+n3J6regXiQqncgHqTqHYgHqXoH4kGq3oF4kKp3IB6k6h2IB6l6B+IRu/DvifE3RLAD8csiAI/Ck13QFKwK25O9vJh7i3c0fdTlfunpCqP7QXYbQ0G2OniwVwO2jYlQmt3jXmwQJ2y4YQ9W/wYbJEVxw3h5vLsbh0F8rxlFQDSMoxnKVpVnon8lPvYE2wJz8dqDzRiLEoigkraD1Ci1j/X1MgaIxAXWP/UvrrRaj+xAtbAnG4sRiEbukqLLmgyS0Hw5UkgNAYgxCSEbBzO7R3ZiQBeU4qG1r7BBqhkQ1QSRjMjgqwiExeaQsrIwgsWaejUG0ehftksGRCPpmyEUZ8Q6Y8VP/kpCedwHwHk6S68ZUSOxOWYq3YxHobHdA8RRsmvhmICQ80Z5HGEOBOsNCQg5GrGE4NKQbLBBUsVlLLP3BkkGRFAg8FbjYoYmRurYF+80QPzjjmPMlb4P7laMfhKLZG3GWQOHHnmUxxigtYD6FQhjYTRkVFyQPQqVqyLnIu2R90dyMC4yNYUx90f2Bc65/iiMDZTiv+Pf7ghOxnHwTIYE2SK1OmbsevlP9kr9m2yQsYTUT64H9juJxZH9QW4I3hOIMSElVy1iEw4Jjh7/ekykwWusmrxCuqavHP9KK+yJ5BAlnXhEn2To12Mkrtzqcex1ABC99JOjONbqma2U1o4DeSySEMO50xnbYkxzSq7MgCDnIu1hmhO0gA5rxEVKBt95rBbCytCO89+xgUsHTofwNCQTF4JsETaDnBtj6L3MvLJX6t9igwgXhDluqbHzLOUlWqReHdK21pK8EyAYGwZgPGYcyf6ECJuxTRlKS+fa0OhfwCHlOnJQgkR8JMjrMQZpmWlgRvU2RnACnUgAwKndYwzk5B6kBvaosbVKuBeybIzLrS+viHJPg0yrxycBQogv3Dc20zuvXI2xv+NwXHEGkTSQjgss1YyLVsDRXCWYv11jgwjng6Qcr63Z6WHzgpT2SkDgFREwDBJBPJXGGEld4WrBoeRXjAASQ+X413npc4dplUAI/wIn0EaMvCJkv5SQ2CQCxIJR4v9gqni2r/+C3QEgOHjclAHRTffIMoOT2RkQXPvJzOAQcw3QfydA4PuSlmLwUyBW2CDjtvH662PdvpsAEZTIsA2EJ70xAhEsWjcFIrAuACEDG6+ajRh9z84ERpQBwVHS/z1rK4EIXNY4kdgEZEBw1p5NTbg4OTVJHTpZIKjOh8l/mIAwNXmnK2XoyPSIq6cSbLptNggDtj3HQYHAVcvJ0vsSiMtFpqZUf6dfiLJtPk1NHAv+jXvRrsbcRSBWYoxA6NEGhOc3AD1xQFTRmrt4ioy9l8VafwcMtlgLs+I08NtOvofLUKWL9bhVfgkMXCzl+7ouX/l/fVMs1uN3aC5yF36j9rqoc7HeYoOcsKD2XNou+E0gYGCxRi2XSstg+gC1URbrVP+4tF8ZRdw2pMW66nmU/I170a4AcdSSWzFW+j3WjpZvBgkI9mAcPf0dIZ8T/XTsfeXWktpYI9yX/mb81CunnIRf97v2S1/vjGC5W2QePU4n89+f4d8JxIM28S+IcQdiB2IHYgdiB2IHYgdiB2IHYgdiB+KxOrkD8ZeH93iD88ci2oFYjsgjiScsBcfnnuWJN7mb6sC30KwsXtuKG+WrUNdgur+Xmqt+bDhUuFtJZoTwHOQmvj1ZJ/kr8B56RsG7TEzgVMgWMwIwREI12F2otejySIJ2mywNHBmsp4G5A7BLOCrgf8SatU4wQyQW1sMSS61HtQ2/Mip6HB/+deAT8AE7KSYZp8DkTewOHkRFUX8/ENUMCLnxLnf8x6EY+JSx1xqFKREfAGbCB5mmoHlCDiEoMUzKWK3jkd6H+xggBRDBeat5sJ6idvIsHEclaA3e50A446y4fATmrZPLocSYbFQmzKTxZrm2ctRb3kfhFOATUzp6JQi9Jqt/XXHjFhCWhBmTSkH5C7pDmBI5EEEVPCZABLIqIhDjMRWpGZvRrQDhWHMVe4qYyLPIxiGdRxGIoJwVVrQ6NpH7EzaBkIgCUypIcgTHvBxTeJLmBj8BKRE+JbytuHGN6aMxfxbIWgBfA21I4l3ZT5IuCQFMi8B0US8YjOmeMd+Fo8f8nbQMroUnywH8jJ6siqOmoKR1TZttsSt06u09I2O66MjSHn3Tnkrt5FkchzQOWRLpYvWOGbXeYhj/W2nda1qNlAWORqUpIuesFwACCb8OgfrjwEw1AsDMKxkwpPH4kPYNxY03TyZDoKaGMS685PokkXka7EobU5gBD7v7I/JskE0JIwgXeda68yTOMDfNtYPvwRMBm4MRVAJZQA7tFgNk3NfzUfseEwHYHkxAByQvx6EApwI9RmqUTEbGgdg7jERHco7rqawhnI+11tE3zZR7FcGoHDP0wpAxxofwAcGKwBVx7Zg+pyxDz6S/Q6q7UYbCtuLGm8yqwZFnEZSx4EGvvAbj13lhbwgQHdLyIMRcjSmR+Hjci8Hu5WjyLFyUWGEEuuhI+nErOtTlcKKMpcbzr8GxXnRApPZAumVzVeKAJ9eE41AZK4MSFVejPFSqrFFxEVhs3dgl/I9bpR3Prwbe8ts4NYKLCewMCHIyuDzacN5S3HgTAmavi20flGUUnJEOog6HsH2uRgfw+l2Fw25A2F5nSXyXtiunQj8zD70ZnVzjzoCQKyJ4q5kD55VfYUCQa1IZMyTyRPQ/Z0DY+7XWfeyhkQrcUOkWB1ZWDgQD1YuJUxO/rcT/5JLEFbGtuPFGSFi5EqrAAZQynXfpZJPFKVFcAJJMDx3+xXSk20nQ1BY6Tk9SnVcmiEybup6uR6ezvbtcUGJsnXuF2SG1U9KLPA/yOMg9IaEm43R0+p9+3SKzIxKBFlo3dkmPL1Bxmuy8KpEkIMiiuJx0eRHuhnzi9/f43zhzE4htxY1efwfY8nhS6qWTMhevvyPke7oAYfwF1ioQjKX6hpTKntv194y2cFTCp7PFGkunjAOv17XoxumVv09CM/B65F6wVa5Sg/RWeR5UDAk2Kphw8sXajuxBrryYosZy68Yu6ceVR+qXfSds4e8lq1u+PrivfYXJr4mw+Uvy97ymMT3KL+vwKyOaIhseAIhpTP8JIO577feaHiSiHYgdiD/e+g7Eg7S+A/Egre9APEjrOxAP0voOxIO0vgPxIK3/FRH9DRSdvy3SHYh3vyJHwxSKkRsAYwLp0/FupKlnMNmqmhWDcjyUc8F79Hyt8RkaTdGnVzPpXgCnwtQwJDJjQsgdUokEdy6hVoAtQ+RgaPIVvA7m1sJNfoVfVvXIvU7KkQoJCInMU+HZeW3JYzQ0vyI8EWh/SAp60FHqc2UQY63IVpc4GvFJSgbt9aFVr2LWzhgNqllh+OHZzqD8D414gc+g3ikFECY04XMghsiQQGSRAIEnPwN1tKmOgdRn5GDILXfGOFAlxA1b/AqVtfCFEUkef4wx/i2BGPiAbSgZHXIb3DX6aDNKgBvjdZSsvCXcSJZAmjFxNCy3FJypasTEiLmi6PPxRwUhAkH+gz/mnVrgM1SzK2IORJUYEkEiS2IZjA0PylIdA+kj42CQF4LkajhqmVv8ilznY1nVY/2KQGT2EHMBhHBhgg4YeSfHmKmzh55LIPCXHA1eYMhXebIlKIHALBI1MJDcB2+B/At7nFdQh6bEhc9sr/AZcNHqs9tB/l4HZHD1EdcjhNmP4FTwWGnTxweAHbLoEg8f9D5iC6VtyAMhHUF4HZU+zL3Jr3AkATTx8eGcV+HiQ8Cm79EgdkkuSXpHeChM9TgIXaS6meeEXEQvOhzkxuD64FPrnSmDkIoQlUyUo4G3R2S8yJYQlkJDlQzwJ/B0c0fuhCVBO8WX3iiSXFznM2g2vIO+RxDuxZvmyDkldqDZdMjSStvKIAmnwZQymEaVDB3VMWR9EN0QsJsG0hEa8DqoEhLCFr9CEsLKWQlqLpLxKqxV46Ig/jH20BszC5HJeAg0nT7q38lJIb2CXEQvOvjkxhwHjpKMoimDyFiTtUIhAHA0hNdQgSAi/A2XnojXdUNXDVkLrpQt6JBix5IkyKslxzqfwZQoHNUoAi0sKnirqNXNFWn9IMyJuEqpao8cC/UOmYx643xIy1IefI6YBafCB01ytuLB0WCFMIacVyFXgGxtoqwEYz+ButDIJKoKBYxKYlW+iydx1cGgQyk6YlnSJ5EMrRNjHZVMOAQ25ComtQFEph/BoeJXjwyIZT6DKVEQCGF76BUhC7KuTQLECeImBoQSWXCsDZKPnA+vwyzvjRZkdBd88/Jb8VQ9GSzi3+IwHXUZEPg6MJB1kYAIPaZvsFVzqQgXVUKmQBg3xrs5ED6yZJRhIQwK+pjoEg3tjE7/+mBTEHkTCSSjl+iZ1a27q3gj6lBoBQtnNjVFKiYYHJleRqHWQZ4auZG6CHA55BTRmWZIoH6GC1v8Cpmaek5NGkPOq0j00AwIUSMJmKKFrcomOV5d4ruEbMLVa8YlPggvcjJG9CuRjLG8I0dD/gVzQ/kbEtJR/1a2zPVGOTwVehtkOpD/sc5nOHFRpjzWhRTIk9ZEViEXa/n2n/QycrUOmQSP+KvqGGgZHAypL5ikF/RBevBCNuOBssfF+lINOa8iLdam78HYqa0kkSKyi/JdLonvolwY1GKUy8QHqXplqMjyLseS6NnbL57f/gr+d/1efU8s4e7y/5pbHMs8nj/TvRRLuLv8fq9pj3Tv3t8Z6Q7EDsQe6Q7EDsQe6Q7EDsQe6d69vxmIxw/9X36W70C867XOcxAWRzW5N9Ss1NIsea84czdBTWhH7l+S40BWBh6uFf34+OyeOl57sjXI1wjMYnO7u4Oh4ZBiCvfd2WxIXYC+RzPQV4WKG2SJgNHSV5odUVaJY+SN6Yr0VPJwxjC5GeHstc5zkGSBaXVE2FAo4zfoQcrK8KXHtZlqqDa7M+F2TTg7dRfRj2U6Stka9kjs4LR86sYGQ8NJ5InLEWMJS6NQKRCMjjLiwR431kQYRsJ8PsDXIHMkkNdClon4xJAscEeEMzfwdZ4DyAOLQMxrqCbeKyUQKnaSgFCdGXUXUc12SzZpMjHjkwSwTYIBEW5FTkZF4nIs9HgJCNMECaa4kYDASGRAHB2ZIwH8FdPhtyi9ux1h8drmOfiYWZvpSUR+g4mNEwhhRsh2k2yvwH0wIPRxYfIxhCkiwWmWmkYXyvqRRPyFbA2qcxzBNsGZiAhvRc4sduJy9J6xuiGxNprhCjl3iVLSUtTvoLSDsETAZ6GGhyr0Cz+D48BPlhZtlO8iVxP3+ptj23sbqSOmr02eA/dIJmuqJ5H4DZ36R4AMMOCReW/eIibaIN00XQ5JZ+LIoROqTgAQAbSSvonq9EjNBuWTjBkt5CU9TGY66uTfYGiQThAq+Jw432usLrI25Pg3fQpfgFDuSKfaHD1dYpjOhDeMJz+DSeQGtZl5g1O+C406Ok+otyPssZ3sEXeL5+DV2sLN9CTytHqDfbwiAtPmVKKIPAl2kzVQ64PJ9A56F9JWl/QvlE8SlJvh9AQQtgnz50YM2GRoKBAs2VzfjAIQZAm2CN+03kqpC06ZGYyJn9BalsFW4oQ3mTF8nVESQ2BGPi6HWxG+eaNT6JS9xXMogcj1JAp+w+DVkyQHIoBAOX7qydBI9hgqJuXN56SKiXZLuquGy9WASGwTG4ibkfcqh6IUggREcCFG+BYFKxChM7cXxqSf3BYQXqkRPqqD5EBsRWhAdBGIDZ6Dx0VkVM5cTyIHAkyMToHoIhAavKciDd1KOmOM+OjFotIj5mbiIp8EU5jXRbwzHskNd5MYec/ItWR3uUQglLVBeABEZ1OTub2YhU9nAwgJjTQ1GSyVkn3gBxWikkcCYiPCy8W8zRSILZ6DsDjAipjpSSR+g2yrBvVh0fXEueRJIkuZ6E3AreRijBHvzItF2iLb26tnovJJLrJaoB6wHcA2MRWO4VbkFSKPXI4LJIyG5HrF43tPKuhp4GJtbi8q8iPqH/xt04NtMdhibQyPE+KVEfGOioXmmOJvji15jFOrvf/C71aXf5N8nAjD72zscYCQq3sH4oFf+02/PcK9m39XhDsQOxB7hDsQOxB7hDsQOxB7hH9VYI8W0W+KZwfij8djvAvjVBhvQVIqVJegGgaShIMxKuhQYjoalaZJKyTvK2U6WLmJ3QaTMO7WTUhhgpBPEZ+xQ0tUFqZuMh8GRhQBOhdaiqwLOrXI3VVV0diKCB+Di+wUN70rBAVjlCXjAwwOP3E9lreeLi2SgyCrBA9RQ6NDnhavhpXbTXm63yTSnepdCPsC3Ahmmy3J55w5lCjHwiu7QZ9rVaaDlZt2u+GGmTJG+VeYIC5SC6KShVN97mCP2ao8/UDflBCZI40zXw7K7G84scTBDy6BEn1N9F9oenuKvZDlQV+YAojBqUuL49OjA/NN2mtpWm6YD+8BIlSa2xJuRKVXhI8nihIUlOQQlN3gLZe2odThqKLh5soYUyACNTkKIDSRZD4w+vivREuY1C8lGAfDucgV8VsRHe8G4hijoi9MIfyCQ7AHZaqQdE2OODFkJKugbifCZpjzLoTBIAlvpkKod4EHfh3VMCi+rr4rSIAw6Q6Rg/jQN6XMebYvsxquAzNVpTJGA7LBcUhqHQCiFwIBhCvkHBe+BB67bqho0VMFQ/4jm8Lzkdze9Xymmj0xMZOtiMBIUdYK3wc+VDwYa0XsBqQ8fFg6ZXBcqK9hkyd0Ti7I3PXQEgn0iuFx5iTCcwtp4rcF3gX8TILwF8BB6OllAl2KQNcR0adwnoIlzNSRhlIhn+bIAukcFDTWlTre1POkVMZg6tR7yyJ7MEGEW6E6FsgQCg/kiK7SB6aDcggGPtA3ReZxrCMXob9oTwDiuhPLmzFSlLVSaW6PBAivGXXYL3QgEAxkedAXxqnjS/Rp6CBMoV4zHC3jcxwBb1AglOMw5V04MCpctK+geAoyzCHEzGxDNxIczaBFGwPsjJ6kAHA++nWljsRmyJUxLGHJuumzAGWLPhjHA6wLCJBwYtBYSewRbgYhbK5cNUCm8eb1gvIbEeEqcJSU0PfBWBh0RRm/PIgT07XSVYmsE6e+K6JvcroyCnNpoVKIbgE9oSO8oGv1xnGY8i5cVJDgYaSV+OiAEiLjQSUOnAkeOJVAyBU01pU6ciCSMkaIdJ4EBBkiudOKi4pJ0QdGgUgKI/oVwmtkLumMbEWEdWFgz/V9IF9FKQH48mA+LM4YHN5lXMVg/i9K1AnZaWD6IifAZt3sFIgJ74IrC6cm74yaVZkaBpxS+H9SdjGuR5QyYSm3rtTxRkbeRBkDU5MyR3x0HvFQxnGl5ws9WAJZFzp9SLRdZ14onLgiAPqtaTMiUoOizImLU5NXSofH15nO7LGUwdGxXauLOieqwYGBUA2PLp5UlODRZfbS+yXeBb5/91ysveO8LPO3sfGwWF9sQY4KFBfTBjwNXv1U5P0aqwEcvN5NlDHo+sXaqug8IjUdo2ZI/K9vlEtusSLay9G8UFDGavfqEbnuxEKdEMZANQ0Xv9YwpgqLNXRAGLUxOMQX5nKMyhzUOSG3Q7RETMODqh34mhC4Wq3oYvx5ZYxHiei3xbPmb/OnlTEeJaI/Hs9+r2kHYgfikbu9A7EDsQOxA/GA3d6B2IHYgdiBeMBu/2eBeNRgHqf7OxD/xr6D/zDw/TKbgXd/cCN4OZhQmRbF4FSn2HlzC6GTiPAW5N6o3UuiY0ul2gPyQq4jVJvMCmuRDiTgReh9WqqBM0TeqzWNDGprWF/ZW6iCyH9DlYkwrLc4f3HE5O4r9EFQtzBGguqa42m6QUdwMPUNKQ22imbIjfEi5cl/2GQz8H5o4yyRPgNCbua6yCRQL5NkUqH0ARNTMLYBXViGzG1Dki6bzAq2Lt4NgT4wzFniOW9ljwymeR81Mqipwd7zWVTvowNHkwm8LLc4ASCWZSTkijSsG0mmhhSNEJ+dtiD5adAGhANDvomWL1Q2NvgV7rh6RdDLJD4bGeBlkruFqC7GBAgcVcUrAi4QwbstZoW1qI8VB32anQwRTwcWl1QzqJERZkC4xE4xrsZ2i/OXT7NB0LHhY8lwlonONM5ILNZH9X9BvzkKThkvSIesq0ZcqTUBsQJ1HIHIuiS8eQzOT0ePFR4vMcD0g24h3dgWeAtUtUD6ZaDChuTl7GFZ0cDQcVqNpUHrBKIBH4JtUqSCDizqDIP0JfT7e/Iq1DMGvQ1ZspJT73KLktS/Um9k0JST6o9QTWMEAlwRGogIn4TOMkfagGhKCo8Wc1rs5Uoht+Ooo6DteCWtbLEZRH2+B+Ogg8ANHVE6ZX2IpcVRPVbw1DBtNsQG50INi6PSV5AvBRT0aIHNgKZh4YnSyXPHW8wKpv8FCNQSpAv6lLekWQdN3gdSYCQ3RqILZU7U/QQUHQJBt5Pg11qUnr55uspAMInKCMjeyUkkQJhIhbA4juos0ynRx3kyWAQkR52PoOwOPB9Pp5qKzBJaZ2yyGXp6Pbgk/EBHE8cEOk1lTlfJDyM1HmJzSNZzL5kX5DcEFw0tAhPqzVXTyG6LWdEg8c4rQualyrHNEGkBooNhqh7C6TAqQwMqpnAupGZTDujMSmSlxQAgOMUKb4O0CY6W1D9Oy2CdkLdi1JsQa0c9VyosNDAWAQ9E6g/GLTFSgS4yW2wG2VsC0YDhZEDoQCizoYp+JwaE7U3Mi7RdOQ5RxcJvxsJuGhDkQxh3JNrLRFUP08tRXoULGSMk2XQYEEst2hVhXzooVVECwW9/kbsxNTnJ9EeCcz5xOpINWgJiQzUCRJdx7wWOWp1dEY5rRCRZ+WTaxKiFyxD1M7idpMTOzhaej8YQoVcLGZvrsZAvwW9N5rYSxVQ6Y3ME2yKLI+2sQuSVWJSkGPk4NS22KGvEm6xtYIIk8ROnahrg4gbVswrWJ3BHQqxHOS4WH/gbnXJg4rXjg9tWjQCb4eqgGkHxDs7JsvzwGAGiiR4i5HoolwH6GeogMpA1Yd4jXKpO8EE5mrrGRcZki1khfAkBQpncwcpRHYTbGtXOgLpHryyS3nglFiV/R5jbyVqLAeyWwF8gF6++MDqRIxIyW9B6b32SVZERUndEWS0aH/gbF+XADDYa+uviva9QehT+zb9nt/uZ/ftnI1l55U8A7EA8yGu/17QDsQOxA/FokexA7EDsQOxAPGAkOxA7EDsQOxAPGMm/trv/sSHegcheNLHnezIi7L3pagR8jl4pmv899aooAc8VuU1PvQvTvbB/wZlQjxRyOyQVwq3J68QYD/qCGwo1PuSuP58wRV29sUFUL3mQdsGY8GRoMFXKveoND8cWHOUTY2WzTcdMg6l9NEj1J7cUlocuiYycKitLZkL24Vk68DLsNhXHcbtN8A4idaEgEpiuBneqgXwq0KuiBJ1QBnvA1RlvQv9ljpHPhbqYIOHWTPLBu0QmSfl2/CUQPoT4BHlqxdpRG3uNOlgCn8weOLZoTHkjq22KzU2jmW2nOhvOFDWSBoTHFj4CzASVPpOoAQxRYcOX/i9LbaonSNwexyZEXQ0Dws2ASHm4Sh+zTroXyZEkPpecARFyjK3SwuFba2LqycVhtXyetkItdXve2vxWQubCQucWqmU4PzkdV9o8iqlHAkIfBEYKKDiLOva3CjHvqEDoM+NgeNhg3mrTHQdTbLeURyyjuhpkMVROdTIuORCgCDhqUEB2IYpA8BN5FvYsNoQaOrBACERfMihOkT/BnDjbkId6j2CBqNAKpyGoa0jSHtQER64G/VbMhYWC6tIOSATeHkK+1SanJnAr2JI3BQ+wRDTpY/1VP6LotSKZt4YCFirXjhG50SY8QRKPIQFxpI5QH5izZVKRCc0MCEn7Hcme6KqBtBnzIsF8DZ7FCUr6SmsRzYqOWwFVxqAgD81IBQkIS60eB2ODSCuVuq5UNJoBzBwIckSoIULnFqhlYIioT7/dJoHooNYB7xbmSdVYhPY74zj0+i7wqXMzNKAuCCkW6joBI57tNp0zICI/ga/OkwXBiYB6FCpxkAPhk52LUykI8yJxagUTp+ZsTu+8cSJyBgV0NwL4E7zonQKhMg6dCTz0pDZSkcP4E7J8Gj8jamNE0x2bFv0dbRKI5KZiihvST1AWoKbBEZGjzQMmrk8kBwTocID0WeqQLLWZARHKNUI7YK5TldEGZkCEqEGRgPDOgAiLQCRORM6gAN2tx7t+CQgbZBWUUF5I2p7pOG0AcbvNBATdVJLbi3I2XGSARO5JKIAIKAs4UEN1s02nzlS6oCpRJA66T0tPnJq4n9+ayHegBkWhxuESlYTLMrUxlGfRcaufMCi8p9eKuaEYV4I8B9SnGhrBfFYiL4QWTXynV07gZBFlW7zFeKtNApFLm3RkcQTjpJCdQaaYmbgFu2KDOatE9siEJ7PUJozMSOriYg1VCTAwzPUEklY602ORwX7+jiDfQTUoMjUOU8pgreO/vfyP43sjInpSNDMGRaXMBrqh8Bj5HUGeA76tq4aGttUnXgji0ndKm+xtsTaWSVqst9sUdxUeoYwQUf+AY4r8ZjALN1zTYpYW7DeNtWvOKtEBZsKTWWpz4Sde5aq5Y+A79n/ulb7bVc7Y3+4Xvx6jzQd7zbU4fv2gPEabD//ab/rtQOyvHYgdiD/W5g7Eg7S5A/Egbe5APEibOxAP0uYOxIO0+Z/p6MO3uQ/KQ7RJxWNTySBDQjQlyEng/UdVm1euglrLD5X6N1NRmAwGOq6w5nXGgsibSz18qky5F3yybaAyBZkZUkNw5IIkHY9Knzijsws/NZtt0m+FDjH0hUk32ei04uDIHbgpcFzIyGDbAXHIDW8wMjzH6mab0CZAGeV32B1dluHYcWTj47L2ZJwmEsmDsFSQJxODSRbkYqF2wfvuTK0HoxZEjsIGY8FDAJ7J/8Gp6wm1wFWZwvS97cFgn+QjvDN1hQZy7uAKbLIkgrFBSF0YnE8KHE4VOzQJJC+AGiyN4jPXeXWRiWyVG202UCoBrM55S6Qp3ySYB4zmfBaBUK2MDAgX1ekDNS5M7cJUJ4LqSiTf7g3GQuBDwYFOI5ogCiYE7+JjugZE8IWOh/1VLQ/vJgoe0zb5v7YJDY+kwEGnFZd8Y+iBok8aB5/xQTwUNEJU9wi32jxCqUQ2Hk2PRB9BdqUHDKGBxorwDqidAbUK8iCEL8G0vGrgQ1fDhPrjo91drisBL5VbjIUe4glIwftAvxUqZQjj4jio1QamA7in9J6eKZ1aT3n+rVTzRvZstRmBAB9DNDyqZBeFzDxEItRRxqtFiFEhJAt4HOiXQrcW0iC2mRlMgcoz4ZQrwuggs0fuh8pYsH5efZjHRCWD2hlQqxjzgD0MMuBeIr4pyM9FXQ3Mzk4rvOij3gP5CsfhJktiUCBEPCWAPUIOFBgX9DyhCAnVPNDNi7TkQKdJShpwIwFzYqtNPAUrz2cPdAzA6qKZbbhe4YF1dVvxlKXAmuFVJgV8ELG26UgTAmNkm5lhawSZHKTzUDmfAHOKMhkWUBnlAXJRyUiJbx9FFYyNodwsPlmvSheVS4l6okq+QudvMhY8c8JwQAkn+K1QKcMoBpU6QRgpwVMYC5oanWdi3kRVaJKx1Wa8IrRFXANXep7ANQaxdFSUEZ0MZ/0J6sISCTJJVMJtMzPsiiCTg1ITSlHVCZ++KpRhwR55fj6jKhZAGBvDF7wMngFzIMKUE7LGWFAgOLxKQTP6Qqlo4RIQIbc+UwUO0/Jwm20mIAzQKmlueBN2CJF9EdVCkspH5KpMgdhqs1KlEgPCuxyIXLsDyzPoKi5jQXgPNlJnA0FNGyU5MqrK6QxNBkdkMNzHkqB4gtelz4RNuAaSseEzjRebQtWdxAdTusAEyU8ubLVJpoo6xHhTzzDNjUB+itM+ZOMQ+2NiRqFTxxgXbjEzCBntoMw7JcTvHoi68yGdAPqdHmyIPlp2QXXjaCyGnos1uR10IxGli9OgvyP6jMFwF0sCclv8taC8CHM29I6MDe+STkXTa+uDaWqo0gW0PPRTv9UmmRV0iFG9DmOkXMBbFybiVfugfl5N1h/zlJF1RB1j+lvMDDq4QKkkeqdIfxvtm/A/qj5qiVzdf4cl8fBt/ldYEg/f5n7f50Ha3AdlB2Jvcx+UHYi9zX1QdiD2NvdB2YHY23Q7G+S/3Y8diE+2SV6DfKwGZpyFl2AeKcpX0NtZ64wFskEc7vfj+MH4C071lvGQsPJDjLlhjiSJpcHMFlkVt9qE2T3UNsDM6O35zJg+lzxiT68WPkyaaWvgyTc850bFkN4cUYzVQgUPHB/LVNoLcjKUAzIUfBf4xPCOKsaht8DX+4EsqaMHCvkR5CkgfwKPFGVP3NKSsMSLlSXZI/EXkqqFVy8VY26YI4nPkvx6zA02SFA2iO+ZmiH1BOyMIQNCdQFUFyPX1oB4imxHJsRT6yOAeWHRNggzQG3DJEU4LKhf69I+6eO9DX1iNOWUPDw2+zEQCNGMCNxHpw8Ye+j4K1/hLpZEsFyJ1uVYj89ULZQfEoupI0kogDgmIZLNNuVMYmKzUl0MibrKroiKSSPTxci0NejHUsHjxHT06YgSgVAFD6vFHoA2KflUV7C01ThgR3JLlJ+RB77ej0quCPp8UF/COzp9eGpvDMxG0e3kFmPBqcGGJMf1oV7lL8BhpGOiPCg/BL4mHduGDYfyOMjUqKCsId240SYkhESjA+4qXqMOJgIvjA0QES6qi3GhtkZQLof8KyUk3UOtDyEkgP+E2Jjyt1oIBGJ2lJogB4R9giSEF+uQyjRf+uDI1rjVDyrwwOcDbiTCR2AyfiBhhtwNOIF0txgLcnaCndBJY6zLDAU6yJFIS/AcEY6IjzUjlS/KH0wAy2BVEFDZZoOQkSgdgEZHMHYJdIQ8/Uo6087oaC7CFKesR+Ry0BtGPp0Gan0IIYFZNRlIAiGlwHEcNJYBLBevTA6wP3Cq9lAGkcQrL67e+CY3GSa4Iq7kNQTwEUBuuVJigR4iLkrw3GAs9HJFML2uVIBgJhdKHbk61cygz4i6iQRRuejgtkIuCVUvMJg32nSUEMJQmfyEzDh0Mkm8C+piILY+mBgENWXU/cVH5QCoDvSMIhIFYhmThHCerJcq+rdgme6VXBBISyULZVv7g5IMAr7ZqzgKhpheRVLRSEBsMRa8s+S8AUE2RJXpaOT/Ws1mTJNrOhlz4labBoR0NgFhriWJd6G6GC4trErB8eb+4jNWBVwhVGujirSaKRDG5DCdDqcEgUrtb6LlifJMt/oh3/5c1MdQUkenzIXKGXdDlS5uMBaCLnI8SsmTcWoyBQ4T/IEzji1pnYbcKZekq5QncbNNz6kpKIIKYYAaR0eCHMkXKrPVBZe4IdzFv0o69JyasPh2UAIhPzWW8dGTxZgcZH94XcI5NXFuimyNm/3A11fM3iEqRsTF+jRE7ga5DbcYC1d+/9ajBm/fpqmN0ZunCJdR+IwoGxxaTnBbUS7J5aTKGrfahJ+LO6q7Ch1SWOMRrUJzSr46XPh7gVwNtV8LykUP7HkVHVHAGgdz5TSw1lTG1EZM+4P8E+h99Hh/gQ4VfqNEtsbNftjviA+9/r3MjL+sH/9eZsZf34/9XtODtLkDsQPx8HXuQDxInb+1zR2IHYiHr3MHYgdiB2IHYu/HDshDvXYgPvSSW07Uvaj4V9gGngwGuqiTnwCehzduBo6ECkbAO3qG0O9EOlBBuUK5Ic6Oh5/zQOaGsSlM2Vi1NRyVQKykubpwu9vQ2VD+RmiUU1HFyFA6cBvrpSIHmSPkYCBW1EhehrSqTi7OvGLA43Dm3AJ2h0bAkTOdjQpUDPtr/A72cb0f411xqGCMrwZ/G+o9UJ8CTAZN5eAg42awBj4mqc/Wens2WDqAo5Qbot4kDncdoTKgdbB4MOl3rwoZnp8Hn2w5dLvb0NmQfhgng/oagcV5APUKWC8fSeZfY9oM1oOUPiIQTVLf8F75Jdr3GAFHzpJeJoauriGR37GtFwKqAXNxR/w96iPLR911jMoYPDy6hEQdfSUSBdOWQI6ALisoe8yAoJqF1hGyp6bR6FEjM90LfRg3RbzFIAkucjKor6GnCA6nZgejIiGjMn0PZywQlS1xxssgEMfknRKC8kui34y2fkxy9sEAsKe/I78jbOuFYGJiVo05uQqPrzJxfiSTAen3oyV94sOvVMEAn8HRe4XaEpIgqnhiU1jd0Q8F7ch49ayDDImgog5IvDh7uJh8IqpyCIlggwXRmf1NEzkZ1NdQjxjyU0RFgylZZux6ntjNQCcUxHppBo0IvAwVh3fmneLgWpGcW9hj6ZWmejsSFfD4r/BWmHSDuwz3bGqU0KBG8krSiPx7kqqxVgjjgppI1NsQTQ4m0kPUxOi8dr1XxkIPqQU5E5QbQm8S9RqBrIjyOwbdOpgCCD0hwJEIAewICbTbZkFcog9R5GRQX4N8lGpgFlXqFKBDOIHyI39x5Qxke4Dr4alGQq6J9AMyEx3cW4YKsJhziwq3eFJo7MntCt4xtNyBnIunuwz3bGqUOE2Hw8PAe02Hm/UFuQkuyjyQm0GfkC5KJnSaMrfkOxgQfVBLNKuJnA0qaFB+KtlukDsizBHhd4BtQcMGX7ltnQ3nM0Mokma86WuclCsiR9g2ppArzBTC6TAhlOgXw4y2lzWiUeqB9aAzWgP1M9DjRgUlfJRZAT1AGSscCduz2Y/bQHiXA0E2RO6AFczmwxkQRNy4IZVLXiNkVZDfUQIRorqHt3YKINZYEDkQ6mES9TVsPSVnw7sEjjEtTI/DWCAhM1UjGSIHIvJLzGFFj54DkfQ79NyPQKz2w6kqFYAICkRn+jDWnHE5lHthPiHBNDiUuYEIxg50ldIQC4ETb8obSe2ii7oagQMFp5LOp6NvaXvIhKNedVo/r1ROTUqT6ZQ6mStwBZ/5oFBwI2TR4AtA1ymbJZMwUa0OHkRHlSpjrWhNnY5Tl9xltvVCHD1U4IKCv1CNuJAPATUO0+PQxZpsCC7XVW8aHGRuyO8IAQJrztW4IaekbKHKG0nt4hh1NcCIUKeSY3b0LW0P+Y/ujcbJoIbIabCF0fQ1oLkBvof0U/4mHxRsvTRZNGC3X47KZrFtp+jcQkYHPGWw8Ea+C1gjWgq9MebItl7IX/Irt3z9O9kcf2EH/r1sjr+sA7+nzf2m3w7EDsSf7scOxIP0YwfiQfqxA/Eg/diBeJB+7EA8SD92IB6kH7+oI2WtW218tv1/jSfQDsQff5mqO3QnTJOX6ZRBXU5EgSKom7QxOrxpbpDTwIfVnZl5JCDq8T+8Wh2snz/k3ydt/Qnvnn6w5Av22RFLr8NrWeJpDkQ7PaZul+pMERwWW2I1032H4ugUzeH1KZbQrdZy1na570lbeWK95vyBW+RMYAAIE2XQpMCg9+C9S0b2zI1rtiBzGcmBaBmG4PHzpwR2KDp0wLuXlkC03F4fZp23vtT1rGtTIM7W3hIQddZyWcMSEHWxry6OKqOp60PsWZ2iO8f9C/sO2sohAXGkz4FmNUogNGWTPQBMdQtnjifGaVgG4sxTS7p0A4jWta1buyLq8m89PUPvB8J9JRAuDXTak9pIV3i9sG8KRKemRI6+Ja43u3p6mNgTxszLeRpS9DT1ABEMTAim1BuTPtfEys+fYwPnp9e2fXl1558/JbCDe3p9epXG+TcCcXbndrxMv70+fee/Tv7Fp6fXl9dvry069IRtPFqOZ9mfP5++S+lv4xb5+zL+J5++vUqbckTbyudxK460OBiB/JX9UpK18P+X13zfC9tDrQdXRiNAIJLXA7Z+e+VZ37boD6KZ7jtoa/L3QBaEASH5JzIvqJ9BvQnqajTQzjipgY0kATtyGcCEUJIJk6id01TjeA04CbmVqFu7IsbPmHzGjXpBP7UC2cG9jME91+0z/m3lgpdj5Yga72Uw5Oj22aEWHi9lf/4c2xi3PNfyWWaAWo4aP+tF8dxKgVrjqS0O1iB/WwYpR4z/t/i/LvZJjWO04x5ppYzm5VUjGffIVk5BcqD0R+JpZ/tYV4u6DpxICITRBEgPANvvanINRqJhAt5kHURrgkwIFwUXyGyQOnhF1AZEvCIiEOMiTSAOApKUjHtbAjFewALEk+PAcsu4rZYlzo5vDQgpjWOfftQoyWPky4CUecFWqS3GoTU8SYkW1w6OHv9HHHWxj0CgrvowiaZ2jOTJscc2BQmEjOflx3Qf65Ipil8YZkCYn4jLdDOMU1ECYbodkbPgcscTrhGtApHWiAREWycgWvmcAfF0RtfdkyxqrwDimVtaDu+rHS9lx6npLPvG8/nMc7nG94QW38QwqOfWEQg50oDQCF65H7XiauDfuthHIFq7IopoCIQcNwVCopHPYw2vUyBwPaENGYWgzh8uU97wqtDRGQGBSzS/MCmrrUvmM8nrJPMp8fzWdCYQdT0HApOQAXHGun7QrW1tXec2OZ5zcT418XgpI0DKvnH+PY+rybMcgO6j1+PE9DwOJia4cmqKNWC/1fqNU06tpXVfzVPkmWtEGQ2BkOPSYKOPrUQj8bDX+T6pK5uahAUB5w+XKW9wwQVDgcwM/I7AO+UhXGxJVo6Cep0knxIu1q4+Y6k8vNrvCC5/L69cQl/wLgEhc6hsHf99tcUQi+Rrrd8+isUax0vZ8Yp4lX2t9N1Je+M3+9c4NX0ftzj5fNBlk0svazgwQsfFWtpv+XesI9934ML6vUWcbrJYIxJnC7I0K9c06pLWs8Xa9o0RfP9m/ZMwfx0L4jO/rNu6/Ju/m5cdr4javnS298T2Y2tru3lEy1bqtGX5uMNqXW7lK/NDAvH0Wv7N383LyhXxHiC2X+2Nve1KNO276/oLgPjz8f+R1w7EDsQOxA7EDsQOxA7EDsQOxA7EDsQOxP76KIxfC+l+gvx9QMR7IfXhjnJ1W26py1sp9cK7WcJ5oe1J3U+T7ePdybMk4sckyvhu0iaYEU/vGJz88CcknUBd0H18Gg+MFWfp9+CQR5aX5CiLFx1TmqyEi8e468+fZt5gD2c1roo1UPJCP8+BOCyNmSbT0yDUBRB5br6+AUSqv2SbpLoPk+3IgqI5iWAKRF2XdzMPk6T/tDdttveA/0BdcMY1carpbc/ncWx1mPWZ6AQStTGqDAju805pSngsvolAVJkMh9buV4BwHweiXQSinZ+K7wWCyRzJNQgobjbMWwM/B8LNgTiz3CECgaerdagq50wSpJ84pLgEjJagSZA+Zik641BpyIFwEyD4AUwJYRN8PyywJqashnTMS2RIWHJjvDF8JlMCqZjx8w8cx61PyJ2V9bNtKY/9nCyU1/CkPAxQUgyIcxuzEuRgjKV+1M6OwPaxfnA1vseof6C1VvfLOyf1yjZkCM+SW09cE57QBML1Tv8NOsUoVwWiD6YkAmkI2H0c4z4c2dOLQi2gOOVh4On3UikrDMokz+Ay1EgazlkTU1YDX+RJkCGBLGhkKUg5yVYJc6Gt/6e8irZmmnFav+VznbbG+pQ8UJOHwY3yecyl6enBcgaPZMssASpFOTUxW8x60Vqr++sWXBEH9ocBUWdcE0c1iQjEaeDT+5q9l8HXR9y9PrTfmVmIUPQ6e/xdiXugABAIXmdYZeJRjjV6pNeffjAxPmdNTFkNNghPkSEhuWJjKdQoX2uJlx//04F+0lT+tH4wHDBx1BkFUngNY7ks0X446zADOptgnn6Qj1GDi/GkdK4EBNtmibGu9kXJni8kybi2zq+IxDWhANj0ivA9/WGOQqQQeRW1wjIFknHw+9NVnTfkrNcrgrXpFQEhMlwR8Sjqc5yuymUwIKasiSmrwa4IHgGGhA5YS3bE67MCIfUSiBb1tSxZ1I/hfSUQzjJer6ALxHqNyEIg2gwIObZV0oxxMxzqBw3mbOwJ0mha4xG+8op4+WFAcI1IXBNzGyvXCLFZC7JGiIWTfKNSK6x4RXQ0YjMgbI1gbfqtaQi6RqSjonQXeQgyLIcF1sSU1cC0N3kSdqnLsWQpnMGjkFLCXODUxK1S83lWf+JK6PV3LqamCH2r35WwtY3lCKRj7Li6zvxOBPZFnN5k4gMQtR7XymqYrgh+a0pcE4ppTb81+V7ZKlHVQ6TKKBlEVScp4Z1OTd5HICgMk6Ymao7YUU6dV2QSGRdQGZbDAmtiymrgN3byJJQh8YpjsXCewaM4cLEel+tzLcedWeb1PKsfbQvf4lyTQ6GLsBIUX8l4wO8H/I7A1nMs950LtLSvnAxsl7VEPkvttfIvan594GJdv7wy/U8WCH9H5FwTiBtNfkdQ1AsidaovUg2mnhdk3veV8lu4z35HsDZoUTkeCa7MxY6Ki/Xsa+oCa6KdfPlr7/y5tLh/of7DncfeaqN1S+yPpePKGt77yzrTgVMxunzbWs2u+NpqR/1zXWlkiTXRvmOwbw3mPayMz7zm7I/bUb4XiHT+wkUOQ7qsq7Fe29ZR/9nXftNvB2J/7UDsQOyvHYgdiP21A7ED4XYgdiDWCDSPdmZYPB+L63O9+S1j8T4gltVOf4kG6l8AhIeWhn0KyyUW3+PVZDuqXj2tca/x5PhJkiF85yi9rDrZzaBpl5LN4F1/gmJyyXiYlvJFuPKQ8eI9zOhBsgYEoupjXPHuT4PaT+qEcoJBgdygG++k9uRmWN7N4liPtRxhttkMVVZaSsIUIFbZ2zlZrQLhJ+dvljwJ6vKu2S1+circLm4mORCzJHqio9CvoWA8TEvxJnVwZluiT7fOzhnJjxmrwpL6ORDwWEknSCyh9asURuVirzwz0XJcUwCxHms5wmxTa9PS6v7h07D3t6+IKRDpb/CLQAQDogrmBETB8knYrgCCAbqqaCGVKoEw/f552PQoSkC4GRCV5aeLqHgyJiA6pINQqic3owrHEojZqPhszNII5202BoTTHJHkYI8DbYvGv/L4rjMnE/h+SGMBHAUVXZcteoTYY3gwHcQ+wJO1oPkp+dTbOz3zevy3wmaQeOSI4Mh4gAz7UqleJgzKyOdAHFV2HfLqA68ISM3bNrSZrRFQyI9xSf3uitYcyDMGxNGA6I2b4XVqOg6wJ3FwglnpUT7CbJNjFPrI7JCs6QlMtM6S4LC5kM/OdfbwO4DooLPh4yTAI1SHwIBQ1sL4GvipYuPw2ah0MovZrxmbQeIRIJqBjAeUXSqFQes8r38ZMJwOYFbAsMAMDyCPwCx0p8oJnc+ACCFYfrrSyesNrQl7YwSwp9dLEC19iQemN157U3EU4OcAJ5iVHuUjzDY5RqGPzI6gFw4YO8zFOgKRZIIopkVCiTiejAsYL0ifLDwMCHAWersiMEN7e0cgdIlYYTMA4p6mY0bGWizVcw6XeBysQcwPi7wKPJpviyFMO2S7mYUkIKregLCoBAi7Ro4urhGdy9YIfD1IJJkQVatWepSPMNvkGDWJ2ZGKdWJL1jPYps+BCC5eEV7NwYbiikDKnEB4ZS1wNuT6J41zKKq0RKywGYKuEc6HyIpbKhUX03hFuGhn1ikQZtWmRiNuCQgClJKX6YrA8I9r1VEX6xII9sauiATEco/yEWabHKNwicwOHiDFzMAIwboMiA4mLFgj4BECOGTLcbCpSQyP6GyidmK2LOmg8R0vR10iCMSMzcCpyfWJ8VAtlgIQdHlJtmU6HBIX6V8KROfVJ857A4ITpMQlGywuA4JrhOcccsm/NamdlfbG1oguAbHYo3yE2WZQmma0v+EBspjRD1GdTeQzOqj6GnIZXWDodaEHIb9n2zIIszw4m4CzcLXfEfQ0rPRdCQR+KczYDBIPf0dExkO/VAq/WwacAlisqQnC/QF8iir+rpAy8kmcvk74O36XhxeLxGW/Iyyq09B7tOaC1mu/I8jEOPXkZsgKwitJRMTi1LTYo3yE2SbHiKol0s/xZTyCkH39CsUe21eUKD5zHv3ILY5bbIbqPs7DpM5br3RF3FGDX/s5uNzaPNZyhG/+Ls8rXGYXhNXPVv6jQNxiM7yP8xDuAGI7rnDX0VtALMV6JxBf8dpv+v3qo3cgVnv5e4/egVjt5e89egditZe/9+gdiNVe/t6jdyBWe/l7j96BWO3l7z16B2K1l7/36B2I1V5+2dHvrfbzg/qxYbhder3E9rG/4zS5o887EA8DhNwT5N3AhZdfLq9ZuIUSTF7mr5C5Sox3VgsOhXE+cs7DYttR1yLxH8h+CLE061NOhqaFAprXPUtUnbg351WkvfeMSN5Ni6zJtnq30mfNkVHPQ5kMzudJ+mUOh8/LDzkQ5ehN9SoMCHzqy9R9EltInIfKkv0FL8J0LYIvdS1yIJKKhvUhOObMS0WM7BX3Wr1V3t9+bUR89qGKNaVoq6z+yi33OfJZQtxV5Wf4DH/jG/isfBXmQLCcd4t6FTeAKFgcK0AwCVoA4eZAlAOaA3G8Cwg3A2LpivDz83QDiGq5z5qsDDawYBUwh7rI4UB+rqf6hJXneVcyO1jOL+pVWKkSCBeDapTV0VyuYwRMN0lKpUENLupaWHYu6VogSqhgoL4uASHbkSfvuceGR6LuPbkeaa/NCU2s8bTBaiFHhD3EEZdKYeu9TYJXTkFjxqnpiz537IPlyGR8nDEZKmabFjkcxjcgZUKZDwpEzuxguWW9Cis1ASLYfGkaFaF785U++4+0KTgZSdeiYZczXQvshwoG6rskIHDu9TrUgelcp1H3QvNgblr3ujilaW5eM9zLrBbLUHuQxzpPwAmEiaAoCaFXQlHq84V9qOKD8B2XEWEVKM9mlcPRqPpEZD6ElHPNmR1SaqpXwf9YqgSi6rM1wpEDYUCQ/0BWhIu6FpbET7oWSGdCBcOumQyIMWFbxaspASGteNSd7e3TcQ3S+36D1aKJUWelEhBvY08NCKxK/bTPxvwyPotyV8gqyIFY5HBcqD5h5ZWRMWN2hDGbvKRXEUsVQaUryQY1AZH5fnVJ1yKyKTJdC/IzyCGZAhH0fCSbpAQCZ2Lcm8iWoERykFZZLboSDgZE09sa8abcFnknrI3pYk3fmpzPgjgaYxXkV8SEw2HnPtQntLyLa0RidnALHYmmehVWivNl4lBMgOh0aiLrosMWnwyp6NE20bUgl8PJ1BQiJ8OIbyHochxZFxXiu8gJ0x2HtDe44jhOxP3aiJAjkiYwfimQVfJysdXoDf2tAGTJGylpFFgjGkdWgXEUFjkcPfkGoj5h5VnVhNmh5aZ6FeBVaKlTLzUkDkXkjoPJIKV6YdaBdYHF+qJfxFXXAs4uLte1YJQCOOpTepv4xGCx9vylIHtUEaOHl1jAKeKyvVc7jvELD2SV1dJXulhXg1eOh9QEbksQPQ5ZvXrdylHL+tw3fQkEwJn/1ptyOKriF8566rya1WHvwqSGbQ5FejEG1nX7GYu8RPlVdG1PuHHc9ojknBKlpc5iv6/PYbmD22yJbQ7DvA7qVSzVcO+tBdb1K25xhDvjuM0fuVViu8/hPcNxu7vvrWG/1/SpMHYgdiB2IHYgdiB2IHYgdiB2IL5mQHYgdiD+ZiD+/OuRg/tP6ct8trPVF5T4VbG94xX5Bln2N//bZOUaLdVMS+JwU66Qe554PvlK9Qo/K82WhHEx3msFPwPaw33BbRhS64vtWVxrmh65Aoboeyy+wsr7BARVkfPIZi+/cQoMi2WWuCqZWcUiEFN2R8i2FQyO+Jw+8wDBntX3U+KNt2fyxpv68uCgPcnvC9EU70ulj7KGpT5tKGAs0wAWgfDTK2KMrJkBUQyRL0eoYIL4NSBmXJXqxhXxASCc5qlVvWIViLHc0AXNcEy5DX5ymtwNRBZZNiX11UeBCJH/YSm1FSDc1sVSckTmXBXTjRAmwhGshyMSMomvADmGYCl+Kl3MGRy5hAiACLl6RSotfAVqaLDcmM/iFdEnMZWoiqH6L9QIaWKqSPkgzh7ova2AIbUibTWYWgc5FtfBPrHnpu1h26Da0VvqM7IuwpH8lNAUbJPE9mhiS169WMY9fap3ylUJ8pR+FfVXkFkmLyPyFU6DuYXoFeG7mBQttTmEU0HlCl0jLqfe1CusdPAUxFFmxiC5Y+ofVUNKmrLbVKCvkP+y9CWSp0H5IFFrwN1UwIhXhDIxujHKN+THqdtBIQeOAPPgXnU1xpNkMCAi6wJP2Xt6qCjbpGR76CkhRIOBBAsl0JCvMeOqBOoCJEUindsLvkIFTgYTiRWYD0YcKBkc4jYS4gCNeztbIwgE6yPXTLkh479vXnN+MeudtDqMDREiswKRBeWDjPuN9nZLAcPWiOZqPZOMMlrXmJoiQsIhtZJ7ZYIpjIwA4FyPbJOS7VEpIQ8eK8EiZ41LXBW7Iro1ILgeBPiJRAUjH9wig6M35Qrn7ei0WOt54QwITLXjln4GRNLqsAEIkVmBbvTaJrZtaXpkChgKROoZUvtI7zMmN+QRRoWPAojIuohACIsjuASEsT0qx8iCljItqTS2E65KcKYbQfkcBaJLfAXu8b1NG6FLU1PO4AAQqlxRfmtKLBBMTU41NDQgAyLEb01Jq8PYEJOpySkfxBnV6w4FDJ2aYs8IhE1NzqamECcq5ZSAxmnKTDHZr4wWtXeyqSlje9i1GbSUlAEPstO4p1yVpBshMysXaNJXjK9AcmGlHO/x/SUuaAWDA/F5+x3RcLW4GjOEbAypL2po6O8N/o7gb4qqL4EwNkSkdOqyyDa5bG9pemQKGO7ExVp7JsMhHAsSL43seaTixpDHLOztSvkfUUVE2IRwT2kS26Rge5yiLohXvQ1ZxpWTsshV2XADuf0Km9/X7n+979frcrS3FDA++uv6K2LbGK1y90d9PYL7mtf7OrumE7LWk/DHY/trXvtNv72zf3VsOxA7EHtsOxA7EHtse2cfPLYdiB2I/3BsjzlIj36e/gLyzHu8UN7zqn5BVH87EH67SmExlNoRwpvInwOaam+st5KxQMirsCpKbojzG7GFGJXqhyxwK+6COSyOgC+5KgsKIbPDt4BIytA31Ubm5hx5dwqHhcwLJT0ZN9feWAeiIB/0LuNWhGL4SrLAROkjRuX8FIjKzZQz1mHp89hyTkUepffLQPgZENXyFeGdt6TattrIBhBa1SK/IrgpEKneRRWYrwYi2IO+ObfixtBvAOHWgHD3AuGWgQj24PANtZFMJQO8Bn7GxBOEi0AWg/NTL5TgEuthqr0hj6+muqaMEGOBSE32meyGyNUA3YDHW1roqswPMkA0qj6PasKjGEwCvRkyHQ31W9G88gqnIo8ypZmM5dJkcSQlEGNu0F0lA2KMjdr7N9RGMpUMJA99ylrL+aYsBjfzQmmGxHqYam9Uaobh1Vmhy9KuiQVCIEyVIHIfvPmzOCp4MG2pSU1jgCi3YigVLXIeBXtgSjDC2vCaSe+8sU7WOBV5lIkT0jGxGUIWh3MpvaqPxMPCIBN8GLNw5kaxqTaSqWSA1yA5VrIyoKZx1eR5P/VCcT6xHqbaG7I71VUyQhILhECo9I9mekVVo1HFDlPhaMDnePNkY5ABwqhKRYuSR8EeyDGNS+yOwPaMabHCqcijTJwQpc7o6BgTRf4l86OKbU7XCHeP2kjGxACvAc/yk4MARgK77N3UC8WAAE9hor3hyrpKRkhid2CNoP9JNJBRtkblUpIfTAlcEcqMIN0gApEULXIehZRtepP4UqMN5XcExtivcSryKBMnpAMQ3kZHmSgdbKZUPmuYXxFTIFbVRjKVDE4nLnqTSJc6shiCm3mhZKyHqfZGnJqKpsyrJ5+aZDCo6BQHoIu0mi5Rt+RyPkYzJ+NWsFwSUihVM4z6KaXMaCMq5VzQ9gqnIo8ycUKOgwLhUxx0lSHnw6amcdXIlTf8nWojiYlBXoMuTgPnNNG3AIvhOvNCyVgPM+2NcbFOdU0YIZEFwt8R9D9pIveBC2zifVDvQlgPZGOwNUbVlNyKgkdhbBJGFtKySwYJxmSNU5FHmRZrXOFK/kxMFEYPsZ/B+CS58kbmVLmtNjL/ElYyEe79bZhrb2TWkT7XpFj2QwnuzhbuiiqPPvi1Nj7CWwl3bg0fUxuZV1QyEd6nlzE9/h5WQ/hSIJZbD5t9/GogPqo2svnab/p9Jr5fftNvB2IH4oGj2oHYgdiB2IHYgdiB2IF48Kh2IP4tQPw75EZut/iZaP6AM9AOxJ/qiWphjHdYN7xKcjZC6VVintWz1429C5F81KukHv/Dq9UB+/lD/n3Sgk949/SDJV9esbG+NSxP2fs2AVGPH3Tb+KEtDqkX3qX3sxbLg9WrxPkkU77kVZISHFOLDPAU/JpFRr73l3mVtOyk4PHzpwzOAQUOWvCAdy8tgWg5IPWhCPYwG6lDNpIFEOeEhwFRZ3/qG0CkltoSnyqqfQe37lXCRNUiEMEk3heBWNz7xV4lZ/apHjt5A4jWtQbEvPlPA9EuAtHOL4BFINQ/QdQw3IZXSe+iMP/Eq6R3vQ31gleJ7O1+rVfJOGTnp9e2HSed88+fLz9kEJ9en15lKPk3AnF257aWbU/fn9y38V8n/+LT0+vL67dXgelp/PttPErey17Z/vMnyo0lD3GbtCRl5ZPskyjGrePUN9Y3fv7xKp+5VfZOW2KNUv7bqzOvEskYHYd1r5IKmhpLXiXMNa15lWDv5dd6lciV0NY41Vu7IsbPmHzGjTWBeGoFsoN7kcGu2+eDex6PGc/IWo6VI2q8l5P0efyPQDzLxvHfsd46HcdtOKtrHKbtteNxtbTwLFPg/+pW66mxd9rSs3yu0WCtVwRmAg7eileJ88kIo/QqyVeBuVdJubb8Gq+Sg6sNiHhFRCDGRZpAHASkseQ39/Lj6QeHDUCMn2V4nrDG4Ir4YcMqA/30Q7bKsek4buP0Ujv5NO6Td44DKyVefvxPB/oJE1k9a0n+e8LUVNsaQSDChlfJHAjzKlkCInmVTIH4JV4l51aBSGtEAqKtExCtfGZpDuiZs/3TeSzxiuF5djivcSY/yxXR1gYEj3Nn2yaQtxhSrBiv8v+zAjH+5whEi5pblixaQm2vEyAS22DFqySSamZeJRlPYcGrxOf0tF/jVTKOLYGo6zkQmEwMiLOs661sk60YJJ6bjhPIePaOq0b7zDWCK0j7LNMIgMBxMtdzm05Njp/aWqL49ipISDlOTdwqx55nLfGaqGXSst8RxnhY9ypRjxE39yrJjlzwKqHTyq/1KnFjJ7GMvtq3Gy7WWDbH7S94F4GosWR+l7ViXEJlQtHl9iBnp0xN3+X8HY+pMbVJvQCCx41I6jb82nit8UnqkCjGgeZiPS7X57H+FrHJsedZS/zaMB571m9Of7tXyVo809d8X1uXf12c+Vt93y4c2643sblvqaXii/K/w6vkI0A8vZZ/7R2BkPdfDcS0pTuH+eNA3Fvz51/7Tb8diB2IHYgdiB2IHYgdCLcDsQOxA/H7e7ID8SVj9FtfeTiPSml5hLh+eQw7EO+NYevR+8m+xER4mmwf7wyeD+cxyfjjML6T+75Iv+PVxiyGJZfDZmgLe33mNxK3zN4tVgaJd7kj6+cOEJYDWTn2tvuJz97f0bMVICyGfqNYvwIE7wJG0oHc4Ndb9iA/tAcyJpzxJXIg/ExxuCp6H+Zng8/8RkxXvxiuVfWQKBlfAuE3gKjcsulGoipU6uGixfw89nsevPNTIO48K28A0TL90UpuSoE4s8yBQEQdjG1tCr9yRayMyh1AuAkQVQ7EgjqHuwnEVCvjk0CsxLD6SjeChe1giQsyFJwBgeSiQ0ZYyhhfwmU6GCf1JunlWX+mdpTrkStXNFDV0L2QUT9iizI8Lo3WwncnPYIOJo3WwQfjGz5qC40MSRhJBi/pdDAq2XM19xR3zNQvkgKItMFHeVUrQ6XfU2Qp9pMehXgzL5bjLM4UQx5F7EHU7+AL21JqRNPpSOvVSryRzy9jDok0Exn8OuNLFDoYjt4kvVoMRK5HyJUrJKn2ZnvheeLxJL4yPDoqZdg7e1KfDia0CCAQJ2S6WYfwPEAwSDodGpXsgWKBewMfJLmfJAUQz3JIywoolVoLpMhS7FQakR5I5rBTBRH7N48zxZCisGOco94PdEBUn8PltMRxSRaWgSbDMVWd20gvSVdE4kuUOhjU5ZAuQ00D2hSNqnGYckUDVQ3uJUCUdzDYTlf+z39VBUctZKR2NThDplu4JprtFuMKl3Q6UlQyBEGBYB1JkYA+Jwa2V8KEObCkyFLs1M+QHrgYbTTnKOJMMeRRBNUTGdPEepReEfjqYXmiV6S1IytBpiZSPtIUxTUi8SVKHQzqckiXHZ70hzbFwDGKHipwFQpuDYhAbQtv/JG8g1C8GNJindEOQtM7l3Q6LKryisgkKTgMQ3DLQFBrIweCsZt+xpteByZw0flpnCmGPIqgeiJOPFZ8LhGgU5OQSrKpqbYronX81oStrQLRJr5EoYOh/IzLZXFqSh4qXT41TYDoojJHl7xNtIPU9lAVD4KQTU2qNkadDotKZmfR8IhA+MwtRv+zNryPQHTlKZJi9zq5EYjjYNulhTLOFEMeBY+pdGrKgOh0sX7SxZrEQWEkEIhxssLvCGw9O35lrXO+RNLBoDYFhIEGk5ZqkmBW9FARJQpT5WB3xdeEukhSNsBnBe98pYsgHUxU2wO4B+V/qFHbhdtNpyOeHo4aHsL6gHdXtliTT8I2qChyMkUNXawtshR7xQguPR3DXKOqH8FN48yBSFGo7khP8a1sarrom1sJ7/T69b+sw439a1yQaiGucFeN+WtZLeRWpGFznMLkX2F/fXqY/jwQy6+c6fEZID4W6fuAcI4r2oMD4b40xn9tDDsQOxCPMwg7EA8yCDsQDzIIOxAPMgg7EA8yCDsQDzIIOxAPMgh/SQyPH9J/E67HB+Iur4kveq0/baVcipJNgbue2zXexdzQxmZp9BD/bSbbVjswlO2uxTPlhdgnVfDIEtMznoX5W+DTP9dwLzg3EtmTfkHQ+oR7r5SaT2OjOtV5vd5Xt86MGRB+Jb41IHxh3bFELpjyK24DUZQsPmnichmIrIYmDn3IQNgGotoMzQTcbS+fwGXqawKEm8XjnftKIGZ8hvcCse05lAIpShafqkTX8StxITl1nAHh7r4itoDgaUEguhyIFINoYTAJcoysBg/PBbu1rJyMwXxKkmoGVTcsDdRb6qdgXSQ+g+xPLiSn6KHCtFBieazyK6LDirmbpNLiWmIlqWGfH0cFD/IzzMOk5FnER4vj0MdI3WqbdpwqjDBeqOVb8imI2Du4KJJw6gnEMQcismCcPEJ+wrPOPrIaTkOl+WCpxBKCnZ5PSTWjVy4EU4t9TJDnrIvEZ+i98TDImjAPlb5gSqzzK8xhhTGU/ihVVjK1UMX4GUMyQuimPAt92D4Bof4tnmmcsNCmerR0lbM0MdKqYubAI0Bq6LxNTXZFBE0mn/oUQ2AWmOwEH1kNHrtPV1voElehKfxR3ryxIRxoA3M2Q5PxGeixYg4rPlpm5EyJLX5FdFi5OmVkNOqPAu+WWNKi9VHsKAKR8S4mPIvo+BKnJtUFIRB43H7Spnq0YPzEq4UjBy+NgATpVYAIfrZGdOmKiHwTF4EIylBIQMRZcshII4VqBoFw6s4wBYKsi8RnoMdKcGtABLfNrwg+xdDRA2ZQfxQ9ToHo1oFIxiI5zyJ3fElABB0JTCnDvE11W8H4masKgZAjgrJLFoAQYxFf8k0EiM6Mh/Ss8hEInw6N/3a5P8rlEtLU5C+XsMC6SGl02e9dnDgi0+NyKVgeq/yKOLFEd5P0X5+VhOlGftzC1JTHleuAJB87m3ArPXXmberp01UuHzm6y/DMF3aJGUF5X+Xfmi4EIrFgnKhbcLH2zlgNXs25ysWa7iO5PwrQH6LDSIguJhnrImsscAsXuSp6qMiFnLE8VvkV0WElupuk0n1e0k+Ow0KKHsXFegqELdZYWHulmelINPolZdqmuq1g/PCFgFsuia+SuCjioMLFGr8guLhfUwwf8bN45+v3Uwzuc0tZimuqPFjWF+6Pzi/FUs2+9P4LbnFsDcp9Z9f9cVl9wb33iFux7PeaHiSuHYgdiB2IHYgdiD/f4R2IB+nwDsSDdHgH4kE6vAPxIB3egfgrQvo1bf+BHlV/Z9h/AxDkPkTew2T75JOX24fJbSLo3unfdbZDcEUqf4HvcRfUM5netRLNfMjQcrzbjFLj3dorvbjh5j3elxX/bW6Vu6f/XE9M8l6jLrQcdRWX7XEbhe+Zlx+m/YnvPZy2wzTGqFfBO+ilOsYyQ6JKNJQCCH83EDP9jIIVMQeiWqvK35KhMCCqSU0GhM/uvvosiST5LG95N89UEP/vMxd5TYo5z3xEBkRikvgpEMzf9TMg7F2CpRyWRYbEVwFhyaqCFbF8RXwNEC4BgZZ9BlCZzUuP4zI3Z894MyVqT4U7yxS6CRCTvmZA0AClxzVKToKqTLhjspenbwo0LhInInEwknIFau3NdH6qvXE01gMTMuEYdSiM/VFyPYQVYTHleheJM8Fo+WDtMeliXFKrSf3iqBGhjgvbJz/lGnurbYO7clUzExDBEhD9MhC+3wKCjA6qeoDD4k6ZIgc9Z6j5H3pIzndmJKB6FeBueM3xdqppscKQ4CPnBgR9VkrtjY6eLJaiDL5Ig871M5hax1EWm+apcWSokiaGbELG2/gd1mp61r+LiVtNtQayik7DW+wt2+bU9AbW0dizIGuClCM9IOA5beT58LC9EHRO+P8ED4CAtWL8Fw4D42oyAtGhNRnNDoyXypG5kvNUpCUBInhlTSBZeq0ieYDOJeQ85Kn4xi0IJuRAuJNrXK5fMdZ+zTQwYEDAVhoX/JJ+RhWPCqrv4bTOsZZQqSYG+RxCWUj8jsgFydQv5BjWYdl08lNMFMIpl8KAMG2Do8lFKPfkyLy1bBUTOCEA6IQcXH5FjHXrGpEEYUwNhO+oNcK15khvDOkfWRPsWgKCifTgpkC4YRkIDYk+K0OpvVFoYMifga2QHTLVz8BADF08SplyrBP+KxptR4LLm0/8Dms1V78Yj++DK4GQMjkQtjjZFYF+jUcdrW5nV7psRYRydL8EBPhMzq6IJSACr1NMS/RpChc3mMeJdLurXOQidPnUlDgRxtvMlSvsG1ZY1t6gJ0uampyRsYz9UXIqGMU4v3ZG52JZG+ZKNTFAIegUCGVnWKu5+oXM5FpHNjUldY5KuRQGBNcIsDFcuGRAQN0FWzv91hQyIHy2Rvi0RnQRCNEo8VG7Q4/gYPX4Q04CVCYup8jioHOJLdaJEwEliYlyhfAe8P5qDIeclSHMC7IdyGwg48HYFXP9DGkZmhWXSmtjWf0Kgagq5WpIbdDFUH6HtZrULxCr0zrA4TgqPyWqc/QGhNQtR4EdSKOrIckBCTNP7a8QYaWyE0Its98R4kKJNQK/IySK4xBdxMJJmSuqyDHg2pEoAzkxt37cbXw25YrP/7ae/g5d+xq6zI+YldqsY9qfsu1w83vyvT1abv+f668TpfiC171A3MePeA8QbguIT/Uo3IjvrwDi72/70R23dyB2IHYgdiB2IHYgdiB2IHYgdiAepEc7EA/So18clNTyON37K8/4xweieuf2XxJJozyDnG0giRgkdmxbSPvssd9Q3O/xsRjvy9p7B79s8a9ebjVV39i7+f08OFf//HnKSAZV79ySYkjIAsodUrK/S/d6+oWBUZ0OeT5cWBslMyVFEmLp4O69jxSWtE6MneFjiieRAeKY9Cm2FM78CTHLsaX3vL/pfV5D3mrqWmXv+tl5CC93yS2n7jPvNFeWyYHwfgkIvypcMdHd0J198r7Pjn8fEFNLkpzCEFutJmoRy0A0S0C4KRBVBkSVA1HUkLf6biA0lRf8HAg3BcK9D4jl0Rs6JqRmQFgknwTCpoghASFJoMRkCJLpUrUNSQxeB0lucDtZCCHjZegZ32OI8vfIVuF55VgDa4z6GsrUqJQFQqkeqZdaH4gG6fgqU6+QyJQHEZ1KGtXLMD0MfRz3wtro0pIxTC5N8RAzdTcaZ+wPS9fgaXIM3HGRd5J7wBiL5TgU/JHCi+WkfYosF7SK8xczOVgJLsvbnvou07oI/RsSnbZd9kiWyXgZEtIJK8FpKN5DBuI05DWwRupkWF3R5qA3IDrfS06U0QAI0TFI6hWdt7SnOZUIL6NTLRD6tQTjjwS6tPicYRKcPoDvnNO0qZwGXaE3Qn2FZjA2y5Jvi4seMA65beFteE0bdzMvFu9cFrm1Cl+SfCpNTIbIYUOZxsVhTEwer1loWcDjVXB0xft4RaQamthqYjQYqevkDAgRh/BgaORXhLE9eGS08oguLZY355AxecnajBESuR6hUW0QBcJxSJICAveBD8IR8ckdJuedkEVC7slJ1UaQYQ9svfRisT5ZOQUiuDkQ3QSIAKZBAqKbARHyNaLP36c1ItUgehamk9FNgBiPIDtCgcA5la0Rie2hV4SJUrglIILxR8jVM70Mujn0ZIrkQDR9NdEb4WJtQAS3xDsJpklCXpdT3Q6hLfCKK7xYvMlAFK2qqIesDt7Z1GRrRKY70YMpBDoE1474X7CK40IU8vcZEKkGz1arjNFQRQcWDrsM3eUifJJxls2ASOoVXCNMecOgNEoZCWdpauJUo1I+xrkAU6TKrwiXAaFTU/rW1C3zTuj/0ukxIel2+H7Ji2U8vsumJm0VC4lXAhctxHImQ9SdCHBCuUBXIvIWuBQmXoaLKLv8Wxcu47wGsjGkvcRoEB0M5TYE7LkQ5PFv8Tsi6z442+RxB3Vp0S8XyupIizW3MMrc3QUeKz0Xa68uLcHneiMU/OIacVzknUjN5gFjShzU7aii50ruxVJlXJLUavGlr5p+BVz9OfK+19qv0Px7/7rWZMhqKQ0yItQfegW/0Ba23eOrsv3LOnwokndX875mtoO+Xdc2EB/XdVk68v7avhKI36BNc0/Q76llv+m3A/FL+rQD8SB92oF4kD7tQDxIn3YgHqRPOxAP0qcdiAfp0w7Eg/TptzT/laHeV1dZ6iPH/I7efcm47ED8diCUZeDL+ybbuhXwpAbfQdUo7ta46MdPvE/JZ/d4D1S2/XOlrrxzc2ULxhi5GsG6SIbIYof9yrBMtmf3Q/UVMrEFiZjHlVyM6THWc+YpU4vp7nSvqaDpkdmrKoCY+KOs6FZUKTPhI+PiLo2L3jNnZFk33q33Qd8tKFukGCNXo7ehJ0OEn5aZGeltAqJM8Cdygi+AYIoi1T6/d1sek7gsVRzDlK9RIMLGPeRVIDZ0K+C6wCNDZFzcpXHRe8vuMndVueyTX1a2yCk8BkRjQLj1K6Jy9gD9EhDc67Nb09tAuOyo2TEuB8JNgGAeEm9lNjjiosg5B0dKpMekyjHKit/SrYAcP5tksv9ujQsB4lgAcZwAUSpbSKTG4bD0j+TwRNXCGCIjLIvMDDyw3Juny3iMMjzYR+71lhqKyh9IORnvA0BAal5UAwY1KuhnxziW14eMNU2EcVE4Zcz5qDDOvFKzAlwEDgDSjKoC4FnJlm6FyPFTpcKS/fdqXMCJ4sL3mrC8kIOBJPtM2cJlHA7jahCIxBAREYYlZgZz6ao/AiDA+fAp5StAoNZM+SP4VIpAdFmCl0fNjnEsjye6oxUCgSDLhbl0anKoyEOuWdHoIDXX5E4iXiK3dCuQylcJBXI37tW4ELcV0gBE4ULULkCHuaYrolS2aDIOh3E1BIgmY4gAiBVmRqPMCgIBDsbV+sip6ThR/uB/yvvoWXsplNHMj7mqDkkhqZFfEb6XvdDkiGobUbOi85n9CjvQ0Uvklm4Fr6IExP0aF2+prT4t1vnUVCpb5ByOyNWQFi+JIWJALDEzpFwCovOmGOIG7vWIM1f+MAYWSpVXhCr0hMv8GFUUcXnJfI0Az0Mm7N7p/JZpVogORkNDoo5TU3PxymTY1q1Il6l3rn+PxgW9WajWUanaxXyNSMoWwu0wDkfkavR2dpOXEq+ICTPDyDpQ9Oi5RkhPfeK4BnNGScof1AdhKdc3jEgnHBkt1Dk9xmp1qWTT59+alOdB6xVK/yTNCtGQcF6VOLBYO/ijNDd1K06DceLGC/J9GhfKRFK/kUviUmAdmSlbCLfDOByRqzH+jsBirLwUWayXmBkgll6UWSGlLlK3MjriXnJUkvIH9UFY6tQ3mBvsV4/zPGp2jJYvSha/I/iVyPmQk4rjt2E35RUkvsN7dCu+UuNiLqhgHI6ccVFSC+zTlJlRMkSkVJh/bS/6rSZXk+NCVq5aOWb6Uve5+17BTXkF6dN7dCu+UuNiDYgyzuVbHLddTdZiK5U/wmpEeQvbaiH6E+Dzr/1e0+d7twOxA7EDsQOxA7EDsQPxiWN2IHYgdiB+/UjsQPxbgfidFJLHIs88WPR/EohlVYyvcHN5SCCSwkVfyCcE+edqzIXEpJjwKOT5MNsVFqr3K++zurij6kvljfhA/bTFfrx3Sx/tDX6HLwKq+qAqzLG0vpeUkjBLrGz0lMnYG9MRmPUnMMtxcvPRmr7iEOVPkem4B/tsQHAfdcAtYZ6YFBMeBXzM9SztbX+1CsRUCSNLmISJ8oZba1GSssFPtT3KUpoJsVENQZOWszGUofdQiU4R5Q4mvdWu6iUTIJCQwsPJGn0xWtXs3LOICtGLjKuQ8wu0waoEYtrhbGhjlf3tK2IuSRJb96XyxnqLAEKjqoo6UqkSCD6PugzEUaktIdWyDIRbAsIpEFUJxGL3Z0DMuQpkR5DTYfsQgtBVChWNKY+CQDRO+Q2r6hYlb0K25IwIKm2UyhvrLcKRRBVFml4eGF4o1ZuCBmUnDAjza7EBouqHpHfIFEFEF21HgchH4AR1DXWHuYhHC2Q0+kZVElJZqIq408RlxnxjoKww5yqQHcFkoloZALwmJszBS1vgUVDPA+ymaAqwpG5R8iZkS86IMCBy5Q2ebkstwghjYFScShZK9VTQcGaXoMQWr2wqA4KqHyFyrBCRWiIYELmOiBxKnwqHh9l5VUpEjD6V7aPtg7nMmM4JrSDGmkveQVK4EH6B+kXggtMrInIklngUTqcHSb9WG+oWc95EwYjIrojcE225RVzWPimKLJbqmWJtrqri4VzU6gj5FGkngzqf8JP5yugVkUYAqhp0bpFWExAkCOSj9abWJ8llhtodMkYNj5lzFciOIKfDgIhrRFLRWOJRKBAj4oPrNzgUM95EzojI14jcimu5RUxdPimKLJWKZM94Rbio1dGtAhFc/imuEXEEHGQeonOLAlEs1rGsAYH+D3rNqduMg8SFm3MVyI4gp8NcVuxbU6aiscSjUCDgidUvcyjAGSl4E/T1yhkRVfzWZDILdERZapHfmiyq5bgABL1dzDkrWuFwjah00KUWH4xRkqYmnSL7UkfEY0KCc0unUxMEXJpeT6NYVlRFkqOK1GgaJGSFVG7iUpIULsjpUPeQwX5HZCoaSzwKufiEM+Gg3rHMoXDkYGS8CX5xjDwHqd1+R0SflWGFueH4OyJGtRhXVOCAEgh5JObRElA7juxZi3grZsz0C31l5L9TX+qIVIM5t0hvxKOlAmOv0d8Rqax8FZO9yWUmRC+bJrH88t8VWwoX6beiueuUPIp/rulrWV5nzqEI8xLFZyPsLv02XWJuzH+J3Co1/wrpXDXhdS/VvKYWEmbaHWES/ZILTZg3MuUqBLfGfJjyMtZKhsXa10qUn638OhC3lCvep9ER3GdfYWWkvlKtYxOIX/vab/rdqH4HYgdiByKvfgdiB2IHIq9+B2IHYgcir34HYgfi3w7E/IDHHITPRvTe438tHWcH4i8AwqctOYMi04lYY2HkXA/9nLmZhKVWeUfUPoXs36Y4JJW5rQPSe5oi/HOVO6iShPnnKttNz2ORT+GLEMC6wKaCs9K4lZ5PgQg8JnFZ/Mpxm0BYUrJkUAS/NviV5s9MWCEHouBZZK8q+99PrDWCliisNvwUiDVtDwHC8gjIKbj0nCufTPV+YUQi+8Li835ClfBzykKVi2LkNfY8JlEoqmxI1s4C9VMpr4gMiMK7ZPkqSEBMr4iCZ7HwqlxudbIBRH7C3ND2GDrLrKnG/xQItwwE2RdJx2POWZlxRz4ERFV41lRlJHzSOGdsNMPUu6RR1xPInU9YCyeTQV91MyGfIzqfGK+jZ5nc48TcW07ahtSl9Qy3dEBkwOVp7W0gzN9kiX2R6XjIcBaclUoTQvkR4+xFZxWtkUyUHo8kS2xX42c4LcH0kgnJayJKIpFxZwK0YGxMGRSnlNST1OSEtUA5/i03EzIrSv+QTh1YSo+TTp0avGnTjLtPeAr5tg6I1PXmT0pPgSVIfwKoSc+DOh5r7ItMx6OfclYqZC95KdgR1EvAU6l0i0Dp3tuZr7IXPZcKpp15YliGnjyWN6U6AIicsdG4kkFxVDWJpIKRsxaMCbHuZlIps2LiWtJzhi88TiSJeTXaSQOuF/xHwH64oe2BLgVd68aWr7wiji6/IszfZIl9kel42BURWRjShyZySRIQDaamRnsipcdtVwNC+RkuFx5qXCJRNNcIBGe5krHhhol3SU8uhE/yPhlrwbtcNGvJzUTFgLrStcSW2tzjxMQSNOiBQNg0c0Pbw2GxViAyj5V+CsQa+yLpeMw5K6hhMH+kBATGSmukSsebne3YG9eIISpADaAjuHidGBCyRpSMDaOyRKMKlbfytJHhghhZCz5OQGtuJmMXOxpr5LyOAogkiYIJMrIdQhIXuaUDkoDgwI9HcQDDFIg19kXS8TAgSnYIp9HsCJ2abNQoIDSuU12amsjpsDaNpxJZXOCx6NTEiShnbMgCVHqXcLn0zrxJctZCZWyKVTcT8VIJ+gsg8Tp4JZQeJ2R6SK1c2oV/4Ul6vKkDIqwIZVQM7K7ZPyU9D52aVtgXScfDfkckFgbZHPhFkY7AwjyWIp8DXysEd+nBSfdyhK1NLtAOLl5knMhxPXRPXPhNv6y3NSpuv96nA7L5eynWsM3zWGJh5EfwvWnNTF9T3kr6ympE7+pWgL8GiG2NivcPZPjk8bdr+FW3OFb7v99r+r1AvKOqHYgdiB2IHYgdiB2IHYgdiB2I+WsH4jPldyD+fUC8J7DHJLj8yah+9+mxA/E7gMDdj8mNs8IFxGf6Epov6n1iK+gjutnR5EuEbK/VNH8flvbFbcbpaFzBDHEL/A4/jSJ/Nc5NVTFC1UsWT9Q5xN0FXJB+etxpdmOQtTRxVD7LLcmiTrYtpaJv/OSzR1YzIHL1jHIIvE+UgCUGxBr3w8UjcvmG4ArWh0Y643dom2s6NpVGlRL7ECa3ZziZAwnWfuntnetAGMsjjsonuSXaSqWP3zpfBj0FoppwO24A4daB2OJ+zC+XAgg3q8qAiJoda1dENRlO55KNiLm7LN2iXwDC6jMgPskt0VYkQWFuIcKxOGoCw2TbmaxJ+hLG7UhsBZYTzgWZC07TPcbNWGJArHE/VLEjaoNQ4OE4mPR7TDa5JupzMKPIqE7kiQxO67X/EAWOH1vWKAtjBsnQdTEJ5vg/NUSMpXGC5wxZJA2f5O6/gluiXjFVfHSbHIsO2hzGflA7pi7pSxi3403ZCiyHLvWaHoSHSOJmLDEg1rgfSJpm2iBV5IBkfJKOZ54pgjBdaQwP0+HomMT1NtgWq1EPbDjI/wjiB2BmCtFuihoixvuglQH1RgK1Dfqv4JaYV4yzJLYwOSiHItuO4CQ0YlB2NTOXnNuR2AqG9Ylbtb7EzVhiQGxxP3JtEK8cEImlUOBwuSJIrtkBd6TYpwC1j8bWCMc+vmlcUtfR6RWhrEY6nDTRiMZF3ocA0eiQSzw2Kp/llphXTBY0gWjwzUHYEUC+NzWLktuR2ArxouuVuTDhZiwxILa4H4U2iJWwRTAqLZkiiFwhuWYHYh1wRdhUMLjBgAg2YGmN6EsgApf7IQcisjSCGzjkiQDxeW6JesXo1ESWgg5pldgPHaempC9hlILEVohTU0hTU87NWGJAbHI/Mm2QHkd3Phc2CZ2cKgoE+CGZZkdUpjkOyRnIpiavFDdGCSDg5+JJGAnGWKlin1zG+/DRXMemJn6F+Sy3RL1iyCwgV8KrJ4ot1qdBXUIuSV8ikWyMrWACWLQxS4u1cTOWGBDb3I+kDdLjaJO8ssW6wXqBqQn8kBRVpVZtjlS5bLE+kY8x1iscEHzrD/I9HiD18g1f4D311v5pMEUO42BUKvxlTBSMyhdwS9QrZs4mCH5JlyL8tl/Wt/keYVJ+KargfsfrK7glq5GusQx+FxDv5XuQCPxngPgKbkn4SJP7vaavbnu/6bcDsQOxA7EDsQOxA7EDsQOxA7EDsQPxQLEvV73d4K8KZ/3kudWiHfn3CrPsQHz0FVkJ6VXergr8E1Uq5rqq1N5gRumaQtcDcSQfg11rI9vrszTx5use7slY7xgPfWKk2uDnQBS9CdY07zWbnnEoW85HT91ZxlTRQMYH7jPL/dmrmjTENqXMxu0on/E3ltQmpioVJpmeMSICtg2570quhiJHytZtRYvKtvd3KFe4+7gnmlUMedRTIIrtvdWnVAl9BDIULed3p5uYQLKYvWXB/VRp2lGSfcIpqpa6t6Q2MVWpcPGRvByqMZ0TqnUg3BIQbhOILeWKe7knBCJEuLoFIApKzzIQk9M4TxIcDQiZDzLAjRdSGAgFGaVVkR8qVKyrTcxUKlQ1/ghGREP/kiDEIHti/zqYOkVyIOFQbylaVJr2qe5QrriXe2Kq9SCKjdEdfaPcEkZp2yWBw75ia28ZembpA9glYzrJHU1dRHVDNP/Ip8nVcSUyhnoDgiUbmPDIxWXOKhLLCdFjVqdCxbraxFSl4jRQs6bT1CqywwGVBWpYvHmqTNiz/XKknvMbihY4n5MiyKZyxb3cE5FxsCsCUV+C6oJIlGl7A7ZFsDQqgICqwCBANEMHohjbcc5cY1Tg4sJnw4+DavEPqDNUg60RMVk7cMiThgPHkLXq5nW1iZlKRfRR8Imhg+S2XRFv8C4JqnzBI/Wc31C0qNRppLpPueIu7km6IjTqznxdEGWajkBrUC0NN70iKMvy5q0dc1ohO8QIJJ3SD2jfVvVpjWiMuOk55D4yPOIY5kCsqU1MVSpsjegiEJRPIOWl0nPNgLAj4zm/qmgRCV/9HcoV7j7uSVojGF3ydcF1O9h2AhGgpeGG6RqRA0HiAkkWZIcYECEDgizMuEbYty49CW4BsaI2MVWpsOXoOHjjOpBDGnwCosv1NQjELUULZTl08GK5oVxxL/dk/q0pqItLmprSiSF95QRlnJX4rQmMvSoShKpswjF5sZAxWIIOc1VMTRwrlzureO8nQKyrTcxUKvSb99h14zp48Bvi74jeU2VCFTVwpGhWbCtakIY5LobhtnLFvdyT+e8IXcgdohxSbxpyO4L8mzgr8XeEHBdOkZeiLeK9tOuivJfSTa90bSn24CsP+MZxsRaGIeq6zL6rz9Um1l5hZfvaL9n3KlpsKVfcyz1Z+20cbtpsrP2yfp+SSLXy/ktf4Z1AbNW1vvde5Y51D5ivvcXxUSWRP/Da7zXtQOxA7EDsQOxA7EDsQOxA7EDsQOxA7ED8oaHbavprwnqEsyyP4f3x2BFfPB47EA8BBPQg6vE/vFrd/POH/PukRZ/w7ukHS768YmM9r7Kdb6q5tV0ahNo2y5+6nR43e5fe19OW0oanyfaDe/nx9Poy9ucwNvTy4+VH3dYOPSl6rAoHwx0jFxIQ6X60vcyBBmknvdWe36HK3vP+fOS9CBAtwxI8fv6UTh2w66BHHPDupSUQLbtQH1aAOKwCcZgCcU542FDW2Z/6BhCHBSAOk4JP8g92j+9epaCAgz7HHicgksLH9DZrLh6RgEgZmoJd4pSjEsxJPh09YXVkz+FJtWcL63ALiNa1BsQXXBHrQLSLQFg17foVMQXim5TG7m/uINEbEOfU4wwI9xEgcqJNAUTf6dPjy0BkvBfhLPz8OQZ/fnpt23HSOf/8ycv46fXpVTrFvxGIszu3tWx7+v7kvo3/jh0cj/zx+u1V3n2TPU7eSxnbj3djqXGKG9/9/IlS474D9r2M26RNlnrBPoln3Cr1fJfPP1AHt8revCVs4bSEWJ+0lm82IZ3bZyl3ELjO8k56KeWsx86pIoZyWo6RbXKMbJSTqntchypfI3pCA9GkmJySNFIFsYgjUlXBpaPJS+FDzZblJz7jGzkvxrNGTvXWrojxMyafcWNNIJ5aCX+cc19lW/t8cM+1nJry7//q51re1Sgt7+X4cc8z9svesZTUWtdjC3WqwY2HPOMEbnF0rS3LMbW09SxTy/9QB49v0UJqqbbJMh7Zopa61qt5hPx5XNnGPe3hFW2dpc+px3BzgSIGOS2dsk3I+TA9DqQ4kawtgSCvxVK4qhgSVGHhIkdK3s6OtjrBMwnkvTinwhwHF8OKV0QEYlykCcRBQBpLfpPl74d2uUX32/9hMGQApLS8fxpLtbpV3o1HEAiZ/LIann7UjgMpC5V8GvfJO5R1KPHyQ+pH7ZjI6qKlOvtS8fJDFmcroVNVzS8jB8xLrrYrIvWYbhU2nztlhzCvTPWPRhVExItl+YqwSekI/Q8CIflz1OTT0VanaZzYFaFrRKthpTUiAdHWCYhWPrM0h/GcXxE8Ezk87pWlABTP0nMCQvedn/XsFfBt+Foc616fFQg5nkDIVhxbtFTLIszXeNwB+1mTTk0RiPGzvkOfY495ReRABBeviOjD4lXkZHmNMCCESeIGAkHBIANCJVK0TtM4UViV/dOeCURdz4HAFGJAnGVdl8XvWbbqIvgqQMjcK0PH7TqAcWqSdwUQqEFm7HG7Ti0YXHxqa4nn26sgIeU4NXGrHHvOWuLVPF4N52JqqtMVMe7FqX/gsm1AtKnHXCO6HAiyTfxxiOwM0nc6AkFhovJbk7JLQpyaQLtUv5V4tNUpn+xbU4VFCL8jzg6L56t9q7YlDwvg9xe8i0DUWKq/y1ohy+Q4eX0/KyTjNIHtXDjH/bpYyztZXZ5eCQRrkLkCrdZcamt8kiMlnvHs5mI9LtfnWuo4s8y4N7X0dJYt/P3wFBf7A1qSFz6fpcTYhxZfBPiVtc57rIoYEQhlmwzBGRulohXbpffyOwI8kj7/HWHsEhXfAvuPHJRqyI+2OqFAor8jyMr61C/rtrbvlK2795XXsnXUfTUe3lV6KQYCMX8JwyTEd+XRdkWU26u7uBrBzWv7NBC8Iv4kEB973QPE0qCVR3/sFkdYi2W/1/TRo/ebfjsQXz0IOxA7EDsQOxA7EDsQX1/LDsSXDsIOxA7E3wLE4zJ7Hpdr9MnIHheI5Sdufk9E1R1bvhyITF3imoA4Lbio2Css1iRbmxs6GmH2r7w59fIMnj6Hm7xR+ngYkyjq9Z4cVNZfzdLGwnUlFF4wib+h+/vZ0X22fzoAub+K3x6leUQRiFxsIc8+LbmopBu/flZXcPrAq1tDwy8D0YsAurmh9L6aWHKkOudAVBONDmunWul2cl0JhQWJ92WE076VvSQouYBDNYmsWtR3qQrGhx13LxCZi4r7VUD4KiQgFrxRSiBCLpCxaE60CYRbAsJ9FohqEtmy0I53m1dEo2yFMg04dVE5Qj+DjiWUQ9dHXAenPibqNVKwHpBgCazffFbMSYVb8dS0sCh6AtGgHCYYcigcH4Dl0csRNSqozsQM7TaMh8E9RxWGT64r5gWjTilFhDmXg6oZjIfeMZXuFe6HMTL4dHZ0nNGa5LFgiwNKDFmseNDZWXsY7qBshSa/Ioapi4rnpBF5DtH1QZViqJtBk5jEW7DUPJV0qIWhjic9t/Kh707XAWoSdPrcfqeP4/P0KR1U+iyiyiwGgotP9AujwlQzIMDQla4r5gXDJ83LCK2PZGV0ceai50SF3iEXaoyMvnJ5ZJXWRJEa0wCpYlqVIyaP5XtTBTEg6KowvyKSi4pXXxLT4qAeBQeiuZL9oGdixnogEORFmM+KKmk4bq2MRZEBYQIKaZDt6OWI6KRyEpOxqwlD6PP92E/3ldJ1xbxgrBd5hInL0cB7hWMldZ90rj9dqQWUGBl5ZJX5XYTEB0lANM5GzOKfXBFLa0RyUfFxCWLqO6gQVLK8CDZoOetButgHl/us6PWkW2WNOF3VGGYCRDcBIrjliOikMrbfR4WOzoDQiDMg6PwSvWCGEghdwZTLIV8jDAjzjpGFLDgDIhT+KmEFiJAv1oONmMZvXIQAdoFxCjK7s8JFxTu7kDrfXExvQ7UlOlPe0KkpsR4IROGzotYzQTso35pCp+oBonrho6SIriGcmjw9hZYiSlOTiQcl+ybGF6Kh1GRqKhiqFmHO5bB3WktQILz3CkR0nUmRVVoTu5gUOXyKtdOpST6Ri6CeKNLSNTEUTjMXFZkxK11sna9MPIrL0IXshxONzXLWg1qk5T4rR+wNXoWm8DsC23ppR1oxHnVwVApx8ejliOwrQ/BU/BgXRZ8Wb7qvQPYquq4coxeMLdZ5hDmXg6oZiOdCVgfGDpoh3uWuMyky9gFSYPFLBDy7UqwyThc6vRRchBAnKtu69cv6faoU976WvFzmHIr76vo1EW6091W3OELx6RYQv0uVIqzGe+v1+3Uz/rX3mn5Jd3cgdiB2IHYgdiB2IHYgdiB2IHYgdiB2IPbI/nRAX9/Ke2v8uODKl7yqhXdrTVUrR+5ALA/tUHz0m5oUwQT53RLTYcpm6At2BMo3k+rKlvO/eRRhqUPe+XRPCcke0Smml7QXqfbx/2G8s6tb/8FnuYkoW09X6ir/c5VcyPivJsWCu0uTwydNDns1OG5hoFN/ArMWk+MGLeN9NTlsrkkR/U/6+X3NLTaD14dYc2OPojeziqrk9uGXUvoWiQGRi8dbmsaSplAJDy4mb3ywh2rF86L3mkFhYicDYtr/RYOXmM3MxsgTiJK5kbmXDAbEzM7DuyVmwRJ7IQHR3QYishn87Jy+BYTLAt/iVmgr2TBo0jUmSfl4reUOQ7RZsHJDZ0B0UyDcfUA0CyfrFAgXSTN0VMmBqBzTyi6a2CuXwLgOXt9xf86WkEOTM4lbYDO4jPkR61aOhCWJmsgAOWb+IkwpNY6m9hRgL7kVeSTaClq4wtnF6SPmEYh+G4jLxYA4KhBrmhwlJ4WxwhKhv0aehrrOQNi9KC/9kXmEvjMOrSgHBtv7KLMv5BNJ7ElWS7gO1IP3MZVZsiVyZxK3wGZwGZuBdTMpabyMKktgGmdCvVM63d+R5XAamJBM5fJI2Aqnpjff0fMlyOwvPWEOnEwO6c+ph02PaPQ7km1O/ZunB8t4xIVArGlyTDgpiJVAvGVZaUQAx4q8fIWEqDPfGYdWlAOD7QBCpybjEuRcBxle8hhKtkTuTOJusBmc8ikquKPo5akuI8adQLtgSpD/Udm2q9faU7k8ErZiQCj1oD+6kOlpnK5HfDpiDqvwOV0Rb7pyBA4V3FeWNDlKTorFWoGsakBE1xlszctHcQj6zgCoxIHRqSkHIriS6yAZ5FDQVkJcrJPlzDabIQHhhhwIuIwMGWdiCOZfNVQZ08O7kv2RR8JW8isCne5loon8DWUtQSGD12KfgOgTED5dEXNNjpKTEmMV4C4GhHnHkBGWl09AmK1OzoGR+UU1JiIQOdfBmT2N7M/ZEhkQkeJSshmqLImegEjciGJqypxXwmRqkmC9L7kVKRJrxYDgGgF7l7FrGRAB35pCsJZD8a1pAsSaJkfJSdFYBYjwlvE0ZBQbXFd5+TQ1ySA1gNs4MNyuGhOYXsgliFwHchqwAA0lW4JGLk3kOiywGfoSiJMuTcbL0GVKGSCsR9kUStbUxVoGfSi5FSkSa4VHyJDK/BxowzQkiR9ZC5xuTYu1/Y4I+PLA9eQEDteyJkfJSdFYRZPjQv+Y5B3TYLHOy9PMjQyQEQgs1saBidu3Xx/jQVS/5RbHtJWw8n36Pa/5Ty15lSyS/NM9bi0sX032ZzaB97w+yoP4E/eawpfUGD4Rwb29hzPr8DtGaL/p9wCjswOxA7EDsQOxA7EDsQOxA7ED8QeHaAdiB+L3AvGeEP6UbMpXxfhnIrrz6B2IXx3RzaMbsBzSxuIuiM/0N3j3ExVMdSv4b+Pu8Rgp6y8dRzSHoXogTc6lAGdC820976bK7TRwMAZha/C+KOTP8/5s3P5TNklYdT3xFnlSKMljV/pCHAlyNMoSxUiEUOzK6tSn8gogQsaLkCxeZTVqwgiDPWFm8F+mhO7zGKmyQfDx4V3vco+RKuNSMFUvuYTEyqDfNVM8XhO/QfdPgAiT0YnxOkdy0KLriU+RLzMzCIQ14TXRMwfCaBfpfZUBUc2BqCIQ+uqHzhVAVNlZr+fBIhDrHiNl/Z3PNSsqV5VAuAkQfRWVC6xl421IpAF8jODXroiqqLOaxDDjm2wA4RaAEI5G9QEgnOazFAgwE3o+2XxS1kU1ZsQaewz2gmeeewKRmBlyDBkLWnnPRtc8Rsr6zXFE1S56c1RInIicMyH55iOG5zgD4nLhYBwVCDsO08ygn8ZoyAAR/Y1K/11wPVEOC1NdFtGUmYFWLkx0QZ1kRA6JHi3HthNjo8oVTOKYa10QBOmZR2XWl7oTlf6VJ+j14fQOPAxc9jkzQ47pYtrvDo+Ron5zHFG1i2AeI4kTkXMmOjnugtzZ5YTcM/Jr4G28cY3ouV8T9F75IIG1VCklHJDBBt1s7nrCeITNwvRts8jMQCudY655HIPjwDSs6X+w7ZyxkSmYxDHXutIVUTxvHxlAbxhur7yFxNGYMDMmQLgtj5GifnMcUbWLdEVETkTOmTi6lM9WkZ9xuzA0TuBLCG2I+1lD1NUIjctz7LgiRDUkQDtk5nriNMl6dPRTYURzZoaMmnBNqKpBjoZjvFF9IzE2qlzBJBtz1JUDkfRgbKBc3ysQwdk5lQPx//auJrdxXenShAMQGrmBZE54ZHgVeUAvIIP0foiMvAwNg7fKT1WniixSlO2kO31zv0fd27Et8afII5KS6uhUZmYYIG7HGLHla8QRqF2YNSJzIirORFISgi7WfF4nSpEUCF0j5lg4IiTLVQFBkhQgEPSinmg5XPJJLVozM5QYo0QDEVPRiwbhomTGRirfhetSlEYABJgH4EHQZ1DWRRQgZKBBpKEAod2vU9OU7okxYsvXiCNRoo7oVZNyInzDmeheNUUFQq+a0B7uIjM1wRpwURYgTl5a34t6IlNLFNkI12NmcC2npWt5+uKLBJmaYjU1ZdWSZNkb2ucyNfFik8A8iA5LDV0RC+siJHDhZOmJrINHQBRmRlmsaQm+HWOkLl8jjkDtotxHZE5EspyJ+j5C1wiwNex9xCSLdRCGBK2EsHVKXoijZA94Fb2oJ6iTmSNBLVoxM7BYXzwv6EwJlcVaW9yolsTyXdke0k+XfLmGYBa+vehutujWLAVfHb8nxsh2+W51z+m7e5VJscWvqO3477veSak1rbrH1S3UdZf8sWNR21qOymJFKYNr+ryxBTtvdVR0a6O2utDdKOU+ID5adj+txPD53BZ+16KNSKVVn/+BRn6+nL8HxHew6M7cA4ivtmgAMYAYQAwgBhADiAHEF9jxJy0aQAwgBhBfYOPvVNnmxe+v6qp7yr2d5k/Jwfxxys8A4m/3W1RaQNnC7QI7uWzemvVB+sphXaLKaF/nU5QjpY4olAYnr8FvK2mgghUng5+CxhiszWdXRZr5dL9N9iexWNy6JT1707pAdXRnPsfNXCWlOJIq1gd58do34Phd9nibT1HKL891Q1SXlL4GXwrwXTtRt60luiL1op5G+FZ6Yhi3e6CUU1gwKGT9PJrt9R8AwlXdeytXBURp8lUgbvMpaqvWQHT5FncCccpA+D8EhO8CsW5JcE5ZB8t4ncGr8A58DH3pVtU1luPC5wBzYzuXTblmfchZkvBKH/moNAKKu4NPIWwMKHqQ9xevHCfxlodJvMsmFssMpw2ivahP3CqIOHn7OiuMJHgaEWkGWiQaU0V6BCoNN/sN5YiexwV7fdY9QRnCVlHWAb3T74TnIK9h66vdoq5R+BwhXM9lUyJvrcdB/l8AQVI+cLOGrPFxnU8Bt6bXIR5jfgmfgYjoPrA/SuSJE5ggwQvNhybLoiDiRI8gK4xgvM4YY9AicRJTRV/Ld3f1m4aE4Jwn9kiyIMApj/DCVnEatWRp9HtsCgSDogABDkK4katKmdasjzIiiCrJjk7h/t3mU4CbsZT0zkAk0bNIRauG1whXYrFM2n1wrsqIsAoi7HgVfggTAFKJJ6NaJJPEVJmU13F3v2kZ/Mp9TikxVIyCh0GWP5MtkBkUJ8zAb5nPEW7ksil7rI+yRlD8kxML+ASVlrjBpwA3Q2kJXvQs8rJYAYERgdglwqnIa4RVEMGIUEmhWC3WqkUSRYoCPeIkvM89/aZlKLPDO9UaYTCUraK8DdZ3OdGcXgoEg4JntZSBKNPIZq46Zcv6QGyqPGhJOifoJHabT5FZUScli8l5L0srPkO2z0QakkknA5EVRLBGZCCEYSFAiBYJorQ46RE9ge/pNy0DKiewOzZTU16sib9BmhKRr4d10QGDgs5PjhWS+RzhRi6bMqxZH/k+gsqPrIbBihdRtS+u8SnAgDgzJwQ6fRINBddmzN84z8GpfUKwVO2QfB9hFURw0QA2B2K8TBqXMmuRkAWquKEzyX39pmXwYn2B3TFz4pWt0o1R2Fwghi3mxtVc5gK0lWrQO+tbnJHVduX6UUu1tcVNzsat+924cWctJYW2hnv77eYWm8918/vyH+6urrz+iONW7vuA6D3iiHdb1E+xmSr8Xr/91jaeNf39fhtADCAGEAOIAcQAYgAxgBhADCAGEAOIP1rUPy9E8ne3b2TdAOIvbpPoZPCPwjOIDQ+h4j/EnLdSzNgoP7sxcungXFCJ9E6clneuHtA1rI5YqsCT0MaUbMV1/d24to9ZHG09wubYtK761XkZ7LxRm+SLnXKEWdDyDJJ9YRfpits95n2h618vITjgS2tL9xIEgd4VLXbFKnet1IE3uH2usx9gx3cc9KH7a4vFofXIK7jb1tW1rre0UZvp3aacFgiXk/p7gHDXgXANEK4FIvSaCt+uVeooQNQWrCId3QDCbwHhukBsWncfEH4TCK0l6rCcZlTa8gyo+5SHIPFQErM5lKFhooio+gQP86icB2JJ0Eu/zHFoWQyAht+ePs6aKzrNTa/i1kodnsuG+x9AEM9j0v3OKFskGvA05Lv8i1THgVEWR1OeeMC3rYsOUWTQM+BzTEaFw7uN2jifL1olHL2FzkuZLlqegXfFpQn/7MS+a35vXdkPwkEA60J4FjEq5yGeRE/Ar1kMSXU3zinAK724FM9Jc3vnG6UOedk9OYc6aT8CeOhL8FnZIo+RLv8i1nFglMXRlCdAbFtHR4IENqCVTx27TmJQiGjQqjbk80WrJAU5r94FiIZnUJzxOVoE+65JfSJmtoKXQBvIy0oUUZQ1HELjCBAtiyHrbkg0FvAqXMntGqUODi8gQKhw0RvbyfudUbbIQHT5F6mOA6Msjqa8PCI2rRORlyBlUl667Jm4B4oM0ao2zlcElKLqCUUdES3PIBUGnZ5tBIRqczgTRUTVOrjYFF0LhAZhKqV7EU6B7gt4GqHKXVgdsodSiPu/ABFlv2UyYVJAKJw1/0ICfqxYHHV5ZY3YtM4A4TEiaBSwwkEUCLq14YosVUBwp5zK1FTxDFLhIahqBANxsnFQfI7HQnmZNJZyJJIYZRAujWtK93n5l2gsUSg4SUlk3tVKHQSmBSKi4/L+omxRls4e/0JD5fRYHLY80cvYtm4NhHJOnNLeurUZILQcaF0ws6DDM4iFhwDVCLrqRwQRxEcpihlgXWCxxmwL5gJ9cumxLV2ugvg+IjjlT9AZhNyq4lGUOlzUSwPlaUxzYju5zlSULcwVSod/EV0dB0ZZHHV5ykDZtu7MwagQ8cXJhYEu1UypdP3akI/ig025HHPZ1deniI2Tv3+n2S/lPsWLrXtX/8HIpuCab9XZ8i/u2+Inrbs/Es0HWnkPEL+z/ckS460E4e9Y9yXxUsazpm9pygBiADGAGEAMIAYQA4gBxABiADGAGED8KZOUfvKnaCj/r7vy92wbQPxF22L1oVuo1DjiRpFn809jcsR12Rt1xioNfU7ZR39OLG5/o5z6WLSNFWuEbRKuZb2pvrHyysfNVHGrvFADEdfHZVeq9/uOCAS+edvcZP5lIFQAITQMiLbONRBeky7uSTigtoEIuVPqVL4CAi/qu9CzQzkVa50MeD8aIYd8LNhAHc6+PwdrtNRwHxDeFHoLCN87OVogIiJBxJzut4AIPt4LRHdEiDU1Ai0QrguETXsPEO2I8L1W94A45kgjGjGE+AUNHyGV4DdgJYDVcWbNDQ4Fkt/Uxz/OkdgfLkoUKJtl1LPOxmT2gw8BjoQTDolyIfA2tnbVkd03LmtsoMyzxkyhfRctj34Va0QR5NKzwzBXnHJSwAmhMhEB5px5KsccFQWlgMrAdUIQ/kJuoiPzV7g2p4oe/OtCLYAqyZG5HU74BeKWDIj4wa+sV3yEVIWDihrlJICPETVeQ/nnZRRFp0oUQfQnbFwS0Mc0ZlDhSjjhkED2H5yKU5BILXwMsSJg/XmGUoGT+BYIp3HiGAj0q1gDRRAJBNLYYZgrTsMwhKAaIjw1yUv4JWIKwD0FYdgFcDM4SMOJul0VOnRqSgEqO2RBElWSE9pm1ZHgJIfSRcNHqIFIEqoiE1zISV+PCI2fFflM03pYjcXEJZnMfqk961aA1DI5BaIwM+BZdhzFJEDhBS7L9+gstSYwOyWGYo1Go5jc2g5DaXDgs8AS+JWh/hGEBwLFjkn4GYipolZ44XnAg81kmXcFghglkzO+dsTJAPWmBQKqGDUfwa4RYCXUQMTVGiGTMhbPWevhudDGJZnLfsuVKEAwMwiyKaEEUwMQMbMAI7Q7ggKBkFVBfhVrchyXeW0HXPkKRNYHMUAU7oroesxe2yW8KQABnkcBIroCBOq2QJxqIGRqUupJqvkIymcoK43yKvRsnVINhEzKOjXJJCJTgtHZKPsloFSMwQCBaCh01RRPFRAcYeuU4wWdgoR10qh0Ut4JQKg1AsSpZ4cAIcQi0QcpQLA1IRSpIuWhYB+0NjIQnOpyiXZqyooemJpwFGuETE3Q1qDF2iu7ItZ8BOUzlOsR5VUwH4PSNPcR2vRlgIsShRf9iVpno+znoDlaP2tnnDV0Gd9HHOd8HxGEYHlRRjmxSjwivoSzLe9CvywQau/aDuJUSK24yJhhCTREOALMxRdSadDjbP8FjBWe7JjDwvItkXKe5ZJEFT10sdbTd6npwou1vUDtbVlFZenebVZG/FZ31jWH4qPWlACHX70xp+vexIWP8O95xFFzKD5jTXR/Z/sAEPc2aDxr+mu2DSAGEAOIAcQAYgAxgBhADCAGEAOIAcT/SvV9OtA/d9J8NT3p06YMIHjbjD1CW6x/kurGmrmgsU6g5rGOiaIpJs0neh6NhkesY1X/lyX9//tOasc+pVCVuHIRp2D5EnjxeHIaYaWnJVKeFOf3A007y+Y5NAIZTtr8/N2pEgjtl5juNnBITtXbzuQo2C//8XZQyaRXo6MR1m1Mdbu98iBU1sFZI9j50YmJoil8zofKGw2PtBG9nVxVy2P56onq6mxI1duA6vosEVZKN5kRIb6T7LJyRZzCW1DEnTORq0zs0LdUnb4eGUx6SVUbXGQ1lloPAILw+PVrf3But44XUgMxtUA4vzK5AiKtY6JYIHwNhKuASD77r6PxeJNraj5V52wT5obcky5bBgUPBcLfA8QVUe0jn1w+0jcfNQ5Rdl5J1tIHJVXoG0y1Pi9QMBA7C4SyJsBEsNyGpSHpfXFhqJKFz8Ik0MOY8iu45Ojw7BRZx0SBG4W4IeJ3TpgOvNNXdI+s0HGkZjAQxxUQlwu6SlPz0FfeBtnAL+Qyf4IVPMgaCLGstUQUiOJfj8IMUQaIMjpETyNRrBE+cSIUbOSFXmYfoD+i8jOSpDrxa8Zzh++yWLB0+/PDy+Hw9OKef/16emUgzvNJWBPZ75q5DQTEm4TCyqobief86IqSBLsVqensBV7FRMHcj3gqqr5RNDzAByGn5km0NyiuyjlBd57SnFPkyCrO5bAGcKHGKLwNHhHCnxAFDwUirrVE8ogQxglZC2aIMkCU0SFTHAXWiWcG2ov2Pq96dOZnIHJqpLpkN27Ld1mAoJFw2B8Oy/x0MCNCtCjARKi4DXRmKBBZdSOPCJ2UKNYJsXnExb6KiTI5REOpRkTW8IBCBh09qnAK/RXZItq7jC6OrOKcxlOReCRxynEYQmLGhi6dpwxEWmuJrEeEC3Dn6xqVlUOc0NxkL8GtVJzld/K8rsmIEPkrpHJZZ2rFd+ERsVcgyojIuKGlNbchxYsCoaobZY3IShKkOzEDCChuNDFRZkrBI+KU14is4cEKGZBdSBrgIrMpeEzS8aRUPo6nIosrETEzEBEjQroFQNSiLqolsl4jLBBTKtQaidXC4XVw/WSBwKmmQLg55qusbSCirBEHAaJaI3QAhTW3YUGcgahUN8pVk0wEMU9NAdPFKiZKROwRSDP4PDUpvQWBkTaumiKAUJIMk1rM1AQbFiBOZmoqQHS0RLwJb5avmpgZogwQ77yZbKKs5iW0lEzjYNqpnEyemqB9IsJJK74LrpqeAcR+vwZCon7U3IbInxe9aj7PlmOqShJYqIgx4fnCbR0ThXkXtCIEvY8oGh4aMaW9j9A1ApwmZpU4jaeCxZqXZmZe0RJJ9TOfBBax4keJsGK1RJixkpr7CGaGKANE1ULwncqkBrOeiCzAzO8g9Zuo0WV0sZZUNK7yYm35LriPeHYPLw8/dy96H7Grbzbu085oLx3v26K5yCv3lr3cakVrTWxSyTzU2NBatNYSySOie2d9v4pGrw339MY//IgjunK//Wcqp7Punq2vJbIFxJeoaHTt+ZN98UdM+WcaPh76DSAGEAOIq6YMIAYQA4gBxD/TF1dNGUAMIAYQA4i7+uL3jPveIif/qm0A8Q9tD/mTvj28gmdADgvnlG/g3OLYa7bdS51iBcShzbE/rGs/rH7tD21dO61LbF0fr7en14cXd2j37ricnVvvX9p6eHpdl7J7fnrt7X+gUp6XfM9PLw8vTy+751/LPvL0PL0+720rD7nV0sv70hMPbb/sjKFLUQcAccD+vR497DWt9vx+3wKxr4F4xr59VeG+C4Smol+757aundSlnbg+XpdLjhf30u6lE22//FvvX36+oKWVdUuXHlxv/2G/lLQAfTiw03O/l0+q2QKxN0DsKiB2z/b02T1vAbG05ODqbj7sbXdbcP80EE+vbV27nGvXtaUHxO7Q7v2x/HdY/q33L6Pn4Fbplz4gg7v7f1AN+McQ4JNrPtwCgnv5VffIr8VBwWcOPjMQz+TOe3A/FvcF/lLjH/gXDcUfPAz3nIuO0efh8OOFBuqvXw8/lxxLmh3lXPY98a/l6KuT3JT+9YV+US7sfeK99ItGRF3Xjut6fXlgtzsB1R635T69LqXwWVrvpfYd+HRr9y+n8XIqt/uXPtgvJXX3764AIfC+IBemeOpdau1i9cvDs44IbgV+oRA+x/YA4uFA7m2a/3bucX945L8HmpfJvUeV76mVe9mzpHBcyuHwuCen+K9f5AQ87B/3lJPmFNq7X37jXJbc+//sUeLj8olUjnNhjajr2tGqsOTgrtyvbdlV5Tqq91DZynsp948XlNHsX7qIOrzZ/8KteFnvf3gpQFB3Awjq8qdnBQJtf1ROH/XyYvPhEf0NBylawb8yEMsiDSB2RPHgQbVTrBmIHzRl0Fnm0LU0Imgpk/OAKtnTDMxA8PRCOR9e95xuL9MJci/L3Ss16YH6iz451cPrQVK1dfGIeC1ArI/X5S4n1XM5rnsp9yOX0e533CHrUqjr3b63vwBBvUijSkfE/lm7/iG32nFvLu07HPQiyCkQeeZXIA77AsSBfhsgaPgcaLJazhr3wo1/xAyYB6QA4WhqeqYjy7nw/Lg/YERwd7EJyL384ia9AAhJtVcg2rpAQBEgHhcQVsdtuUs9+z13Tr0XMHIrm/1OjralKBDr/TUQdmpSINB27XjpTbuAOO3ZGgiehBSIZ2YF7mQvMqMQHqZ05v906DozNdERnpr4CM2uy/z9uOe9em4gN+X6DwNAU9PyiVSP2ZqmLgbiEUDwerU6viqXF9l6bwGi3X9g89alENVCJr66dBqj5apJQClAUPei7Xp9xykeMTWh82HHwQkUD7yk8OfPJ/5WgKC5j/bS0iiLNS+puxeel5rF+uHnUhkW6xc6Ql1xoE56ob15auLclPp5j1p5sUaqn3QtzqOvqYunpiUHW7jvHW/LpTuDdi8uCJ5e1vvprmPv1qXsnnERs7Ka7x30PuIgawXuI3a4IHZou7T6ma39yf+94P7h4RmXJw9uc1vfG+eZrnMxu84t577r3NHdse3c9bo+Ysvf3n69lkvyQ6dN5Wi5ED98BIiHl/rTflvnxrHPArGuoa7rI7b8k9t9bb9q+3jW9E22AcQAYmwDiAHE2AYQA4ixDSAGEGP7t4Deh/BjwI7TYADxe9vyzGlfGAXlCdT6meDDah8xKx6ult6mODTl0ct8rS4FbaKGwS9vVl0bzJHeFuidOH3XE1s03/ndu+aIeUco5mNhXYd3PkGVo7dVOVexVLYUT7CfUy7e0n1hFBQg5Lft1BUNZb+vnyvuWNTgWoqDZS7wf0xUcKpLIaIYNi4Jqd3zy554j9ffBCKGnMqVQCEqRF+6zGvwkBKfI10DYnL8pvNsoQlNLa4K4VFENayGic0ZQn7rD4yeTwLh2ge8+xspDk15DMQzUu76QMRPABF7QKjUdGzOYgPUVSCOqr7RiSlzEwjXmm2VT7yMiOfCKJBJQ5whD8K/IFYEfLp6lI+97p2m4/0/H8jF4pTD0UlBLAZXlfdMRIWiS4G39zmayiyS5vQiLr1IixdtL94VNQ5ofbxzTBL6/b7s54hCqcRFUU0QFV9H2hx7ZfmM8uoyS5qwqgjKZzn2C71EjJgx/MZ1CAIE4qWcRePjPEsEF86lEWFU3STHcpkRV0VzQmR+gloBOfwKo0DOW6UM7MG/YP/rwZC79gdhcbjsInTsNmR344G9tb0U+wMzQ0x5z0RUKLoUIilA5z29Hn6CuoyOiMiaHS4gKgo0QN5I7SNC1Wb5duKIQsnn2CtBo6Pg9fcZafEKOuLH0HvQTkIU8LGI8jlAwYlf54/l1XdaRtCdgV/Zl6AMEhHGcS7HUVY8a+dTyfTetkfsmuV7yUn7nWqJFIpWAeKBHX6Lix5EGHRhAcI9vD4wdWTPzA98t0AAwnWK5VNYGIe9HRFFl0K0NDh+Ccv+vwenIyKI+sH0ru/5Q2LiLSAlAmX8+uWTN3FRgsRQ0QhPHC6AZYy8KpCQ6kaOnoJux2v2TvQzoPHBSgtJBYneGNogEWCgUEXliCxGmFRr5F1juQSJLVNy8oh4z0AcMhCZUfDCjnmnQBA1xgJBdMaDUGR2nJq7+RlAPDzLWFqlWD55RJTysEYUXQoV2VDBhMgvrJsREbJoRANEdApEcAUIUe4IbrZAQNHDqwIJCxHJGpGBkNgpJ5agiFFgChKopnRndAWImEVaVOKi6HbwrzlUOZEvA/HsCqMA/AMzNe3rESFHHzHZgMhC33k/M1SV+abMpyoFl/LjpZSHq6aiS4EZWKKp8NSUChABUxOmDdW5yEAgRNWJAgGEAoREicFU5CRslE5VmLpijolCsl8ChExNtEaUqQlCIUD0cuHuFHmloCWd4kk0Q05QaEPJ0vUBcVU0JwNxqoBQRgGu+nWZfhD+BbMmmOMhR38iLTErChOBmQ2cjzgWe9dLcSB2nykP9xG1LgUCsS3L5YXDhJX4d6L4sXROKIt1Ip2OCytnkPLfhSayAoRGiWF9kMQ5LlhUg0h7BehosN6Iw0Iu5fOyK98kKkoi9Q295eDoKSauCrRHlv9QM7RJHEqmtMFNEldFc/LUxKohWOKdpfZed3kfruxfsyv6Oesy1nfWrZ7FvffK/kbqe1VD6svhj2eJ7re332NDrNkV94D5zR9xBPe/so1nTQOIsQ0gBhBjG0AMIMY2gBhAjG0AMbaxje3rNo1Oz5ufp7xbN6uPGk3G81wXFLup3MbedZppw0Dyq/33nZ7TntP5vWP/rapWdfRbFG6VusHQmCR9bf8ZTltWR77SPr8BRAjepNBHzKHbwlZJuAXCmwr6QIQtm+qU4iQq0uyuDgyyxiVtlXetRX5VTItGCJt9Gdr6rGvrevtKlSYoSnB+ZZaNmRLN0T4Q62Dd14Hw9wKhuvam9/1VILZPwF6LGonrPhDuPiC8sRra/VX7wH/A56QRS8hRkcgdchT3BcLHTzMiqRBvwcZMiRJcHlUJm0GO4hg5ZVAucvOUMmucE6TVf+BTOOFYMJ8jSBrDqWiBQOQXiLY7B/cPyq45GpObzBHY6IV41rQIwutJRdgRbcXGnIGNQeTip1WZEp1FSlK1fwBxlDYh+MNxFpcdGA8aZ6G4JMGOIA6CeHuTkxg+VcwUCTdAzV08UeKchEsyShABJwwJ5EZ8CSdRHfgvp9fILkxBkXgpLsdksZwKIRJATz85CTiy5Oa6yEGpZdccjRJLIkqdEmJk1SJElQA1R+O+1DFnYONiW8xe9KpMSu9zSZ7bBauX3xcTZ0bjnpShjhglxGDQqArgGoBVMdlIKiZmSsyRSDDAKRYJx0mhv3IMnArEiAC3gRgMUUKVEeuB2RUCBCJ0IQ4LmBGIu1I4FdK0oCMCQFCoNJ3v0TWTqzka2kaydBIbvSyttkX0yVFSEpZU1F/HnHHC4qCeQfwVWyZ4IL6U5EB3wKkFUPUkCIWjhrMFMUqihFjCdyeMh2VozSaSiomZEnMkEgARNU4KB7vAMXAqpuSdhsIITrkSqPUU8FmAkDgsMBqu+cyp6AOxTFUGiBPKzhwN20aNdRJ5emMiZd0iiZIic/icGRo25owspTFyPJW5LjNKGLZo4q0YIPKoptZJABbM4JaqEk8JbAnhGvBEhUJc7soSM8UsmEpgyf8JADI1aW7iY4EroVFHTsdZ+Rb5eoHjsMQTzbXMTrq0U1O5akLkF/qf5+ITcruKo2HbCKZFNFPTqkUnGUHJlbgvVapMo4n9MnVqwj5hGtYWUPkXAQKxSqIrV7gcCYXjliDCCLEOmM2ASCKyeJaYKRSJUK/CoxNehLAncGzSy4FZcl888RguuhBzJ+Nz1utyKidyzU7SWE6FLoh6H4HIL1wCTSUX5HYVR8O2EYwOsVEW2qZF0mKN5MJxX6pUsBERXvJibcqk9PjUPmgt4PhdJWZddPdwD67HGrl21IwXTtWP3BI398Vw9Upxs5zY1BTdZzab6+MxZ27X3vTcbSOvxxq5drQcu5ZqG4ic64OcinhnV/yz29fHcBnb2MY2trGNbWxjG9vYxja2sY3t/m3QaQYQf26bMqdAn4Dyb3HO4+khb9cYCKa0+jPKt2jS2Hr++07vINNzT+JnOPNkaWpyVfUWUQd+1k2vPuLJrLXUrWrubuFaSuGKRPGqz+ivWKWfmpYZrY1i/VyVmkqWbENxlAfr2hZhBF+EDoTV4TutCE255TNmJ72RRDD1UO1JHFI1AaBQD8K6XgOEZyB8yiUUyQi8atuxurL+KhCyL4nbSlQ0aiBKm9Eyo7VRyjGcmGB0WIwhBgh3HQj3p4BwNRDz6feBgFOzHRHBWPBHgHA1EO2pGbr2tmyWUB6CT7OqRNBwVvaEMhPQQcpLIP4C3DqTU4YF+Az0S5Un1J2kzA81A+mPzMKgtOt6vLtcYqYFwA1zFKbDMbMxlBlxVL6J8D2IeYIR4TIQainsOuIVX3A2oupuHMV6lEX2HJnlccx1pACWB2wBGPyybu6bGMH1yJwN6eKitQE+jJcpGnWTIodqg/BAEe5E8DLolJngxDXKXA5+kx6sDuZ4ZIYF+3gDvJEoB45AqAc4YS84h/RLCam8JF7XQ6+sn5N1NFJ6+K31NXUAwcwM4ZtA+YIcrwCCZjeVJFFLnfiwjas3qu4GFDmC+sbJ48rebpRKViZwWUhEIygQqqJxAkdDuB7aI178mEVrIwgfBqMnCKMlcKkRfAQmBmRVCMtMmN7VcGIieJVTcMqFYIbFkvpNeB/gOICPAf7CUkoWsVJGxjnrV7T1sHaAjAhwIKZ3y42b8lAHM6MoZuAXgFiOJx0Raikfl2kAnA3R5BBvcxDOCO2HfoeWGpgmgMuFoFS7ZFQ0AvdBnqCIsTGJzoDV2ijiEVEmeQCBI9O7ULSImxB1RBRmguAPCoAAcRLkZ2FYLKnfiprFLGlm8BemVJam3KEp61dU9dSLtWhh6Ig4WUUNHC0skCgCDFgjVOEiL4yzSD1kIJhvAU0OHRFOj4cUXTClAggwNJiLldcIUdEAQSIpECytNXNfnazWRhANpxUQapl2TwhQhaiYCacMhGhLwIGOAQ+GhU5NYF+gHHKgy9TkpAyHqYnTR5XWqetpgBCZHszLVCLqAG2HmRmuMCv4F9tPQMQChFgKwlnFQsFY4DVCSWPcdQnSQloq1XK5gH7TAHHC1IQ2geuhPcJaNBertVGW5XxKR+7RKKcueBWsVcGqEBUz4cKLYwSv48y6FsyouPCSnLkdiTWP+Mqay3EB2hXTnMvgxRo6GsrUaOvxTu8jCmGRuB2RZ9qsqOGUGcFEICywgVkb9C8REFSGLtawNDM8khPJKmFbcPlkh58LRTKAVMqlBqckzzNPVHofoSoaR+GigOuhPULKG8KREa2NkO8fcFR7dFkj3FS0OO7f4tVLvFWa35ZQiOCY3FErpp3enXWsLh9pK2yM+GGWxr0cFLkt/aqt3BfGO9L8PkMBHXV/OdeB2Cr/a3vqf3IbD/0GEGMbQAwgxjaAGECMbQAxgBjbAGJsY/uf3GL+Is9GJP7BmqGhr87SpryKbaWM/DV85Lmf5jsnw/hYMzE6jwDjao/W3+5IwafEHjcoY/j033e800ZPkelZ9Pld36qz7XU3OCGxqXHa6uraoiBMlpw9xWCB8GtHe8ivwmUnuN9os3nqGny3TyzJoEhMaHSTGP1NIMKHgAgGiMU3xR4J8cTIJx0RkfbsibTtdV1OSOd3qGUsciGpPp57NdwAYj14YrgbCIv42qTN71F+Z8fOHwaCSp1PeAVf/YDFHyhAJH1d2ba35pX8FhCxalL2F0UoU5DbGw4TcBs8uAYXl7UrCCoM1hzXZC6RS5RjgbQx8zVY1SMhhMY5cz6c6F5AHeM8TyYfTw8RfAgpgf294EEcEWnlokwQ+Nic04gm2h61SVkeYKCQJeRvC2kbiCP2ig87K2ukNSfEOa2PSQFRX42G84la8J5rZpcTq5u4VOl5sGNo+WROBVRUokRMYG6DuAGj6ktAEV6iI0TlRnidszLHAu/7k0lBAgBkNQs4Bdmxyuo0olvhRdnglN+mJm5D9m9LjBK8MH8m/3OEJge9/X3i19zBuZCIJtweOuLyS+oIKhDEVfomnyDtQB0jOg0EsuSU1QNrhCprQOGj5oQ4iXIhDJZUXotXF/RbrpldtFHVN4qeB7NNyCuunAoO7uVcjinilQ4j+hKOiSKyhMqsqgwNVd8Ikt9xlJLAjAhwKzIQbBz0PZTvoUBoPmZcoAa2rvh6Y246uBYFXpcjmpyzHWCYxKyYoV5jql9HhBPdDIyINx4PNCKOIK6pKsImJwTKICLkwrWVGr3wW7Rm7zSt1fNAPyxAKKeChghzFSogVLbgJEAg+Nek52sO6hUdopQUINjVnowb3wARHYAAM6MGQvkZQj1I2uAaiCnl2gwQPKXIkZNoYsSsmAFehUxAsQBRpqY3jMHErAzp0lMDRM0JidJGGREilIEavfBbcs1p0tVntqofwhPBIOcVwLVTky5ZxKXgpkaFRihfJ8ydqr4RgumuwEDkNFG0MvLURI1RxkPwzqpyhKBjTiY3MyJkapLoJKcMxEkjmkh7iLlhJwpKDt2oxJSxPK4zjwWrR4wivCIjAgoh0oaGE8J9knLLIwtZ5MkwT00BjA70MMWIsXoeiWF6AxMCEz9xH5ywKo4sbIXFGVwHZmgkKEpkAiNxJJzL6hvgcYjOxaxyW1nNgpkUSmBkRQtmUjCXoehjcDrM0mXxAxBYrLFUo7ajsDnoW8hKGLiGEQWMi9AjmVcxuQIE6ohOFZR0ucX0omuEXj5A4aPmhORAbXwvwHnEXi8Legpas+M7NUqbKj2PxEyPdOtmKwbXuXGy3Izobm/rNL/Lb7jNDondXPGKBdfZGbFT8jrHll0aAurTDS6aM729/9x224L4qVz3lBM/YZdc9n+i/rGNbWxjG9vYxja2sY1tbGMb29j+vdug5nyT7S8DobE/qi20X5L4smmroqasHnxVOh5NaU5ehF1xHMKnrO6VFBvBhda6vNcyTdalQ9fDqHr0y4zaWq9vvKaS6tyvIXbZL8UtHqwwQ8tfqICIqxAfFRUgmHKNGSKckKp6eyk2tkqWYhXqI/9OPSBaYYrgaqbJunS8KmzEJIwVBogUsnNMPmMJUVLXUPKU1lcSEV7srYCI1nD/MSAaCJvxlWyAnF4KfwMI1wXCfQiIRhMjrEu/G4hcj/i1w31ArHZPeO84aYyTd3ASkqpdaDZycRyFsxDDlBUsKGXNzwArQcqVSChga1B6n8GdhJfRpoB2/9FEKJHXa/n7u2iHeOdU1B16Hi6zUXK5yuioo6BoZ1RMk3XpidcIfddcGRicDuVCWyOwF5vBSfBEllgxeCU5x2K5qIXsTnLK/kCdUFhh762+ZU9OR407cgqiO+Hg9FOORMwKFtOKnwFWgs/hDcilCrYGsx1mlOdV42OVAtFXRKkjKBtEHZ5vAfoXcvZFCVwQlAeisVsKw6KNgsL1x4Zpsi4dQESiDoEsg7IC/OISa8ZztBnyN9Epa0YE9945B2JQlY+TCAF4cTaTOVIndgWJcUK8gyKIgNAuaBz2gyNxlPgieBN/zc8gVoICIZFQxIcbXBkR0PhYp3BOORmIZAI2iHIx3gL0L1hq5d0n9p/HKdMPNBANx2mJGrHFRkHREVExTdalJ1W6keU9p4OaiOqNcA8mllBJDLKJFXOUiDJCb4gaYwXTmLI/UCc6BM547n6MiJiB0I4DEOBI0F9VsPAdfkbUEaGRUMKpdHOyQETXSeGECkBfZmWDKBfjLfis6ETxUwCEmzMPJGUmyaxA1FFQ8kphmCbr0rFGhEyznEo64QIyoyUpEIXpUWLFcLoMhJbgUhSlpxUQFLZDYpwspi5ueGYaWMKM8OOE80F/VcHCN/yMKYGVgHKnVKKleDAvUlkO23gqSIE1ohImSTEoTUy66tSbmmoghAMS2igo5aqpME3WpUPXI+bLYeXDBuWWSG3Ug3Q+ZRJaKLFiivYN8z+yhUVOJYQMBOtvELMAMU5mkiXxc5IYIiVIHi1Qyvmgv6pg4d5rfsYkrASUO5loKcypm/U+wrJE6hQqyaOLNdggYEYsOPLMC/UOLNbQv1AeiIvK95hMxBYbBcXl+4jCNFmXDl2PZTqTqzCNgiKaHmI7elBjdokwSo4Vo4s1iDw5HmPUCyOwP1Bncznq3L3hLuId+23koa1NmRCxKuE+G752q3U97mFg+KvH7iihZhfc1wn3pLpHt6Of4j4bvnqzQPw+A2NwOP4IEGMbQIxtADGAGNsAYgAxtgHEAGJsA4ixje1fthVFArxDpht9E15C6L/SVbE7qlJ005gt2BdXaa+a1NmQZ3mW+Y733pbPtC6L/R9pK/fG8f7rabG05J5Nnrfm0utYMHATha021jyHYmx0+RnkCgi/erfOVaUUvoJ5UTY79sNVIHyWfPArkkKU0mIJch/7QNQ6Itb60BzXg+t0rsg4dJ+0FvJF0RaBPVq6XwExOX8dCO2o+pzX5+a3X3J0VSkrIKoGtkD4bguDc7eAyMyJNRBo9NQZEaF/PLnfAMIVIDBCezFoSAN+cTtLn5xF4YLcOswomKWyxF4pifdxFHaDvG6bEOFk42jmKzjDljiLdodEVWEOB50t6iQRvsUlxz3JrJBJ9p5ZtUP1MQqfYg1ECseqPI1GQS+zuxzP5D0zUZrjkmedTqPExAgWiSqESB9AdSPiZWDRFln81goE4kdMIuNObIAjfOzM/nD6ujjePX4TukDmOUSJZCJxV5iXcMoRTrpHC18BpUThK4SgLsLAJJgTzhbDenBZrQIKINDGQCgA8DskPAbv51wJvJFzOrNkyzkBiFOvPOlo2Brjm6QqIwLHVV1jnU4DJmicGD77xaV8UtWNGEPRFln+unpEIMqMUCUuTC9KQvFTBY43BmJyZUS4IHFHhCRwfj8K9wDxTfpHC18hj6scEgPsBbjkiQ3hhD0C1oPNOTEXBGdRlIgtJU6JskXOOCuF7OBCkXKwltRTj+gNRE3VTk2aZ51OosQEGycmCFMD8V7Yn28kLeyIcK5EmRFKwQkjQuez2QLhZkNhDBJ3RIs2ehOIR9I7avgKskbUQLBWBpz0qWY92JwUZwWsEBVqECBOov2R+RSigtEAYcvLU88lXmogtFx7XGxI63QOMg4nkBWcnA56oSBckQaIskbIScVRZopqR6aehsykuFyKhE7mOejkkwcqudYzx6N71PAVMltCeRTKohLWBalhnArrQUET7Y7Tm8rrnHQCDEHilGCCOolmRqyvmi4XZVkYSyKni07D0ciUE0VBrByXPLGbLjHRIZY4MSHzXaSNEZYWJgmrlUQzNXF70IsAgtcIP2O5m3jpTKrMoTwHUCHnIEJX0JtIThfm3lHDV8hsCeIrhLwwB2FsUJcLe+RStDg0OglxLmRRvqAEUu2QOCUzW34Bl4KnDmhm8H0EnxCmPL1PIBUP585iO5Z0Zl1Ux8X6sE7noHwYKJ3eM6kOSRTVDQ74ZrRFhIgmV1saZQa9iPUtrO9J7lTIuM5GCHfxFboXgf997118Npf3HyLwf0b94maeD5YZu+V/VM/kE0D8Dl/hds6Plf0ZS27m+c1wPUOXY2xjG9vYxja2sY1tbGMb29jG9k9vn6W5DHrMAOKOrYlOotoSZevxFTYjqdxkN8TrR+4Kl0zvtvnkxd1d4kTDH5jtM05ucDsoH4mnu4hnzMpJud4vtTVXWsjP/MlHeKZY3wnPXs8crQWxW0pgZeKdUGQXjt8i7+MFS6DxTSQaG5DCbn7Dbn9FwCHc6GB1YKzT+fWZzXFQxMekncUv3MZih2UbJM3HIUpS8dptA7GOnhKuttDBXxFF9D069ZDI26XelXd0aX+SWC7qu2iaHtyfAMJ/Agi/CURnioGPLNnoJMueWIBwfSDi/UC4zwFhAoNkQoPsPeW31mk/RXbBvhP5jfTVWGE9cGnKwsARvFx7nK22hXfKXFC9jJqloTwM1dFouBr2SMX4aKzRqCjOcjK4Q+VdZcpTeixY+0RuvgEiHWcZLQnuMKR5F87IMbNEetFTztLCHL/lohwOLuGiwTpaII7yEv2xAoL8iNh3ZPfgib2xQXUirO4EjvjM84AbUILCEGUhu1lbloYKMaiORsPVMEcaxkdljZWGOJm0v375WSOx2GWl2O3VwXqqgfAzAjIsdSX46hyneQsaYiC/1N5ETznP2hueI7ngxXflcHAJTAU4JwsEeQyjOzmJ23LxsjIR7+Qt6L54iXAzFhYFuHTKpeCX1zkyiLgjNY4Ju9uJf4GX0zssDVfraNRcDXukYXxU1kwcqaXobGhanuu5Ljv7+KSSD7QgK/ejXSPQlYvrMjnWFEG0GOKLiMiFsETW0VO0N1T6BNIXsFCISNsjAifvSUfEkcU2OJbLiU+Zd05aWBRoWuZSzOLml8EUNI5JUZKYN1gatBkdjZqrYY80jI/KGkq31JB1NjStAuFEo0On6LOScrLSRx8ICDcI+0PPZ1UbEZbIOnqK9oao5ziRu2ALFYjNNcKVxVprTnkBB1nhFA2LQkaEKxoWGKIynSQN6ASiVTBReGqWhvAwso5Gw9WwR2rGR2WNnZpKWtbIkKum5ECZxAngsvZGmZpwDFIsWQgrIbwbJIKqqckpS6QbPUXKU62QmDkcXqamzasm11w1xQaIk7AOlEXB/LmsO4Ej58zz8G6S47ygXopexoql4WodjTVXIx+pGR+VNVMOq1ZxPBJp7eE+IqGeRP8tE13UdGdR6GANDDpGFwIpa/TlUFGc4gL2ijBYhCXSjZ7Ce3kVFfaGcjimlJjoQ/cRXPt7fR+RlUbyFR7dR+AeAuof1fUXbjf++/4bkT9WN0Cl3N52ra42l6b1qztkf+Wi0ptL6nvurO/TJYlNWu++ZPubDIOP1KVpv/IRx326JPGv99O33MZDvwHE2AYQA4ixDSAGEGMbQAwgxjaA+DZdNLYBxPfZ/vtO7yfru3PtFje+bwERlieb3jzhXD+P6TEY1IYqYXmkJvs3Y+P2bAzdsqDIsN0SGxrDBY7xXT9Q9D0bNh9cTp3aNn3yXngGPv8ulIFgMoTsmIdfzFdFBwGC9OUDPADGQW9T9xgMasNG+I8sgG6t7n0Pm0CIjke6AUQJA0K+a341vRBo2DEbNvu2Y48VvPBV/JRQ9XYBooocEnoNM997qhgKBHkFwJSArEhcWdphMGQb1s2zXumtR+b3AOE+AcTidyI5end03Rgom0C4LhB1IJtOb3sTKQLDiQXQHV6tLdFJzqJJMbmzBJ8vDA38kxEhsvwuWSUOaG+ockbDYDBAcHQRibKifJActSS0zA7U7/N3tZFcTPkb1XuZJHwI+BfKR2njtsCXpzFQnIxrdXBO7JnjCTIzTViP5NJjnSDiCjuzIh+Tl3uN1knU3lwBAWNVdYK6AJFDHDvKT/raucP0IzyMgN/wJi9TzgzKip9VACIrW0RVzmgYDAYIji7i1GGKSQlRS9RTaJkdMXNKTqLPcdJYJuziPInt5OJEq4R/kcBHKTyUoNNz0hgvsiJEAIGeOamyfYxaD730f9I4M7VtZ/G2Q2HBi2O1aJ1QXpW16IwINXlyEj+EWRgavgZCDMRt8MrQkN/0na+aglBWglXiwCqgyhkNg6ECQsEFo0NXGATLaJkdYJHguwgyqAqHKIbA9hjK6aUxUTChTs6ySgAEYrzI2pK8iQKjPI/FKZuZJag7uJ5toDCw5gj3Gx23IwJ2Tf01Ii/Ws7jRMa9XQMgnZtwZv6NTIOTMClaJQ5Zj12UwbAChfBALRMPsmAsQ0TnDAInCR3L5UkTWiKAaHrKyzc6ySlKxWa4TXB8IN1sgxNKVbapRQsc8VjCjdZLtmreumnKHZAbDSclUDAAxOIJXHkbAb/ArnMtMiUwzy+oUWTmjYTBUV025G0+qHFOilrTMjjI1mXgnYnFgFgZs56mJ6S4nYSRZtRvDKknZ5pNeWJTL16Ij4nJMFWGChKI6Yu1RPQ+ZmswJHwoQIU9N9X0EK17IEuI16smF2AyIp3JmNgMrYwgPw2tMlURAKFNiMowQj/gpF1XOaBkMaoOz8UkuGqWkRC1pmR3lYgHf/ZxVOJhjQqOEbCf2BbQ0sNyiJcRLauK2RNisvApW95hVdwNsP2onB02TetAubWtt23kujBjut7nSOrnwOjL/cd+3/4I769/jR8QvKdvrfeH33b7bI474ReWysM8AYmwDiAHE2AYQA4ixDSAGEGMbQAwgxja2sY2t3WL9NatY3Lkh/rSrcsUr1ayOhc63W1unpslYVP5O/Zo6drS/tzVFpk4fmKPzDeWEsGVLKsdph7fyFusqV4dCsO72cDcQmSkS6j/li99uRweI2pvixKdSRzBZq2v0f2t5oVt7R97Cqhobf4Nv+t3Y3rGlB8QmnGtTDFHGAOFXjeoBUXbUStThygPqNRB9VkRo4HFddY37gfDXgDAwKBC+Y41p5doW79SZAU4Di0Cko2hTgHdR+BDCREjCjZiP4lBJodbR8K6Kt5KyekXmT5wlDotLmg+1imMlKY8EpdBfjWsC9QyUQyUxv2LWdkw5xgR99xoTJrF9K3UNlALZi/IK8bu04Cx6I1Nuc9EmUebGkV09cBSxKn4oPmlq33kuDiLTypUtKq/Ao+sUnChcBHkBHO7LUygaHMFlF2gEd4FKSMybKDoazGhQ52qQd/W5nsyfcEGYTRRwIRhXojpIxWkaNXbLKb9Wr1Fbgqp4wCZxRJ7ljXEQYWAFW9yoa2gbECsFCjIaSYPtknTFo1+0Sc6z6n4gukuUV+19UpUPOs29KBRocAPTypUtzNaArga/AP7uWdCH+AYAQnU4wFLwEs3ECScjSNCKt1DraODde/CRZLoKzGF4L/yJoD7gCA5IEJaDcje8xDfR2C0qNFFipQSn3ECaDCdhUOQR8S5AZItbdQ2oi8DCs5brcApCU4T2qdpGo00iuiBBGCATAnUkqHw40Tpg16pz0rLgJhc2lD4ow6zxQoB84PfqRZuCeRqnzFLwmUuHYzUQRUdDgXCzBSJW/AnhOnGEEcQlAcshlvl9BhBuziPCqeGo3QLBvInZRF3REZEtbtU1oC4SdURwPgDBI0LqiqK24SptEtUFkd6KzODgiGZljfBZrwQtC9K6rtIHZDziCVMP2A6JmyFAsH5R0eAIOeIJlDkABEVjWeloZAIOpRf2xAn8CRrSgXl3NJbBAUlZ2SJwDTo1gQdxnGFFEIYFaj+Fcq2CmpknBD2NUwYCFvfUNZLGSkE0GJ2ajjPkjkq0FGlz0SbJumNR4qhIq6tlvRIeCqaVK1uYbTExC4OnJmY7JF7qUtCFKRoNDmYi5OWRB5qSClsdjUvWq3BOyIqIgcJGMh+EJtRJOCApRyfBvQmWOeRY/ooVXhZr1C6SW1hGL1gQs57GBYocWLad66prOI2VgmgwWKwTkcJgxyziWtrmrE2i7IyQGSfgYujEGh1YH17YMNIubeXKlru2eOP3R/PXR3pmaI6ixxGrUq7HfikihmsL4pUy4pVb0riZ9vNbvLM9d3TknzDlPlZP/IAV10qMd+WLv1XHx4EY29jGNraxjW1sYxvb2MY2trGN7X9xG+Sh/79A2Dgh/+Uw8v99Z3dKbBLqY+crZcVblcXQKVHzqnpFdCu+RCgWnt+L5br5WdkURVxbHytSBJYUEu86v1+3Wnkp0R7oMkBKhBa0CXFb6tRTpxZuGzloe8E6rH4E3hBGYIo/CYTUkSr3erDHYv6MbuWmt7rDlQaxvolrNTsmV0WqJx+tAFH4B757PgTRGhAHFXqgywApou94aTe/WResc8h3gYBzt6p5DYRoT8QOEPGjQHT4GMl3u/c+IHyxrQUiF0XuoqPxlGWp9MggxVW1rmaBqA0WCNcHIrLiSPLRRGipCq94JBaI3ApJXMcJgX6ENDZBuyLG96Jbkd6zCoXyG8CE0Egkyvmw7Iesf0HHLij5fZ5EJF1KcihT1SuOmS9BU0kSOXWUhxJQA+QZwKUIwvw4Bk3jRNIBQJCme4hQ21AWCV45dlLiUV/0ZS0PVe/osi4ARHKsTIBSMxOEU6NNnrkcygJRJosAURgshZkACZwotKzzrECQgsZb5iuwPH8o/IWY46zErGzB3ISK/aDxR1AaSn4jpya/LE7xSZg9wnmFjZFO8uK4DKKgbtZykoAV4lnhn3yo5xmMkqVNF7UfbAqOa4Kayb0/SzyLYPVBKB3sPM9wsYLdscm6cIjQAiAiuBvkvo3qlFX9Ha5FWCDSKynHuIiqYlLFCQmyOnjzNzIQ4hRc/r0xqyHHHclsDk3hlJtg2A8af8TLXwXCJ9XNAHtEmBbvpF4h1BhZWN9YYQOcErUNrBAvk9KkjBIq5bQeEYuNCUA4iY6ishSynCZlmgTpoqhAbLAusEZ4V4DgEyVlvojzOaKG8EigFMLaHBF8k6gqJlWcEDAOLBAkmZBHBHMQ3pjVoPyFmNkcYE4w7yKokoayH0AcoI5gJzx3CICIOX5RAQLqFRqZBFPTG08uk6gtqbXChnBBXPgZiFDH/8LUBCCEkDIrEIs9s6ZjOyUwyCRBS1zaYl0UIFziayecPEn5ItHpiIBOiI2wolFplMHibJwQn+kp3uXLv0RrBFgNdDTJ1FRIU8rmoLk/K1ucgrPsB8QfARDRFSAwNTFLRGR8TsrGyJHb0GTSd1r2Cx9DNdQwZYARQnWGrORRgAgNECU6SqjC3wSX7ZT4LGB3MBAr1gX6iyO7WJGw2E5NEEwqBDWdzAFWYbA4GydElSm8UW2iMTddErOPmNmBZTPzF6Y5x1kJmp+4IMFZ9gPij4CDp3oXSfT8mMLCDAt0xVl4IHQsS0qB0njR8hQIsCHACKE6g1NNJ40NU+4jdNS7rB8SxB7UEbOdtP88K7uDCluzLhCnhSK0BGfvI3SxVopl5LK4liQaH0abozBYbl1omguu9uiKJxG288cq3VYdt7cY/rzD3dpzv506Im6Wfqt37+eCxDu76l7mRC/d7dJv1/HZzZb5UTs/AsTfa9H/4DYe+g0gxjaAGECMbQAxgBjbAGIAMbYBxG81+091R13OtVL/x0fC/xAQ0XwWfYmp+by5hY/U1eRb5S12EGcCTt3i/8Wjt8k8v5zusoz8B+WpK+8hFef98h9vB+ns3fO6kIdXOrY/7A/Pknr3Yo9TnqfXet+y5ZJ1O1zrnNLEsIrVXIu4X+n3zR3B9HdcJ+4CUbQ+yA2jnoAaiFBFlvZdQ9TpoA/HJxdr3QkC4oDuIjx+/dofqLu5a3M/0t+nAwGxe949A4id2++RRnqc8tAu7fsuELvrQJTP0ogWiFbn4ouBsBNC3ARiZe0tII49IJ7RO/ulmwDEYV/3YwHi6fXp9XCo+ljPdPrJh64BsRoR9DKrREpRdQzRl6ijgoAvAS7G2ahTgOsABYv3WSOhMEtDHCAx6gux51nTl0gt58L5uDh5TfbdMCWCsCDAmVAgyD2FCCgKBHKBlQWuBGpRRRDV8DgL0wSsotzumYBYzurnh5fD4enFPf/69fRKnfrwsvznHl6els8fy187Ig4H2vew9PGSzi3ffhICDMQzdTPloX2cij8Phx+0j3P94E/aS/+xjsZJXsZGzB/4kFkZ3kjvu6zAoZFBlJegmgVvpD8QEF+EXKHOxGKR8sACcyVSi+pdnOQ19uWfqICoygZxKpCWOBO6RiRR9VAgCPS3zEE5ZWduCNDycFnDI4iXHKwiYWZwGJNlDNCEseeePOiIoP9o77Jv/8gTzsNB1wjq9P1y9OmF0jzuaRgAiB13MeXB0FiOPXJph0cucY/0eyqQfi9J0QBW0XDqJcZvcBo0GAX4EqSKkSOD4DeAcABietdYIvheYrGUyCvqb9ZoLPDjFj6HqIAEHRHKgrBrBJV+5iUXQAA+WOuFQSKkggjrVcND1pMgoUGEmTE5jIi9AiEj4ul1GQ0LPHTWP7wyHG530Kum/fPTK4FHuQBhHhFL1zvkOTjOSXsf+OgDL0WUnnLT3qWWVwEigqMRNMJPdCVKis/6Go7SRI0MwrbMpwoIIRbMDiSDU5GCcDnySnb8O93HI6KE6XBrIKJrgYCqRw2ErhrCIDFAFPZFAULtFr0MXiMOAoRZI17cyzJlvTBEKyDQpcuxR+rYh2fex0AcGAjsw7ha9r4wTC8KhFwbvFCtrKORCTDMOxBZoBzGRaKCnMzUZNQpjnMIZWpiBoZMTfE0XUoslhx5xQUbqYWnBehsYGqi3B0gAjgT6hlfbD6BGubkqslOTSW+iqpkCPviVMI7KfFGOSe4anoGEPu9W01Ny+SCqaQCYg8gHn6iY2nf/rDDdOM4Dy3LS85manIyNTmp45EXU4kvgggnrizWwRXRJ/Al8OmNOkXkK30oWCxL6EXjptB3FzQWS4m8Ejh9jtQCvQtebGWxvkAFpAChLAjcLyihCxod5T4CuVxhTczK7gBXJGt45IsEnhpncE4ongvdRzzT0vlz96L3EVisadHdLfsXSF4sEMuUtYyUpxfqc1onHujosow/PPPy6ygPfVJOLNYPP7FYU64f/CmL9c/NS9jbW7yZ/l6Owj3yDv5b3Vn/enXrS9Z8sbtbHfnEFv9g+ns5CvfpbHznRxwPL+Vv78j/q2089BtAfFGNA4gBxABiADGAGEAMIAYQA4gBxP8vIP5WVX+vSV9T05fbP4D4h0p9MN/pqSw9BN6TyzA/stqtn5l89nHWqh54bw8fzF9y0fPR/HBtTy7KJ376Ka3al5rI7bluD7Xz48+E7u4n08fl6IP5eyi9aZ8Xkjd28VYtf3dOWQ0tU6EGYn8FIXW6a95dp54D7z1YJkS3KbvqqOYyjhc5qP4ASb83Ne13UpJtz04cOW3tu05bTEvu6Kd9ZT2O2rbsrgFB/ARyFD69lgq6XfMhINZbqWc9Iq4Ptn0zIg6uA0Rp1d7WtOt2kTv0gHBdID7ST00t5vcKiCfmEewcuAXEMrBIk2ODuAs1U0F4CD+VhQAnx8PL6wsYC/SbGvbjpbAfHpyk5tzEf7Ajgkpe9uXy6xJRnzAffoIdwaU7crQUIJQtUYCA06Uee2vmBbE3kB88iyflVzj9zrXzd23Jw139ZNshnm+qY48eVwsz9wbOQOYaLK68Mvft2Mn3yI69iqmwPzAvQVkIe56G3f4//OtRf/PfvdMSDuxQx7TB/AezRnDJz6X8ukTY9pjr37O7hX6TvQSErBHClmBrHepaJp3Huia3Yl7skFIse2SbMcXo98c92uJySw539ZNtx06OFvdrtvBZgaAkr8w1kCEqhENmGqhftjAViHdAhSkLgdgglJ+qQ3p0Fe3dOy1B5+0HZjM8vZZ6DqjJlfJticR6AA9Cj++l23bCmTjwmChsiR3vgf20r67pYcW82GmJTk4Rh5aBRkPf9RN5qZYHd08/2XbwGkG9weOVetxJvz85A8Qhnw91BQeXK8hMBfAOCguBO37Z8x9pDP9+5A6TIxYI4kUc5ErJAkEjQsu3Jeba9pkpkYF4eLZA6KJtgdDVw9bUtmen+TMQaBnS0vdH+TQtebmnn2w7AATb+ngQyqX0O48IPbd4qqEZsqrgUelQlqkgw+tRWQh7HsCHR51I6DfNkRjWWkLupH078gSIQym/LRF2ZKbEPo8IBQCfwpbQ/dKqR1MT5XlsmRc74lrmqemg05AT9sby/ceLtOVRW9LMHBv9ZNshQNBo+mmnpiXvQa5ssQyBa2DP1CdekIm7UDMVZMH5qSyE3QsvQj+fecGj9HvQ3PiYlvDA8zjXJOyGXM8r5s5Sfl0ibCvHd7xY02+Uufx1li3xJL/RKjAkUBO39GfLvHh6eXbaLnA10DKe7/n7QdvyU1tS2b/ZT7YdT3yU+R37B7nT0X53W7dJvTvGmqlw83brrnTrem7lq4/fz5HYqum+Eg53l3pPuXfVuQ3ENlOht92XbosPcW+5998Pf4R58XtA3C73A3fx41nTN7F/APFN7B9ADCAGEAOIAcQAYgAxgPir9Qwgvkk9/1og/nZ1/3ZA/lL9A4h/sH7hG+w/zrUwKfb9/Q9VeuPpel4fbWs9mLIPfXs2H6lVvIt7WrC/t+RrxwpTw9b/sOrw5nv2wYvX/6Nci70z4gd766o/rOoUB3quoShflDQ1EPsNIHZXumSDd9Ec3RtaQQ+IfdWe20C0vJVDVf9hv+v0f7c87cqPci1qINxVILCZGvbro/cBcb1L1jXtttp7Awj3ASDcCgjb0t164PwAI4KcG8I7OIhTp2VAXOVa8Iurz/sn5j8IweunMh7AhVDWB3MXftLRH3UNe2ZHsAdYawF/AqoXjl+K/fGiL8T+kFLJJi3b8kyQss+74Jbo0Z/4VI6IraMwO7Q92hKpe3+91sLUqHgre7BJCjOFmQqyFBx2hXegfIPHu7kWTGt43u9RnrguhbshXA5lfcAxSvVRSaYGTstOxIPWAv7E4Rl59/LbsUv0cY8cYrnhcYBnUl4uX/MuHtkpKkcPTl9BP7R1FGaHtgfu00ewXvaHG7UWpoat/4nUDCpmihBJmF1w2AnvQPc8va49y1tcC3JCk5Ncc2f3PjMp4EIHN0I91OKjPpQaqL6HV7boOWtbcIlPwqtQPgW4GeBOPLzqKVR4HEo3oAl1i3dBTJIHHD1kdsZhVUcmFOT2oLeYH1KzR3q1FqaGrR9sEstMyUAsE8/zTnkHwsA4uI6Lf4Nr8fDcB8K9QOrgkLUnUM7D80MpKbv2xbTHndtlHQt2uD8fXAFCCWXoiGXPswKhbn3tkoMzXdLwLkhJg9oALsjeWY6IqaMBAkoaB/eo5T/eqLUwNV5WfWmYKQWIvVBNDAMDnIg7uRac4+fPQi3TobzMxmCt7UtzYKyWVAHxSLy5fT4Gipd2hVLWwM2Qzng0QBTVi/zfBu+CgNCjLrMzVnUUZoeZmqRVYGRcr7UwNVYndcVMEY4clmZoThQGhmpE3Me12LMw1O4FekVYrJnxQAQTYT6A9SFMjpenhg2BhhG3GnWBWMnlSl4qfe+Um/FD2CQ7vjjgsovqBdv6wC3q8S6wXOpRsDOUI2LrKMwObY+0FHUxD/xarYWpUbWUmR+2N+2F4n79zZnztZ92e099AbpOb9kOdQ1a1qd1LK5s27yLj9V4+HCOe+rHhezL+ttW5jUPYZuZ0AcC+i2ar65By/oKHYtt3sXHajx8OMc99X8i89d3z7+9pi+ofwAxgPgX1zSA+CY1DSC+SU0DiG9S0wDim9Q0gPgmNQ0gvklN36j+r6z07zTob0ij/IWWDCC+oiVXHm7VjATwP1puxudKXm8NG+L1o3nvbi4/nUYnQW73IbeWvnFclT29L/fxNmwAseaM7Hq9tN9OVTvC9+wYb7kZvZR1yet6saeup2FDmCO3lDwKA2WLuVK5/J/pBIJCeJ1rx984isQ+a3jst2uvf9voLLtbQLi7gNjqXhxpuRm3R8S63q2XLVtmibvagjrv9fQ14+MmEIes4bF3dwLhXKtPIkDUXAxhXPDna44hcp2lUCt5sEuJvHCGsfDwkv8zah6vUoO6eNb10p7Xl8LneHBrFQ8qXdJsKHk87w3rwqnGRs1cWauK6IiAPgC7epyTzwzEs9PW/dyqvVYmKUyZ0oO/ftGvhouxF/4Bff5nf5AYItdZCrWSB3Xfzu0rbkb5z6p5/IdqlJL79dKe/xQFDrgVazYETXx7pNlS8ngW/shBOBM95spaVUTXCIwI2M4unD2AoLgqqq7h2MHbr71WJilMmaLqsdRBx2suBhQnoBnxH9GXuMVSaJU89izdXwYdopJobBLhZjgAoSX361UglF1BHIoVG+KZLN9tKmAAWNbRcJUUScVc6amKoBaMiAyEI5YJr0h0Kiypf0jb+7W3yiSFKVNUPagcqqXiYuxdjhGydMHhHm7ESsmjBQJRSQ4OsUmEmyFAaMn9egsQYFcwh6JlQzwf9gWINbsEQDAHRHRjesyVnqoIamlGBNejQDDPBVFYtmpvlEkMU2ZvgTi0XAwMUmhG/EfYCLdYCq2SB3fowQJhp6ai5sFASMn9emnPfwq7QvlQFRuC0Dcjwq2VPH7+VOYFpQH3rmWu9FRFukDsQfUBEM+k4XEA32NbR6TixWSmTFH1YCD2DRcDv6EZ8byHvsQtlkKr5LFX9aQqKknN23hgeubzXkvu10t7njPPYe86bAwiev1Emi12CQdYEi4IdJzWzJWeqkipZScpERPlib9lIPbge2zWvq95McqUKaoeBMQf984f+rcqdZpP3Pys83y27MNm+l+rm8Nfrx+7s77CZLnCS/mSZwT3APEZ9Nd5Plv24UPpPxNX5ZpdvRTjWdNv1jIe+v0t8wcQ38T8AcQ3MX8A8U3MH0B8E/MHEN/E/AHENzF/APFNzP//A8RXdMcts/X41zZvu/S/U/8A4kuByK/agyHRHFt/W72u32TJP1vNjafX3Qt5KpZnkK/4fODf5L/Y6TdxUrKz6YWoBxTku9SkDUQ0bf7KocInjpPt3vsdwcfkL21xFXZ44ujXcixv57nUhuDwfPS9lB5N/rr+jnU5dTRWBNOtzwUP7Ubr0d9fBUJx3K+A2LX7yUkCR6l+7vfwah1w5AHECP7m5BV1CnuvQUcIFMR4R7z50lTPkeP9xhkZAv56zelckiMKlRzFHq8dFvJRypS8HEXpwXTtuv61dR0gUqnrOhCHLhAHVx29G4hDI6ou/rkfec+P/H7zD36HnqBgIMTCXW6oL83SprorQFSdji6YWiBcgRifvgYiERB+G4im/tY6Lm8bCH61VVgUP17Id1rUM5aHts+ZyfHzITMssJeOaj51nPx4KQ98ld0hMVXoaf+zAgBeA940LrL/+eVwhDd4FtYQMTbYwbPUsHyf3Pt8nAHEtHybLjFMMzrCJTs1THMMy/GZuuzIf6flc+JcS6ek9zlGmnwopXQ1l0FTybIvTYlKXkqJfpmkGITEaZZa3pf955lKMvmr+lvruDxOe6T9Um4KVOL53SkDwYmKBpx9LrsWwUNY/E+PLjMs1L3IzAXOpy+z7/fFBSLOy8yRoC798QKN+0eEovkJr9kuQ8NiJXvsYz+kAIGX3gk7mprewomnEeroU4inpRnB83w+2RGxnIt8PNBcf+K/3lHOyPljeguRT3lKx/kTrwrR8Vigjj/PdIw6LGDEJDpKtbwFmm2oJJO/qr+1DiMCNi25qNykQDgwNHaqlwEghGMApYqHTCB4eCWGBbr2QYN07BFvhJQu4O4vxEQS4wHf47DXkFKP4uWWc70E3tg/yrfHvG/HxAAdEQLEM4CImM/D+Z07KkzvnRHxPnGzo66z3JWUc5K/CsS0nJH1iNBJ6bgcWVJHnVrKiHhb6gzcsSZ/VX/HOgEC+6I7O2qL1MrNfha9jD1NIIesRgHuxWNhcjjhXQjzQfU1oHQh3Dz1Qb0IuyOLjJSwZD0g1t804AzWCACxc3ZELGdVblhnjg5ujqceEMv+pfnxokAsv5s1QoGIy2TiZgBRrxF0MigQmr9Zo1bWuTwicILEpcQ3bskyalhx4ln1MmRqcqqe8cyThjA5HpVh8SykrOcsmlPiifCU81xNTXsdEQxJvlp6eIa2EMf4OWAPT3qHvcb9cfhGV00MxH4vQMgagQZRZ59KR3g+dxcglv00QwMCCwTvSzEyEHlqUSCCK1NTjHlqCvVV05L3FAJKr0+EXH9jXZmajvMJ+5cSLxddrElx4gC9DADxIBwDZlqw6gSYHDSjg2HxLMob4DDsXpTXQQo1GhHxIS/4ypEQrka+f3hYAMPvJ/rFe55E3spxzBPcbeA+AnoZdB9xntNyNukii86eLmfcBSzX8bQoTssUMV2WZZomgbkAQXP+mXO5OF3Sks9LOZS/3EdQmrJYU+lLuuo+Anm5pJy/qb+xjvYuEx3tpUuGC6yIMbjO9Whvu5/MvvtQjsPdNa3vrONmqeWMtPcBn9m8ucK/ZtG1+vvlxc919kdjiP757WNA2E+5u/1Si67V39+i+5du46HfAOIOi/6J+gcQA4jbXTCAGED89foHEAOI210wgBhA/PX6BxDfAQgq2v7794D3/8y67wKEv+v4vw6I2H7Tx2Sr52XLc0/zj11W5I1NV7opoNQp8yf4M2xZ4JzwJUz+dptml260Mp2TMxZO8sT1WqnW8pj/+nSeweOYVsc27A/2s2JuBFtOse6cJE3lUHeh7EjemBiQOZl/UhD7XUwpPSCUwyBsioBU6tCPVU6/CYR365pCP2kFBLwFcZXUNyWvgSB3xdaxYs82ECGY48GWY6yLksbfAYTrARH5P2ngTSAq+6zNTUOuANHbdRWIbCEDcbXUfmcjp98Awt0Gwl0BwvQf0ixGJx7AR+Y0kMMiwQOWaG8i1wi7YehJe/nH3tuEgsgtAl7C+4xHz/g1XfDNAoG9xKbAdIGc7Fxhd4lf5Y/xHVwJYWLI0cvkLI/CWiBAZAsVCC2Vaohx4poWx02kko9iOfqB/mnOKVEH1uyLkl/tLxaDu0GfZGtu34VK9Q6uJGNdhO0g1/AAPrEHl1x4CT7hRD23HAt5aprLPy+ODrgKidKwfAtvQTDmX/Hk3CkowwL/Iu8lNoV6zU7ibqR95JRs85NL8xSQR84ycV+y1ziElQUAolioQGipXN+Sn0fsib6dhI2h/bD8YwiFRON8w74o+dX+YjG4G8ht2neiUtnmaK07J7L9BObCGzMRIoBYsglLgkbE+1vmINQjQqY4ZuRM/H1xA76jJCe/0LDCYcB+EK5iBmIxmMvgxhKD6L3Oz77loA79oKVEz/We38HDsBYACLXQACGlkoWc/905hTSK5exVXjrizG2Lef2o2Rclv9pfLAZ3AydHOY5SqXW+ti5KT1BFb2AiVEBgjVBQ1muETHHCYggOjAaU5ORX7VjXGXUNBM+iRPkKU9Iu0/x2RBQgoqOUVG9E2bUFCQQvWHg0QEipZGFCeRgRqMvNBQiMiBoIw77I+dV+02LXAiHrDUaE85V1MUhPUGJiEoCtEHjgXi4AYtl7Wr4TByFMqQZCpjidmriweMpTE//CAFWGhVOzTgJEHtI6NaHhdX6arYm10QAhKUNAc2sLyHZj4UWvmrRU/k4decLc7XPXCTMkMsNCasEa0bAvcn61v2qxTE15GhXAmV+47Kisi9ITjEqcEhgHHuwCDA3iHdBSxhyGqbmPsEAsy/GMZRjLvi7KtGQZhoVZrGmt8LyI+Rk8C1qy6Nt5bvI7KjU65DGlc0oqg6emyoIzsyjUQk1bSuV6ialxIZPIPpmaZtx1KAknMltkcsLqsOyLnF/tLxajBF2sc/uWY1wa/TbWLbbOfo6hd9HYvZSMf+XOes26WF8wrlP+931tubHwOpPjJs8jdr59snU96z6+/Q0g1qyLLSBu8TPutvADhJv4h4D4f/Ks6X5r/59aN4AYQPztpg4gBhADiAHEAGIAMYAYQAwghnX/rGn30nC+2sY/cBr7O/d9TUf9bwFhnrvE5hCxJ+zzHTmewEJY5+p8i20d9mhxp7d19x8GTcKIiB/oAN1UYaPW8Miti1mBY66tyGoCRktD0hbFjYAemVYlRCnjXDFMtDUrIPoufZE7CGHdvcmmWXe/b1OGutRYeEgdIMq7ntYOvL/pTY1h+4zKnWi6JJTPoqERhV3iUjlekyI6QKSV4kaMUdPXJQQpw3Jg4opRch8Q7uNAuJtA5DNWnVYNS+LLgXAFCFcD4T4MRN7jOyUYICoOjKRWdsJRXDYNt2J+F6cKFVu4ElFcPOjeqclF3ijwHpibMcPDVvgNxF04MvFLUlwKr4FTcQp+p3/GPrUSFIRJnPpa41lbIP/OszIy8AuSDGCEsLNqhvvLamiQ542YGb4cN+1V92urpaEvAGtZ8LwrX6MpIbkVBwb2C0fqxN5YcA9aboWDB9vBD1a4EvDtKhDqLtRczIMgNya4GQHD32V+gzoIA2tjkF+68BocfMOxnGOwA1aqq1RHhHAoaFLQVojbFCU5/gUlAvi/XVbYqDU8lEZQFDhse7MfvNHSYL+hUdwIxTu9ZK1KiELzqTgwp5CnxIlZB8o9aLkVxIsAS8ExdU25EjUQU5OLzzYemJ73YfhPUgdxMGgf3OY4hwqvAam4mwoQujcoiyNPTWJdcOBnIE2AsBbteQ+6SL5HAeLI/Ay28b0eEezcz8dtey0QVkvjKOwNLcvXfI26hDwiLMME7RRUTiG6FgieB2fmcTgLBLgSkTkXeeafm1yRlTDUMGlsVEf64nqnfcuJI0BMqeY1LJ1JKhh5sS4dEPISr0DAuiD8jJMAwYyMkH9pJzIQpLDBNlTSDUcBYrFTjtv2emdHRNHS4LRWcSNVfI2qhLJGVG3VNSKejjxfg3vQciuIF6FTk+VKRO30NKU8NZVcOjU5mZoAxCnzG5hWNV1CUPaGoSOcTgAiFF0x0bSQSS5kKmTmUARQ42jOzWQzblnk2SlrYwCIKFBXGh5R+Byk0qHHS3uJzaFAWC0NTnsyeiCRNuVr2BKCQxk1ByaaqekSWYIK3IOWW8GKFbMyHApXAqoSzELIi7XNhSUqyGKFq1LoUnA54DgEzws25TKsiwtomliEvbA0wKwgC2AjeNpaI9vFC2bkkr3SHAls+pWgnwSWBxZY7xoND9bcWPJFPW7bG3gd6WhpBLA3clnLfYTha5gSopMynGWYwH65G4mfdIvHzu3Xx/Pj25+6s47dX/6eO+twq2QtI37QiraMTVs+C8TvKl2U/DQi3B/Z4savP/mII37QiraMb/1AcjxrGkAMIP50IwcQA4gBxABiADGAGEAMIAYQA4j/J0B8trY/F9zmX6JXM4DY2OLqr+pqqGBwq3HR6lWE6rdstTpHjNbMTB5Ybd29Z3aIdrzoW3lDpYVRbfap2dm+CPbepFQfM2vqd7o2WDvqZ3GlhXgnsKT873u0rvlQlbcFhNeEqdW4aGUStrQqUAq7KFLMOWsgfO22D+0+/I6qmu96dIceEEaCoVJraPkp5d1C29neylfMFohgjhc7UKrfAMKqkMSwcdp9AAi3DYS/BoQrQIiZ0T4GDp3O7ACRlTJ+Ewh3DxB0QNkZPq6BsCPCN6XaFnaBmJzwJSo1i6IwoYyK46y6Gvp6bAGisDQqvQr23Vk+yLFR5/BSPvE4CrOBlEAmeaHWIQLKJTM6XLEGQGhwGFsPv5Q813og+OaFe0JuoDOzOsALqfkpRxzjMQuxddHqIEsSO+EchByOwnIR1ojT2uBGllLdUeqsWjgr80SA8CLYXqtZKDvixFyLmOCiPKHrT6odoEAoS6PRq0iWZxEzT6Soc7AiB/86hcJsSMz9CNn9WmoUd3vU+hbfnpMoKFU99HJ59iyfrE6GF8oDOU65vYhbUPFTzumUna3C7kCrWACAz3QRTfn1K4hOQ1A9BaiH8EvtWipYKlZ/40186YXfsqxLBMRaT6MoSPA3UbzQVWFyKyBcR68iySQnzI6iUaHcCQhB0C/y0OkSAe8u2+Jc5oFYIJQVEk24pKaeyH9F+aPoZBQg0F4+t9nMmp8ShEThUmFumPAeUbuW8gAIWIj2QW9DSy11lhaC2VL4LTwiwJeo1SyKggR/S9HKi7CWhUvHLhCF4ZAXa2V2rNU5UiHUFGaDAhFF1UOmoAxEEGsAhFkjSj00Ivh3zHpIpStp0iB20RqIwk/hXychFXSAAOjUtdFpbJUCRHQtEKiztBDMFuW3FCBWehrKjoCYjw7gaKYmCg2jV00WiMJwqHkWMRSNiqzOwZMA/SIg1KF+ubBVWePCTE2hMEAEiHLVZOqhNUJHp5LmoJOhCzxPTSCqqsxQxU8JzDFxNjJKNTVxIJDEQGBqCmVqinlqQjmo08o+XC5R8pSe42hQLUOjsCOOvI++AwhelnjppPuIvEiJRFTNcAiQEynMDuGJGO5E5PJZC8PKhLBVslgHpzVyVJPCAOEVotxH2HpIXwNWY2kuOhmwEZwOMD8mLdXwU0gnRBhE7xoZBbG7sFizPXHiEQFpMS/ET2mfLNYoFSyVqRFCgQKH6HX8VsCRjdusWF1g3r/90Tvrq82K4T4tDHROSQnFDyu9V5eC46oL4puy4t9+xBE/me/vAUHn7j214b74zzziuLfOb7CNZ00DiAHEAGIAMYAYQAwgBhADiL/fwgHEN2nhAOKbtPBbGvK9zsU/ao3/4P4BxE1rDA+j+/p+7GWKm1FNUqiSNQbkt1rrTFNlwH/f47qE0NqXj8Rzgus2VGXZsq8yRNxG6zfaod81Rkx9PK7qjz0gKmsirDSPYDtAFAd3Gyhjqw2WFbIJRKrz+iqvr9zvFghvfuQjCa7eycRF8Y09PSCQLpS4LKGPhq9kJop2SFiF6oBVbVyYuJolTFig3BJfiZZ8PRDqoroLiO0R0QDBvoJjLzROASL2XxQMzm0C0RJ1qvgoJmcPiBsjIluzAuKYI40URQs4UpQbYTkecIwfN6OZgBdSxxqxPA3wPaask6H8DwDkndSZOSSi4MExVI6zxlExXA72vUGzg8ty71mLA94/rVltQenMFZG4LHjtuLSE3Ec0007SjhjP2qZKKN7qgaA+4XNw/e9Iz6wS9Iplc4CXAlaLQ8YTuA8aUQSe55PlRliOR1GROHWjmWCyKEwQOAYLi8Gzmgs83NbbrUCoPsVJVDlEwYNdqvz+umoKqBea9PxFs8PzFPYWtC0AQmtWWyj9KcjUFDSYgm0JnLCJ08AKnP0ncQ8vXZh1EFQPxAU9KhN+eMOeiEAIyGf7ITjlxig7LagqhOpyKP9BuRGW46EBY5BmHc2EVF4sE+T8TmyFwmJgVQ4HjoXlfygQytoQb3EsESYmB3WNWDMggo4IjvfwTkBMrpAdrPYFbEE+AEHlrVuCRfctE3sUCFjhoP3hbMwUqIF44XNACwSAKhCTTE2lHwoToAIi6pkOBkSlrtFGLNF07TFe/JJEVeG8nM4Fw2LgeZ2/1yPCRC9xfSBUayNWCh8AwsscHXlEQIsDZIdSs9pigdDympbM20CU1tmYKcEQjLxogbxJuCgJDSJTk1rTAGFoWQEKQrGiZq05Hqo01DsGIKwyB9IF4zwXVQ7mfBRq2rEKrIRPFQwqUxOraxiBFLpqQhg3jFMwLd4McYe0oLRmtSUDwelDiYFSuBzs/qdYM1JbLCogcoqeijKItPdUT01UDuRRytTkTD8oL4WBwH2EKErwAiO6HFW8kJrjQQ3XdO0xmXrmkhdiUxUQkeoC58PPhf8RchgZLH6QlcoKHqyUQeoafi7MEr6PSM6dRbPjzIoaqh/Cka8sQ0RsQenEC6H0Pi/WpSXTjJBQZ2FbUCq0KUdnuagyiOqBkIrIOS/cjmPBHOVSQPvXAgFeCrgx7vrty7UN6dbRTLbK691Rxua7d/eWdv9mdT4+kvvjNW1b0Fy+/lkDPpruHiC+onuszsfHc/+JbfWexD+7jWdNA4gBxABiAPGdmj6A+CZNH0B8k6YPIL5J0wcQ36TpA4hv0vRvbM23MefjdvxL+EkDiM9u/EBKuQfTioVQsw3qPZ0trr6sckRtzkYElZt13Ngm6+EOW3FXJgeN5b6exro/opZtLLd8E9fqjNQWVSlit40m7IQ+1e91SWiACP1+S/qrNnHNvyjNCZXoQknor6ARriDmKyB8A4TPuhlBhNd7fI51f0Qtu2Jh9IAQnZFiTSklA+H/LhB+dbgCQhxVW0C4LwGiCJjkUDNmbx8I3wIhlvtrOiNms/1AKWL+ZTZV1oCjxotvqeZygMNQMSoqvoPLyhlQ6lCWBV5x7fMvCpfhnCOlgGFBbAeuQ90rM7lXlGkS82vFawYJuXPo/W7hfHB6tIze/Ff2xllaisgUpEGw5nPU/UHtOMpvy8LIfJOOzkjeC2aJY3l3SVH0ObQV9LqwUdZwARWT98pyOeCxrRgVFd/BZeUMKHsEYVvoAOzxLwqXITgckVfFs2vUiwxDDInZIiGrbKCGsGKQpCDBONixCv0N8XwneNm1VMRN0cAaaz5H3R+F/6GTmY6UbZ0RbQ1YJZ5fH1elDokic3KI+YIIRUZZg/kPxYdduBwAwjryW77DCghmW/B58d7nXxQuQ5Aj0NEIDjoa6DJwMRRsVdmY+DtyWAbJmwIhihhgVbAmh3PZwsD1lBHR43O0/aH0CgBRWBjbOiPaGi+luKR9eVarAmK+0N/JibKGF96GzzIGlsuxBqLlOygQJA2UWRbSgD7/onAZgstEAPZbhxRLPKzZAqEqG1S7cDgqBkkNRMx0Go59YoCgesoa0eNztP1RA1FYGFd0RlyJKIMRFI1d3qQXDOzURAMZM2zN5QCHoWJUVHyHwmRwCUoWWppzW/yL4kLHlKN8hoCQM5iaxEzSrpCRIyobIag6iGWQsMaFmZpIf0NaFg0QOQyNZ62NHp+j7g87NVlNDdudtc6IRpkJot8hE+KplKPMFLA9YnCqc4GljeOUhJbLAQ5Dxaio+A6IcVIUOoRlIboVff6FaQ6n9bJohSxudRaFCwQqA8kRKhuoAawNyyDBMn+e82J9Qc3KNgHTDraeeSxRt/b4HHV/lMX6XGlqKN9krTOiUWagJEKl2MVaosiwlWzZpRuJ4/fipMTQuWxz6xI/eX/6W+Ih662vylEzU4oCDlpxl47HnzAO6MTfyn3buO8BxD12tC0az5q+iR0DiAHEAOI7Wj6AGEAMIAYQ39DyAcQAYgAxgPiGln+ZUf9sA//l5/kA4mu3ePWn2Us6wfjlWfN9cZNEy3iwigHnlPeuihQ+Bftbeu+Txe6OvDdUigHzx5oYLTcDfob5Vtuvb+f57q51m93BW0LXhiuZgwChaYh4Emn3XBztvpZuSLo3VCIQ6p7PXZHpDKGqq9pSrdRhn4+G4OvWOG+EJuo4L0W2wQIRgtusuffU0TBD0F9hnT405flVX24C4T4ExHH5jwaDj5bx0APCNUAoPMKOMM24BoTbBqLTZaFrfQNE1NEc3MeAaOu8B4iVNXiF1rl3ZmrAeeLXUU0kUgo4B/QX7IkChGdOA/nvNZVHBBJ2h2jnHcFomH2lirHUkchFA3+LvGrsClejVfXAm/2VUkfCUbxgzByJzAvJsVicKnz09DO09iO7c6Z8/DjXCiWFP4LP0lOFd4IWKN8Euc9VeaTxccwCTbRXWBXs72W/NDlhfRPVhLgJJ6O9ccpKGzUQp+BnxDJx2d8Ll6ACcQpevMWqiiEvhycv7Ai8AQ3Ps3I1WlUPrc3wShJcrgk+OFdcqdlP7fRV9FPo6Wdo7bSfLAjB6olk7Q/DHwEDo/RU4Z1wCzLfBLmDs+Wxxoco8AuPRaUfAARigfgmqgnHSAmG05FVOmogaJZFCA3HPAnvNI8CETMQtSoG+BTgRhyJE0HspMzVaFU9UFslEJGQ6k0iTgTO7Zg7UoCYMhxr/QytXWOdIEZKqRn7JznGS/w7TmDtqcI7OVZ8E/Ve23ohLaGB1vgYn7MZCIkFkuqoJhplJ/MQskrHGojzu6TSznIWCB0RFMKjVsVQdsSSM1HYC2+4Gq2qBxZrC4TGVClAuFl4IQYI5av09DNy7RLrJBSGiVNWS9BRykDEYIGIzvBOkuWbtECgt32l/cTkgVimpoCpyaU6qgkNtONctDcKb4OA8Pk/PmIkTHRqEkKWrBHMDU2tKoayIyAyBHETnWBaVQ8FovBKFIjLRYHQCaTEYrEqGWv9DK39pPS1Kh1YLUF4HtK5zMAoPWV4J9HyTTJxzpTHGh/5FJVjYCKkoBwL6QsT1YSLdUV7I+aYKxxvJNF/iJPIkUYk1VmirxCbg1MkBARjBYpoVTF4eXQZiKCkSuVqtKoelKhW6sC4AbUysC6HRkopsVh0uSzWW/0MrV0X61DpiUwSvQV9BP4IGBilpwrvhPNkvgl6DEdyS+JZanDSvruDqnSV2Ph+1Peoy6uL05YMrN+UMVGzI+LNaC1/ert1Z/3huj9Kc9hmXdwLxEe2eKVhtqy+VR/ujH8TEF9r/j9V1r+t9gHEN6l9APFNah9AfJPaBxDfpPYBxDepfQDxTWofQHyT2gcQ36T2LzPhdpnrFPfs+TOW/5l8+HVvWXe0bgDxfYAIbjtiyc0tfqapESly3rDKEzZKCVZJVp5JySvHYF/Eyq7YkQWh9+YkzQfz8bPUXHsQG+Lt7rrdXgtEN2JJ3lNvbZiLOo1W4t2GXEO6bphf7QlmfwFCntIar0OnQ1Nrd87nPpovGHkIqHhwBPic9jfaW/gLGxFLNoBwdwHhrhg2udsjYlrnrYFwFRDyPvOHgbg7n43/Ai93DcRvtLfwF/oRS95ZG0Mjf8Soe6fsdOnFXPn1S2K0uL5uBjcg1YofCGp/VGYH74npKHofypMor8gCCKhgcOh6fl13StFNmXUCbY7C/kjiHYfzSULe35HvOKtXXWK2zNDkIL8iqA6/217DX+hHLHkLEHUQDkXUvT77rnsxV2gQt1wMmwKGSW3ysjg3iX3jTt73pzR09C37fLH/JC5dnp9ZBWNxw7MGhr7hL+WwCodljCgQZ7zOH+/NF3I+l1tOQMiISOJW/o32VvyFXsSSN44EovwGMh17vYhK9WOuUJktF8Om8DztaG1gerD7PpTYEZia6CiA0CgrsMQnrG5QwbAaGFoKc0/cuWKMvEmHMs8CvuW78qluR2BljYlt4ZmEu/MsPI7faW/FX+hFLHnjSCDiLmedDAXCzTZuSq2KQSa3XIwqBQ3pixMyQHRbQMQLHZUR4UzYEadAlO6rO/QkMWJisoyRN+WLJN6T7s0X8hqBXor1Yp1oqP1eew1/oR+x5C1PR7wn6V5fsTlqVQwvJtdcjEo3gwyLULDAPpltCxDYE4OIooRQpia2JJocJ9XAKFPMkU6pE6twGMYIsSemXDdUP+7Jx8IUiVsl6h70S8ritL/bXlf4C/2IJSlo5BAsbLo3S2V1Yq7A5JaLUelmLNfVVC4YGFjmsHhF4b/JHj5Keh/KkxDFC8qZc4DJwbwIYou8Iz7LcUYNljFCwE55sfbu3nys24GpSdQ95JesU0xM+q32uvYyKrrf31ThwnIx2sgr7e2MZXEg7Vr1Avv7vA+t2XfaEW+U+5l8+LXNPfl4e78ACC3zGkPkqx9xxCtt+jP51o84frO941nT5/KNh34DiAHEAOILO3QA8Yk9A4gBxADiqzt0APGJPQOIAcQXA/F/5GwGR41R+k0AAAAASUVORK5CYII=","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":2638,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"}],"totalChars":141799,"truncatedChars":0,"text":"# User ¶█Complete the assignment below, thoroughly:█# Target█v0.36 REDISPATCH changes and tests/history available in repository. Non-goal: unrelated lifecycle code.█# Change█Assess whether stream retirement/reconstruction fixes can cause retained broker workers, replay loops, or connection churn after update/refresh. Compare intended O(active) behavior with thread/process evidence.█# Acceptance█Give a source-grounded causal assessment and targeted regression-test seam.█# Assistant ¶█_**Planning skill-based code exploration**_█# Tool call ¶█//Reading diagnosis workflow█read(path=\"skill://diagnose\", selector=null)█<out>█\u000e---█name: diagnose█description: 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.█---█# Diagnose█A discipline for hard bugs. Skip phases only when explicitly justified.█When exploring the codebase, use the project's domain glossary to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.█## Phase 1 - Build a feedback loop█**This is the skill.** Everything else is mechanical. If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause - bisection, hypothesis-testing, and instrumentation all just consume that signal. If you don't have one, no amount of staring at code will save you.█Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**█### Ways to construct one - try them in roughly this order█1. **Failing test** at whatever seam reaches the bug - unit, integration, e2e.█2. **Curl / HTTP script** against a running dev server [...5116ch elided...] # Phase 6 - Cleanup + post-mortem█Required before declaring done:█- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)█- [ ] Regression test passes (or absence of seam is documented)█- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)█- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)█- [ ] The hypothesis that turned out correct is stated in the commit / PR message - so the next debugger learns█**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before - you have more information now than when you started.█\u000f█</out>█# Tool call ¶█//Locating redispatch changes█grep(pattern=\"REDISPATCH|redispatch|re-dispatch|redispatching\", path=\".\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000eGrep timed out after 30s; narrow paths or pattern, or scope with `glob` first\u000f█</out>█# Tool call ¶█//Mapping broker sources█glob(path=\"**/*{broker,stream,dispatch,worker}*\", hidden=true, gitignore=true, limit=300)█<out>█\u000e# crates/█## spt-daemon/█### src/█dispatch.rs█broker.rs█### tests/█redispatch_stall.rs█broker.rs█redispatch.rs█net_worker_starve.rs█netstream.rs█netbroker.rs█dispatch.rs█#### fixtures/█dispatch_fixture.rs█## spt/█### src/api/█worker.rs█### tests/█worker_visibility_e2e.rs█worker_lifecycle_e2e.rs█## spt-store/src/█worker_reap.rs█worker_seq.rs█## spt-term/█### tests/█stream.rs█### src/█stream.rs█# docs/█## adr/█0038-broker-stream-lifecycle-truth.md█0018-broker-brain-process-isolation-restoration.md█0004-single-daemon-broker-brain-split-and-self-update.md█## spikes/█SPIKE-01-broker-handoff.md\u000f█</out>█# Tool call ¶█//Mapping repository history█glob(path=\".git/{logs,refs}/**/*\", hidden=true, gitignore=false, limit=300)█<out>█\u000e# .git/█## logs/█HEAD█### refs/█#### remotes/█##### origin/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█gateway-liveness█pump-w2-design█lifecycle-truth█###### docs/█v0360-notes-plain-language█readme-install-channel█reorg-plans-process█###### fix/█redispatch-stall-w1-replay-order█changelog-0330-heading█update-wedge-bounded-conn-write█inject-settle-rearm█###### release/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.30.6█v0.30.5█v0.30.4█v0.30.2█v0.30.1█0.29.1█###### build/█redispatch-stall-w1█mesh-recovery-w1█redispatch-truth-w1█project-index-w3-reader-cutover█project-index-w2-daemon-writer█project-index-w1-identity-store█###### triage/█update-wedge-rca-trace█musl-tier█###### test/█uwedge-r4-stale-guard-swap█###### release-merge/█uwedge-r4█###### feat/█musl-w3-release-artifact█musl-w1-platform-registry█###### spec/█brain-handoff-pilot█###### rca/█pump-w1-fastfail█##### mirror/█main█#### heads/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█pump-w2-design█pump-w1-fix█musl-w2█musl-w1█gate-inject-rearm█##### docs/█v0360-notes-plain-language█reorg-plans-process█##### fix/█update-wedge-bounded-conn-write█inject-settle-rearm█##### [...1170ch elided...] 1-fastfail█#### mirror/█main█### heads/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█pump-w2-design█pump-w1-fix█musl-w2█musl-w1█gate-inject-rearm█#### docs/█v0360-notes-plain-language█reorg-plans-process█#### fix/█update-wedge-bounded-conn-write█inject-settle-rearm█#### release/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.30.4█0.29.1█#### triage/█update-wedge-rca-trace█update-wedge-rca-capture█musl-tier█#### test/█uwedge-r4-stale-guard-swap█#### release-merge/█uwedge-r4█#### spec/█brain-handoff-pilot█#### rca/█pump-w1-fastfail█#### feat/█musl-w3-release-artifact█musl-w1-platform-registry█### tags/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.31.0█v0.30.6█v0.30.5█v0.30.4█v0.30.3█v0.30.2█v0.30.1█v0.30.0█v0.29.1█[200 results limit reached. Use limit=400 for more]\u000f█</out>█# Assistant ¶█_**Planning detailed daemon inspection**_█# Tool call ¶█//Tracing retirement semantics█grep(pattern=\"retir|reconstruct|replay|redispatch|spawn|thread|worker|connection\", path=\"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/█## src/█### broker.rs█4|//! would lose continuity on if the logic restarted: the PTY master+writer and█*5|//! the spawned child (both via [`spt_term::PtySession`]), and the accepted█6|//! client / listening sockets. It runs **no logic** - no routing, registry,█*7|//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,█8|//! forward its output to the brain, inject the brain's input, report exit.█9|//!█10|//! ## Gapless handoff (B2 - the load-bearing invariant)█...█18|//! [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held█*19|//! in a bounded ring. The current subscriber (a brain connection) receives█20|//! chunks live; all subscriber writes happen *under the log lock*, so frames█21|//! reach the brain in strict `seq` order with no interleave.█22|//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its█*23|//! last-seen `seq + 1`; the broker replays the ring from there, then streams█24|//! live. The brain dedups by `seq`. Output produced *while no brain was█*25|//! attached* sits in the ring and is replayed on the next subscribe - so a█26|//! brain restart loses nothing. [...15272ch elided...] thread::spawn(move || {█89| let _ = serve.serve();█90| });█91| broker█...█98| }█*99| thread::sleep(Duration::from_millis(10));█100| }█101| panic!(\"brain could not connect\");█102|}█103|█*104|fn echo_spawn_req(endpoint: &str) -> SpawnReq {█105| #[cfg(unix)]█106| let (program, args) = (\"cat\".to_string(), Vec::<String>::new());█107| #[cfg(windows)]█108| let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);█*109| SpawnReq {█110| program,█111| args,█112| rows: 24,█...█122|█*123|/// One REAL dispatcher generation (the redispatch.rs harness): the█124|/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =█125|/// brain death. Stderr lands in `log`.█126|struct Generation {\u000f█</out>█# Tool call ¶█//Reading stream lifecycle ADR█read(path=\"docs/adr/0038-broker-stream-lifecycle-truth.md\", selector=null)█<out>█\u000e# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)█- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL - see Amendment below)**█- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█## Context█The broker's `NetShared.streams` table is append-only in practice: rows are█inserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF -█no removal or retirement path exists. Stream *classification* is derived at█redispatch time by peeking ring seq 0, but the `StreamLog` data ring is█bounded (4096 transport chunks) and evicts the protocol opener under█traffic. Dispatcher claims are a per-process one-shot `HashSet` insert█performed before worker spawn, never cleared or retried.█After any brain cycle (`spt daemon refresh`, `spt update` apply - same█path), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY█broker-held peer stream, including finished historical attach streams. A█replayed historical Attach silently steals the live controller█(same-identity `become_controller`, no `Displaced`) and its replayed EOF█[...5665ch elided...] ver█detach a replacement controller. This also covers the UNFINISHED-stale-row█steal shape (raw-close no-FIN viewports) that Decision 1's finished-row█retirement cannot see.█6. **(new - dispatcher hygiene + telemetry)** The redispatch worker pool is█bounded with batched cold enumeration and NO claim/sessions locks held█across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12█kin); dispatch telemetry is keyed per█gen/stream/family/endpoint/attempt/conn with█replay/poison/cancel/worker-outcome events and gauges.█Rejected here: reverting retryable claims (restores permanent abandonment);█widening any write deadline (hides the composition instead of removing it);█tearing down all subscribers on any poison (punishes healthy streams for a█wedged conn).█\u000f█</out>█# Tool call ¶█//Mapping broker declarations█read(path=\"crates/spt-daemon/src/broker.rs\", selector=null)█<out>█\u000e//! The broker kernel (B1 + B2) - the stable half of the broker/brain split.█//!█//! Per ADR-0004 §B the broker holds **only** the resources a live consumer█//! would lose continuity on if the logic restarted: the PTY master+writer and█//! the spawned child (both via [`spt_term::PtySession`]), and the accepted█//! client / listening sockets. It runs **no logic** - no routing, registry,█//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,█//! forward its output to the brain, inject the brain's input, report exit.█//!█//! ## Gapless handoff (B2 - the load-bearing invariant)█//!█//! A brain may be killed and restarted at any moment (a routine self-update, a█//! crash). The broker - and every hosted child - must survive that untouched,█//! and the new brain must re-attach to the output stream **gaplessly and█//! exactly-once** (Spike #1 made real). Two mechanisms:█//!█//! - **Sequenced, buffered output.** Each session's output is appended to an█//! [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held█//! in a bounded ring. The current subscriber (a brain connection) receives█//! chunks live; all subscriber writes happen *under the log l [...14785ch elided...] .█const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);█/// The effective commit deadline. Production is the const above; the int gate█/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`█/// so the structural outcome (floor releases -> buffered input flushes -> binary█/// faults) is asserted in well under a second rather than a 5 s wall-clock wait -█/// the deadline's PRESENCE is the invariant, not its exact length.█fn inject_commit_deadline() -> Duration {█match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {█Ok(ms) => ms█.parse::<u64>()█.map(Duration::from_millis)█.unwrap_or(INJECT_COMMIT_DEADLINE),█Err(_) => INJECT_COMMIT_DEADLINE,█}█}█[Showing lines 1-300 of 6298. Use :301 to continue]\u000f█</out>█# Tool call ¶█//Mapping dispatch declarations█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=null)█<out>█\u000e//! Inbound net-stream dispatch (D9-1) - the daemon-side acceptor that turns█//! the hand-wired serve loops of D5-D8 into production behavior.█//!█//! Every E2E test so far drove the serve half manually (a probe brain calling█//! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against█//! a known stream). In production nothing did - a peer could dial and write█//! and the bytes sat in the broker's stream log unread. This module is the█//! missing consumer: a daemon-hosted loop that watches the broker's stream█//! table for **peer-initiated** streams, classifies each by its first NDJSON█//! record, and hands it to the right serve loop on its own worker thread.█//!█//! ## Direction (the gotcha-#9 fact, now first-class)█//!█//! The stream table holds locally-opened outbound streams too. The dispatcher█//! claims only rows with `initiated_locally == false` - serving our own█//! requester stream would deadlock the requester against ourselves. The flag█//! is broker-recorded at registration (`open_stream` vs the accept loop),█//! never inferred.█//!█//! ## Classification (first-record demux)█//!█//! Streams are single-purpose; the opener's first record names the protoc [...11307ch elided...] leteness is a unit fact.█fn dispatch_event(█gen: u64,█stream_id: u64,█family: Option<StreamFamily>,█attempt: u32,█conn_id: u64,█event: &str,█detail: &str,█) -> String {█format!(█\"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",█family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),█)█}█/// PURE claim decision: may this row be (re)claimed now? `Some(prior█/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing█/// off). The unit-tested seam for the no-hot-loop ruling.█// [impl->REQ-DISPATCH-CLAIM-RETRY]█fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {█...█}█[Showing lines 1-300 of 1558. Use :301 to continue]\u000f█</out>█# Tool call ¶█//Mapping redispatch tests█read(path=\"crates/spt-daemon/tests/redispatch.rs\", selector=null)█<out>█\u000e//! REDISPATCH-TRUTH W1 - the three production-path regressions (ADR-0038 D,█//! KNOWN-HAZARDS 7.41): every leg drives the REAL█//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child -█//! a dispatcher GENERATION whose death drops its workers' broker conns█//! exactly like a production brain death). NO manual re-serve anywhere: the█//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)█//! hand-stepped a known stream and bypassed exactly the rediscovery/█//! classification seam that was broken in the field.█//!█//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same█//! endpoint/origin; restart the dispatcher generation only -> the█//! historical stream neither takes nor clears the current controller█//! (post-restart input still renders; no EOF; no replay-storm log line;█//! the retired row left the broker's enumeration).█//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;█//! restart the dispatcher -> durable classification (the pinned opener)█//! resumes the SAME operator stream.█//! - D3: ONE injected transient worker-start failure -> the claim requeues█//! and the retry serves the stream (no perman [...10370ch elided...] ical detach cleared it (the 7.41 steal)\"█);█let painted = screen_of(&view2);█assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");█assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");█// The fresh generation never touched the historical stream id - the█// DISPATCH:<id> replay-storm line (the field evidence class) must be█// absent for s1, while s2's re-serve is expected work, not noise.█let gen2_log = gen2.stderr_log();█assert!(█!gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),█\"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"█);█let _ = s2_a;█gen2.kill();█let _ = probe.kill_session();█}█[Showing lines 1-300 of 621. Use :301 to continue]\u000f█</out>█# Tool call ¶█//Mapping stall regression tests█read(path=\"crates/spt-daemon/tests/redispatch_stall.rs\", selector=null)█<out>█\u000e//! REDISPATCH-STALL W1 - the seven-leg production-path matrix (ADR-0038█//! Amendment, KNOWN-HAZARDS 7.43, docs/REDISPATCH-STALL-TRIAGE.md): the v0.34█//! field stalls (20-30s PTY/RC freezes, 17-62s DISPATCH tails) were claim█//! retries × the broad `Err(_)` opener fallback manufacturing throwaway peek█//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the█//! per-stream mutex against wedged conns - serial 15s bounded-write poison█//! windows (33 observed, all 15,000-15,154ms).█//!█//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns█//! through the production broker subscribe path - gate ruling 1):█//! - T1+T3 `wedged_subscriber_...`: replay + live fan-out halt bounded at█//! the first failed write; the producer stays FLAT (never a serial poison█//! composition); recovery replays gapless from the cursor.█//! - T2 `opener_transient_...`: a transport-class opener failure requeues█//! bounded and NEVER creates a peek subscriber (pre-fix: each retry█//! subscribed + replayed against the conn).█//! - T4 `poisoned_subscriber_cancels_...`: subscriber poison cancels the█//! stream's serve lease - the paired forwarding le [...9734ch elided...] yte-exact, never over\");█return got;█}█}█Ok(BrokerEvent::Error { message }) => {█// The prior poisoned seat may still be draining - the█// no-reinstall gate refuses busy; retry on a fresh conn.█assert!(█message.contains(\"subscriber busy\"),█\"unexpected error while re-attaching: {message}\"█);█thread::sleep(Duration::from_millis(200));█continue 'attach;█}█Ok(_) => {}█Err(e) => panic!(\"recovery subscriber read failed: {e}\"),█}█}█}█}█[Showing lines 1-300 of 799. Use :301 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch branch history█read(path=\".git/logs/refs/remotes/origin/fix/redispatch-stall-w1-replay-order\", selector=\"raw\")█<out>█\u000e0000000000000000000000000000000000000000 fc2579964647cfbf26d26bf045b20c162cb44b54 Reavo End <decidel@gmail.com> 1784261859 -0700 update by push█fc2579964647cfbf26d26bf045b20c162cb44b54 f4727cdd3da014fcd8097cbba3c63203fb6b38b2 Reavo End <decidel@gmail.com> 1784262306 -0700 update by push█\u000f█</out>█# Tool call ¶█//Reading redispatch build history█read(path=\".git/logs/refs/remotes/origin/build/redispatch-truth-w1\", selector=\"raw\")█<out>█\u000e0000000000000000000000000000000000000000 ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200461 -0700 update by push█ad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201519 -0700 update by push█2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204306 -0700 update by push█84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204465 -0700 update by push█\u000f█</out>█# Tool call ¶█//Reading v036 release history█read(path=\".git/logs/refs/remotes/origin/release/v0.36.0\", selector=\"raw\")█<out>█\u000e0000000000000000000000000000000000000000 b677030328cf7684d2b89076e8b03858d3ae0661 Reavo End <decidel@gmail.com> 1784258640 -0700 update by push█\u000f█</out>█# Assistant ¶█_**Planning inspection of dispatch core loop**_█# Tool call ¶█//Locating dispatcher loop█grep(pattern=\"fn should_claim|fn dispatch_worker|run_dispatch_loop|thread::spawn|net_streams|net_stream_retire|ClaimState::|CircuitBreaker\", path=\"crates/spt-daemon/src/dispatch.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 246|#[derive(Debug, Default)]█*247|struct CircuitBreaker {█248| until: Option<Instant>,█249| consecutive: u32,█250|}█...█254|█*255|impl CircuitBreaker {█256| /// A seat-blocked failure landed: open (or extend) the window.█257| fn trip(&mut self, now: Instant) -> Duration {█258| let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);█...█299|// [impl->REQ-DISPATCH-CLAIM-RETRY]█*300|fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {█301| match state {█302| None => Some(0),█*303| Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),█304| _ => None,█305| }█306|}█...█324| match outcome {█*325| DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,█326| DispatchOutcome::Failed(msg) => match classify_failure(msg) {█*327| FailClass::SeatBlocked => ClaimState::Retry {█328| attempts,█329| next_at: now + breaker_window,█330| },█...█333| if attempts >= MAX_DISPATCH_ATTEMPTS {█*334| ClaimState::Terminal█335| [...3665ch elided...] , ClaimState::Terminal);█1311| }█1312|█1313| // [unit->REQ-DISPATCH-FALLBACK-CIRCUIT] the failure classification table:█...█1379| let now = Instant::now();█*1380| let mut b = CircuitBreaker::default();█1381| assert!(!b.open(now), \"a fresh breaker is closed\");█1382|█1383| // Trip #1: base window; open inside, closed at its end.█...█1421| s,█*1422| ClaimState::Retry {█1423| attempts: MAX_DISPATCH_ATTEMPTS - 1,█1424| next_at: now + window█1425| },█...█1434| outcome_transition(&transient, MAX_DISPATCH_ATTEMPTS - 1, now, window),█*1435| ClaimState::Terminal,█1436| \"a transient at the budget edge still exhausts (D3 unchanged)\"█1437| );█1438| }\u000f█</out>█# Tool call ¶█//Locating stream lifecycle implementation█grep(pattern=\"struct StreamLog|struct StreamSubscriber|fn attach|fn append|fn finish|fn retire|retire_stream|stream_opener|streams\\\\(|thread::spawn|subscriber busy|replay halted|lease\", path=\"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/brain.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/█## nethost.rs█130|/// either, but only a POISON (real write failure on the conn) cancels the█*131|/// stream's lease; an overflow is a slow-but-healthy brain being detached.█132|// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█133|#[derive(Debug, Clone, Copy, PartialEq, Eq)]█134|enum SeatFail {█...█174| /// re-serve \"got seq 134 want 6\" CI red.)█*175| /// `lease` (StreamLog seats; `None` for presence) is the stream's serve█*176| /// lease AT INSTALL: the writer cancels it the moment a write fails, so█177| /// the paired serve/wire worker's next forward refuses PROMPTLY - even on█178| /// a traffic-quiet stream where no producer enqueue would ever run█179| /// `drop_seat` (a mid-replay poison with no follow-on appends previously█*180| /// left the lease alive until the next append; the T4 forwarder starved).█181| /// A LATE cancel from a superseded writer - poisoned OR healthily█182| /// displaced - is harmless by construction: every install mints a fresh█*183| /// lease Arc, so an old writer cancels only its own generation's Arc.█184| // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█185| fn install(█186| s [...13212ch elided...] rs the controller only if its generation still matches `gen` - a█1682| /// STALE worker's late detach can never clear a replacement controller.█1683| // [impl->REQ-SERVE-OWNERSHIP-GENERATION]█...█1919| let brain_handle =█*1920| thread::spawn(move || Brain::cold_start(&connect_name, 1).expect(\"brain cold_start\"));█1921|█1922| let mut server = listener.accept().expect(\"accept the brain\");█1923| crate::transport::recv_hello(&mut server, Role::Brain).expect(\"stub recv_hello\");█...█1931| let (feed, frames) = sync_channel::<Envelope>(64);█*1932| let server_thread = thread::spawn(move || {█1933| while let Ok(env) = frames.recv() {█1934| if write_frame(&mut send, &env).is_err() {█1935| break;\u000f█</out>█# Tool call ¶█//Locating regression assertions█grep(pattern=\"fn (finished|active|transient|wedged|opener|poisoned|seat|stale|old_broker)|DISPATCH_EV|attempt|replay|thread|net_streams|retir\", path=\"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## redispatch.rs█12|//! historical stream neither takes nor clears the current controller█*13|//! (post-restart input still renders; no EOF; no replay-storm log line;█*14|//! the retired row left the broker's enumeration).█15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;█16|//! restart the dispatcher -> durable classification (the pinned opener)█17|//! resumes the SAME operator stream.█...█27|use std::sync::{Arc, OnceLock};█*28|use std::thread;█29|use std::time::{Duration, Instant};█30|█31|use spt_daemon::attach::{request_attach, send_attach_input};█...█73| let serve = Arc::clone(&broker);█*74| thread::spawn(move || {█75| let _ = serve.serve();█76| });█77| broker█...█84| }█*85| thread::sleep(Duration::from_millis(10));█86| }█87| panic!(\"brain could not connect\");█88|}█...█110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death█*111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.█112|struct Generation {█113| child: Child,█114| log: std::path::PathBuf,█...█144| probe█*145| .net_streams()█146| [...6250ch elided...] 310|// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█*311|// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the█312|// production broker subscribe path: a REAL subscriber conn that attaches with█*313|// a full ring and never reads - its seat's replay wedges on the named-pipe█314|// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat█315|// is removed, later appends never re-feed it, and the PRODUCER - B's ack'd█*316|// sends through A's read pump - stays flat throughout (pre-fix: the replay█317|// ran under the stream mutex with discarded errors, one 400ms poison window█318|// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh█*319|// subscriber replays everything gapless from the cursor - no abandonment.█320|#[test]\u000f█</out>█# Tool call ¶█//Locating release documentation█grep(pattern=\"REDISPATCH|0\\\\.36\\\\.0|redispatch\", path=\"CHANGELOG.md; docs; .planning\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CHANGELOG.md█11|█*12|## [0.36.0] - 2026-07-16█13|█14|A stability release for attached sessions. A daemon update no longer disrupts█15|active sessions, and a session stuck by a bad connection now recovers on its own.█# docs/█## KNOWN-HAZARDS.md█664|█*665|### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream - historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`█*666|<!-- [doc->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] -->█667|- **Failure (paid-for, hertz field RCA 2026-07-16 - 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply - same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter -> the fresh dispatcher replays FINISHED historical attach streams. A repl...█*668|- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); cla [...7477ch elided...] 6|<!-- [doc->REQ-SERVE-OWNERSHIP-GENERATION] -->█...█80| day, both hours apart); RCA summary█*81| `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`; hazard█*82| KNOWN-HAZARDS 7.43; build shape `docs/REDISPATCH-STALL-TRIAGE.md`.█83|█84|The W1 machinery this ADR shipped is field-correct on its own terms (D1/D1b█85|hold: finished rows never serve) but two of its legs COMPOSE into a stall█...█121| retirement cannot see.█*122|6. **(new - dispatcher hygiene + telemetry)** The redispatch worker pool is█123| bounded with batched cold enumeration and NO claim/sessions locks held█124| across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12█125| kin); dispatch telemetry is keyed per█Skipped missing paths: .planning█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading current branch history█read(path=\".git/logs/HEAD\", selector=\"raw:1-200\")█<out>█\u000e0000000000000000000000000000000000000000 9515e2ea26ea2b081e1f51f4abb0be4f718a96ee Reavo End <decidel@gmail.com> 1780222436 -0700 commit (initial): docs: initial spt-core planning artifacts█9515e2ea26ea2b081e1f51f4abb0be4f718a96ee 1d30876dbb90d1df77f434d26604528adaeeccb6 Reavo End <decidel@gmail.com> 1780224876 -0700 commit: docs: shell model, sleep/wake, and from-shell owner-shutdown█1d30876dbb90d1df77f434d26604528adaeeccb6 293e33774dc77b4f2e80cd2a56a921624776ed72 Reavo End <decidel@gmail.com> 1780227620 -0700 commit: docs: cross-node context sync (P2P + opt-in GitHub hub)█293e33774dc77b4f2e80cd2a56a921624776ed72 b73269976256fd4ecf67bd8222a9f71b32b7b775 Reavo End <decidel@gmail.com> 1780228153 -0700 commit: docs: dual-audience documentation strategy█b73269976256fd4ecf67bd8222a9f71b32b7b775 9d064c2f5a9b44161b9c55fc1c1b74d14204b81a Reavo End <decidel@gmail.com> 1780229594 -0700 commit: docs: concrete doc templates from firsthand artifact pass█9d064c2f5a9b44161b9c55fc1c1b74d14204b81a a71c14559c221b0841f53d6180d410e119e6c2c9 Reavo End <decidel@gmail.com> 1780273857 -0700 commit: docs: psyche context & memory model (17.3 + memformat redesign)█a71c14559c221b0841f53d6180d410e119e6c2c9 953 [...39701ch elided...] End <decidel@gmail.com> 1780583853 -0700 commit: docs(reqs,plan): M4-D9-6 complete - rig proof evidence, int stages live, gh-interim retired█2ba79061f05c5b273485fac83671cf2bdbc4681d becb6ecb2a5458879c3c4f77139cd1ec31e39a36 Reavo End <decidel@gmail.com> 1780584205 -0700 commit: docs(plan,reqs): M4-D9-7 closeout sweep - M4 DELIVERED█becb6ecb2a5458879c3c4f77139cd1ec31e39a36 a6f5cabac5f0a8d340a5aa81206f94e096d215a5 Reavo End <decidel@gmail.com> 1780586451 -0700 commit: docs(plan): author M5-PLAN - scope locked at M5 start via user forks█a6f5cabac5f0a8d340a5aa81206f94e096d215a5 2a59e7d356d6329b2b35e25095ec355ee384fa18 Reavo End <decidel@gmail.com> 1780586580 -0700 commit: docs(reqs,roadmap): M5-D0 - scope alignment + requirement registry prep█[Showing lines 1-203 of 1502. Use :204 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch build ref█read(path=\".git/refs/remotes/origin/build/redispatch-stall-w1\", selector=\"raw\")█<out>█\u000e73b29b858bbffb2c758382c80d74def0aabb328f█\u000f█</out>█# Tool call ¶█//Reading release tag ref█read(path=\".git/refs/tags/v0.36.0\", selector=\"raw\")█<out>█\u000efa3e474783a6e3ebbb178a478f03e4ebc8ce3928█\u000f█</out>█# Tool call ¶█//Reading main ref█read(path=\".git/refs/heads/main\", selector=\"raw\")█<out>█\u000efa3e474783a6e3ebbb178a478f03e4ebc8ce3928█\u000f█</out>█# Assistant ¶█_**Planning line-exact dispatch loop review**_█# Tool call ¶█//Reading dispatcher claim loop█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=\"290-560\")█<out>█\u000efn dispatch_event(█...█) -> String {█format!(█\"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",█family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),█)█}█/// PURE claim decision: may this row be (re)claimed now? `Some(prior█/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing█/// off). The unit-tested seam for the no-hot-loop ruling.█// [impl->REQ-DISPATCH-CLAIM-RETRY]█fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {█match state {█None => Some(0),█Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),█_ => None,█}█}█/// PURE outcome transition: what a finished worker's claim becomes. Served█/// and Unknown (unclassifiable) are TERMINAL - a terminal classification never█/// hot-loops. A failure requeues with backoff until the bounded budget is█/// spent, then goes terminal.█/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget -█/// its pacing comes from the global breaker window (`breaker_window`), and█/// the stream recovers once the wedged se [...11516ch elided...] cked =>█{█let mut b = breaker_done.lock().unwrap();█let window = b.trip(Instant::now());█eprintln!(█\"{}\",█dispatch_event(█generation,█stream_id,█None,█attempts,█conn_id,█\"breaker-trip\",█&format!(\"window_ms={}\", window.as_millis()),█)█);█...█}█...█};█...█});█}█...█}█...█}█[Showing lines 289-563 of 1558. Use :564 to continue]\u000f█</out>█# Tool call ¶█//Reading dispatcher worker path█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=\"560-900\")█<out>█\u000epub fn run_dispatch_loop(█...█) -> io::Result<()> {█...█while !stop.load(Ordering::Relaxed) {█...█for s in reply.streams {█...█thread::spawn(move || {█...█let breaker_window = match &outcome {█DispatchOutcome::Failed(msg)█...█{█...█eprintln!(█...█dispatch_event(█...█None,█attempts,█conn_id,█\"breaker-trip\",█&format!(\"window_ms={}\", window.as_millis()),█)█);█window█}█_ => Duration::ZERO,█};█let mut c = claims_done.lock().unwrap();█c.insert(█stream_id,█outcome_transition(&outcome, attempts, Instant::now(), breaker_window),█);█});█}█thread::sleep(poll);█}█Ok(())█}█/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream█/// table claims at most [...13287ch elided...] &endpoint,█origin,█floor_seq,█remote_session,█serde_json::from_value(line.clone()).ok(),█) {█Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),█Err(e) => DispatchOutcome::Failed(e.to_string()),█},█None => {█// Refuse politely: finish our send side, claim stays.█let _ = brain.net_stream_send(stream_id, &[], None, true);█DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())█}█}█}█StreamFamily::Xfer => {█...█}█...█};█...█}█[Showing lines 559-903 of 1558. Use :904 to continue]\u000f█</out>█# Tool call ¶█//Reading stream log core█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"100-430\")█<out>█\u000epub struct NetConfig {█...█/// Live source of this node's subnet credentials for the connect-time█/// seed-proof (Mesh-D2, REQ-MESH-1). `Some` enables the mutual proof at█/// every dial + accept (fail -> drop); `None` disables it entirely - both█/// peers must agree, or the side expecting the control stream would hang.█/// Production wires `Some(SubnetStore::load ...)`; mechanics-only tests that█/// don't exercise membership pass `None`.█pub membership: Option<MembershipSource>,█}█/// Default bounded stream-read ring depth (chunks) - same sizing rationale as█/// the PTY `OutputLog`: large enough that a brain-restart window never evicts█/// unreplayed chunks in practice; bounded so an unsubscribed stream can't█/// buffer without limit.█const DEFAULT_STREAM_RING_CHUNKS: usize = 4096;█/// Max bytes pulled per stream read (one ring chunk).█const STREAM_READ_CHUNK: usize = 64 * 1024;█/// Max bytes buffered while hunting the opener's first newline (ADR-0038 B).█/// A protocol opener is one small NDJSON line; a stream whose first 64KiB has█/// no newline is not speaking an NDJSON protocol - capture gives up█/// (`Oversize`) and classification treats it as unclass [...14445ch elided...] Capture::Buffering(buf) = &mut self.opener {█match bytes.iter().position(|&b| b == b'\\n') {█Some(nl) => {█buf.extend_from_slice(&bytes[..nl]);█let line = std::mem::take(buf);█self.opener = if line.len() > OPENER_PIN_MAX {█OpenerCapture::Oversize█} else {█OpenerCapture::Pinned(line)█};█}█None => {█buf.extend_from_slice(bytes);█if buf.len() > OPENER_PIN_MAX {█self.opener = OpenerCapture::Oversize;█}█}█}█}█...█}█...█}█[Showing lines 99-433 of 2865. Use :434 to continue]\u000f█</out>█# Tool call ¶█//Reading stream subscriber methods█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"430-760\")█<out>█\u000eimpl StreamLog {█...█fn append(&mut self, bytes: &[u8]) {█...█if let OpenerCapture::Buffering(buf) = &mut self.opener {█match bytes.iter().position(|&b| b == b'\\n') {█Some(nl) => {█...█}█None => {█buf.extend_from_slice(bytes);█if buf.len() > OPENER_PIN_MAX {█self.opener = OpenerCapture::Oversize;█}█}█}█}█let seq = self.next_seq;█self.next_seq += 1;█self.ring.push_back((seq, bytes.to_vec()));█if self.retentive_cap == 0 {█while self.ring.len() > self.cap_chunks {█self.ring.pop_front();█}█}█if let Some(seat) = &self.subscriber {█let frame = net_stream_data_envelope(self.stream_id, seq, bytes);█// ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on█// the seat's writer thread, outside this mutex - a wedged█// subscriber can no longer park this producer OR the lock.█// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█if let Err(fail) = seat.e [...13180ch elided...]█/// Write all `bytes` (no-op on empty). Runs on the host runtime.█async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {█match self {█SendHalf::Quic(s) => s█.write_all(bytes)█.await█.map_err(|e| io::Error::other(e.to_string())),█SendHalf::Loopback(w) => w.write_all(bytes).await,█}█}█/// Close the send side so the peer's read pump sees a clean end (QUIC█/// `finish` / duplex `shutdown`).█async fn finish(&mut self) -> io::Result<()> {█match self {█SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),█SendHalf::Loopback(w) => w.shutdown().await,█}█}█}█[Showing lines 429-763 of 2865. Use :764 to continue]\u000f█</out>█# Tool call ¶█//Reading lifecycle broker commands█read(path=\"crates/spt-daemon/src/broker.rs\", selector=\"4000-4320\")█<out>█\u000e fn dispatch_net_dial(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {█...█match req.op_id {█None => {█...█let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;█let observed = host.conn_remote_addr_json(conn_id);█send_frame(█send,█&net_dialed_envelope(conn_id, remote, None, true, observed),█);█Ok(())█}█Some(op) => {█let minter = req.minter.unwrap_or(Minter::Legacy);█let key = EffectKey {█class: NET_EFFECT_SESSION,█minter,█op,█};█let outcome = self█.journal█.apply_once(key, EffectKind::NetDial, || {█let (conn_id, _) = host.dial(addr)?;█host.record_dial_op(minter, op, conn_id);█Ok(())█})█.map_err(|e| format!(\"net-dial failed: {e}\"))?;█// Frozen wire-compat text, single-sourced from the shared marker so█[...13649ch elided...] 4b retry matches one source of truth (ADR-0034 Addendum 2).█let stream_id = host█.stream_op_id(minter, op)█.ok_or_else(crate::effect::stream_open_no_longer_held_message)?;█let applied_now = outcome == Outcome::Applied;█send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));█Ok(())█}█}█}█/// Write bytes to a held stream's send side (D4b). With an `op_id` the█/// write is exactly-once through the broker-owned journal (a replayed█/// chunk after a brain crash is not written twice - Spike #3 invariant D█/// on the send path) and acked with `net-sent`; without one it is a direct█[Showing lines 3999-4323 of 6298. Use :4324 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch remaining tests█read(path=\"crates/spt-daemon/tests/redispatch.rs\", selector=\"300-621\")█<out>█\u000efn finished_historical_attach_neither_takes_nor_clears_current_controller() {█...█gen2.kill();█let _ = probe.kill_session();█}█// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) - the█// finished-UNRETIRED orderings the Served-report path can never see: the█// pre-existing backlog and the brain-down-detach window. s1 is attached and█// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the█// operator detaches (the row finishes with no dispatcher alive; the conn█// stays open, so the conn-close sweep does not fire either). gen2 starts and█// serves a fresh current attach: the finished s1 row must be retired█// UNSERVED (the family-aware terminal check - pre-fix, the pinned opener█// made this replay PERFECTLY: seeded attach_as steals the current█// controller, replayed input re-types, replayed EOF clears). The current█// viewport keeps flowing across several polls, sees no EOF, and gen2's log█// never carries a DISPATCH:<s1> line; the s1 row leaves the table without█// ever being served (retire-on-sight drains the backlog).█// [int->REQ-REDISPATCH-FINISHED-RETIRE]█#[test]█fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {█[...12255ch elided...] erator.net_stream_subscribe(s1, 0).expect(\"subscribe\");█send_attach_input(&mut operator, s1, b\"RETRY-MARK\\r\", 10).expect(\"input\");█// Through the injected failure + the claim retry: the marker renders.█let mut dec = AttachDecoder::new();█let (mut cur, mut view) = (0u64, Vec::new());█render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"RETRY-MARK\", Duration::from_secs(30));█// The injection actually fired (this is what distinguishes \"recovered█// through retry\" from \"nothing ever failed\").█let log = gen1.stderr_log();█assert!(█log.contains(\"injected transient worker-start failure\"),█\"the transient-failure valve never fired - the retry path was not exercised: {log}\"█);█gen1.kill();█let _ = probe.kill_session();█}█\u000f█</out>█# Tool call ¶█//Reading stall test cases█read(path=\"crates/spt-daemon/tests/redispatch_stall.rs\", selector=\"300-799\")█<out>█\u000efn drain_gapless(name_a: &str, sid_a: u64, want_bytes: usize, deadline: Duration) -> Vec<u8> {█...█'attach: loop {█...█loop {█...█match f.read_event() {█...█Err(e) => panic!(\"recovery subscriber read failed: {e}\"),█}█}█}█}█const FLOOD_CHUNK: usize = 32 * 1024;█const FLOOD_PRE: usize = 48; // chunks before the wedged subscriber attaches█const FLOOD_POST: usize = 48; // chunks appended WHILE the subscriber is wedged█// [int->REQ-HAZARD-REDISPATCH-STALL]█// [int->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█// T1 (poisoned-replay halt) + T3 (producer latency flat), one incident on the█// production broker subscribe path: a REAL subscriber conn that attaches with█// a full ring and never reads - its seat's replay wedges on the named-pipe█// write, fails at the (shrunk, 400ms) bounded deadline, and HALTS: the seat█// is removed, later appends never re-feed it, and the PRODUCER - B's ack'd█// sends through A's read pump - stays flat throughout (pre-fix: the replay█// ran under the stream mutex with discarded errors, one 400ms poison window█// PER RETAINED FRAME ≈ 20s+ of producer stall right here). Recovery: a fresh█// subscriber replays [...20941ch elided...] through the peek: classification had ONLY the ring to read.█let mut dec = AttachDecoder::new();█let (mut cur, mut view) = (0u64, Vec::new());█let eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"N1-MARK\", Duration::from_secs(30));█assert!(!eof, \"the N-1 fallback serve must not tear the stream down\");█// And it served FIRST TRY - the fallback is a clean path, not a█// fail-then-retry accident.█let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");█let log = gen.stderr_log();█assert!(█!log.contains(&format!(\"DISPATCH:{s1_a}:Failed\")),█\"the unsupported-verb answer must route straight to the peek, never \\█through Failed/requeue: {log}\"█);█gen.kill();█let _ = probe.kill_session();█}█\u000f█</out>█# Tool call ¶█//Reading release notes details█read(path=\"CHANGELOG.md\", selector=\"1-70\")█<out>█\u000e# Changelog█All notable **user-facing** changes to `spt` - what a person running the CLI█notices or does differently. The `## [<version>]` section of each release█becomes that release's GitHub Release notes verbatim (see█`docs/RELEASE-RUNBOOK.md`). This project follows█[Keep a Changelog](https://keepachangelog.com) and semantic versioning█(pre-1.0: breaking changes bump the minor).█## [Unreleased]█## [0.36.0] - 2026-07-16█A stability release for attached sessions. A daemon update no longer disrupts█active sessions, and a session stuck by a bad connection now recovers on its own.█### Fixed█- Attached sessions no longer freeze after a daemon update or refresh.█Previously, a single bad viewer connection could hang an attached session for█up to a minute; a stuck viewer can no longer freeze other sessions.█- Improved the stability of controlled sessions across a daemon restart.█Previously, session controllers could mix up and drop their attached sessions.█- A session left stuck by a bad connection now recovers on reconnect, instead of█staying unresponsive.█### Internal█- Improved logging granularity for attached endpoints.█## [0.35.0] - 2026-07-16█A subnet resilience [...1289ch elided...] dress has come to claim a different peer's identity, the daemon now detects█and repairs it from the subnet roster when it starts - and says so loudly in█the log - rather than carrying the bad entry forward.█## [0.34.0] - 2026-07-16█A stability fix for live remote sessions during a daemon update.█### Fixed█- Updating or refreshing the daemon - `spt update`, `spt update --restart`, or█`spt daemon refresh` - no longer disturbs a remote session you are attached to.█A daemon cycle could previously replay already-finished output onto a live█remote terminal: old text re-typed itself, control of the session was stolen,█...█the remote sessions you are attached to keep running cleanly across an update.█...█First public release of `spt`.█[Showing lines 1-73 of 1147. Use :74 to continue]\u000f█</out>█# Tool call ¶█//Reading triage probes█read(path=\"docs/REDISPATCH-STALL-TRIAGE.md\", selector=\"1-150\")█<out>█\u000e# REDISPATCH-STALL - wave triage (doyle, 2026-07-16)█<!-- [doc->REQ-HAZARD-REDISPATCH-STALL] -->█**Wave:** stop the v0.34 field stalls - REDISPATCH-TRUTH W1's claim retry and█the broad `Err(_)` opener fallback repeatedly drive `StreamLog::attach`'s█discard-errors replay-under-mutex into serial 15s subscriber-poison windows█that compose into 17-62s PTY/RC freezes. Counter-63 fast-follow after█MESH-RECOVERY's counter 62.█**Canonical RCA:** hertz 2026-07-16, doyle seam-verified the same day; repo█summary `.claude/reports/2026-07-16-redispatch-stall/rca-summary.md`.█**Decision record:** ADR-0038 **Amendment (2026-07-16)**.█**Hazard:** KNOWN-HAZARDS 7.43.█**Field incident:** live v0.34 boxes, recurrent 20-30s freezes, DISPATCH tails█17-62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all█15,000-15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach█records/generation; per-stream, not broker-wide (unrelated conns progressed█mid-poison - broker-wide theory falsified).█## Seam census (verified current, main @7f5ead8)█| Seam | Location | Defect |█|---|---|---|█| Broad opener fallback | `spt-daemon/src/dispatch.rs:414` (`first_line`, `Err(_) => {}` ar [...6977ch elided...] ONED`█fingerprint disappears from live logs; stream-sub-attach per generation█drops from ~4361 to O(active streams).█2. **Stall census:** no 17-62s DISPATCH tails across a daemon refresh + one█week of live traffic.█3. **Next steal catch (if any):** capture the stolen row's `finished` +█`retired` flags - the discriminating observable between the finished█(D1/D1b-covered) and unfinished-stale (fix 6) shapes.█## Lane█doyle triaged (this doc) -> todlando builds (branch█`build/redispatch-stall-w1`, all five REQs, flip `required_stages` at build█start per [[traceable-per-wave-activation]]) -> doyle gates (rulings above) ->█deployah releases (counter 63 fast-follow, bump-in-PR, counter from published█metadata) -> hertz field-verify (discriminators above; they own the incident█logs).█\u000f█</out>█# Assistant ¶█_**Assessing broker connection management and thread behavior**█**Investigating subscriber thread cleanup and connection handling**█**Tracing connection churn from terminal retirements**█**Analyzing worker thread count and race conditions**█**Planning inspection of serve and writer threads**_█# Tool call ¶█//Tracing broker connection threads█grep(pattern=\"fn serve\\\\(|accept\\\\(|handle_client|handle_conn|client|thread::spawn\\\\(move.*conn|dispatch_net_stream_retire|KIND_NET_STREAM_RETIRE|detach_stream_subscriber\", path=\"crates/spt-daemon/src/broker.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e 5|//! the spawned child (both via [`spt_term::PtySession`]), and the accepted█*6|//! client / listening sockets. It runs **no logic** - no routing, registry,█7|//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,█8|//! forward its output to the brain, inject the brain's input, report exit.█9|//!█...█66| KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,█*67| KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,█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,█69| KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,█70|};█...█160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound█*161|/// protects the fan-out against ONE per-client conn falling behind and is█162|/// output-driven (the drain observes a Full channel); THIS bound is measured█163|/// full-with-**zero writer progress** and covers the case the output-driven path█164|/// can't - [...15183ch elided...] OG_CHUNKS, String::new(), (24, 80));█5992| let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);█5993| let writer = thread::spawn(move || {█...█6023| ) -> Arc<Mutex<Option<Instant>>> {█*6024| // The peer client/recv can drop immediately: no writer in these fixtures ever█6025| // writes to `send` (the thread only parks on rx.recv()), so a closed peer is█6026| // irrelevant - the stall signal is the injected `write_blocked_since`, not a█6027| // real socket block.█*6028| let (send, _client, _recv) = controller_socket_pair();█6029| let (tx, rx) = sync_channel::<CtrlMsg>(CONTROLLER_CHANNEL_DEPTH);█6030| let write_blocked_since = Arc::new(Mutex::new(blocked));█6031| let writer = thread::spawn(move || {\u000f█</out>█# Tool call ¶█//Tracing network retirement█grep(pattern=\"pub fn retire_stream|fn retire_stream|streams\\\\.remove|retain\\\\(|closed|detach_stream|begin_attach|stream_lease|subscribe_stream|stream_info\", path=\"crates/spt-daemon/src/nethost.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e 307| /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];█*308| /// there is no accept loop and no closed-watcher (it lives for the host).█309| Loopback,█310|}█311|█...█517| // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█*518| fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {█519| if let Some(prior) = &self.subscriber {█520| // No-reinstall-until-gone applies to the POISON class only█521| // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a█...█565| // Arc, old workers' handles stay theirs (C2 consumes via█*566| // [`NetHost::stream_lease`]).█567| // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█568| self.lease = Arc::new(StreamLease::default());█569| let replay_len = replay.len();█...█591|/// applied to the conn table itself. `register_conn` appends `connected`; each█*592|/// connection's closed-watcher appends `disconnected` after removing the row.█593|/// A brain subscribes with a cursor and replays events produced while it was█594|/// down, so presence survives a brain restart exactly like stream reads do.█595|// [i [...11669ch elided...] let mut log2 = PresenceLog::new(64);█*2831| log2.begin_attach(Arc::clone(&a), 0).expect(\"attach\");█2832| log2.subscriber█2833| .as_ref()█2834| .unwrap()█...█2837| let err = log2█*2838| .begin_attach(Arc::clone(&b), 0)█2839| .expect_err(\"poisoned prior presence seat refuses reinstall\");█2840| assert_eq!(err.kind(), io::ErrorKind::WouldBlock);█2841| assert!(err.to_string().contains(\"subscriber busy\"), \"{err}\");█...█2853| let (owner, _shell) = host.open_loopback_pair().expect(\"open pair\");█*2854| let lease = host.stream_lease(owner).expect(\"lease handle\");█2855| assert!(!lease.is_canceled());█2856| host.send_stream(owner, b\"ok\", false).expect(\"a live lease serves\");█2857|\u000f█</out>█# Tool call ¶█//Tracing brain connection lifetime█grep(pattern=\"pub struct Brain|impl Drop for Brain|struct Brain|cold_start|DaemonTransport|read_event|send\\\\(\", path=\"crates/spt-daemon/src/brain.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 17|//! output sequence it still needs. On handoff it `subscribe`s from that cursor;█*18|//! the broker replays buffered output from there. [`Brain::read_event`] accepts█19|//! only the contiguous next `seq` (advancing the cursor), silently drops an█20|//! already-seen `seq` (a replay duplicate), and treats a forward gap as a hard█21|//! error - so a lost chunk can never pass silently.█...█83|#[derive(Debug, Clone)]█*84|pub struct BrainState {█85| /// The session the brain was driving.█86| pub session_id: u64,█87| /// The generation counter (incremented on each handoff).█...█241| // Receiver gone (Brain dropped) -> stop reading.█*242| if tx.send(framed).is_err() {█243| break;█244| }█245| // A transport error (conn closed / hung up) is terminal -█...█263|/// A live brain attached to a broker.█*264|pub struct Brain {█265| conn: BrainConn,█266| generation: u64,█267| gen_start_ms: u64,█...█272| /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by█*273| /// [`Brain::read_event`] under the same accept/dedup/gap discipline.█[...26294ch elided...] assert_eq!(seq, 4504, \"the forward-jumped live seq is accepted, not rejected\");█2216| assert_eq!(bytes, b\"ROLLED\");█...█2228| // gap, not re-emitted), seq 4506 accepted.█*2229| stub.feed.send(output_envelope(sid, 4505, b\"LIVE-B\")).unwrap();█*2230| stub.feed.send(output_envelope(sid, 4504, b\"DUP\")).unwrap(); // dedup below cursor█*2231| stub.feed.send(output_envelope(sid, 4506, b\"LIVE-C\")).unwrap();█2232| let mut accepted = Vec::new();█2233| for _ in 0..2 {█*2234| match brain.read_event().expect(\"subsequent live frames\") {█2235| BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),█2236| other => panic!(\"expected Output, got {other:?}\"),█2237| }\u000f█</out>█# Tool call ¶█//Tracing attach worker termination█grep(pattern=\"pub fn serve_attach|net_stream_subscribe|lease canceled|detach|loop \\\\{|read_event|NetStreamEof|return\", path=\"crates/spt-daemon/src/attach.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e 56|/// served viewport is a **driver** - attaching wakes the endpoint, the█*57|/// viewport ending detaches it (active -> dormant; the transition echo is█58|/// gate-armed for the endpoint's own pulse loop). Best-effort exactly like█59|/// [`mark_driven_by`]: a session served without a perch has no resting█60|/// record to move.█...█65|█*66|/// Why a serve loop returned.█67|#[derive(Debug, Clone, PartialEq, Eq)]█68|pub enum AttachServeOutcome {█69| /// The access gate refused the requesting origin, OR a `Control` attach hit█...█76| Exited(Option<i32>),█*77| /// The operator closed its send side (detached); the viewport ended with█78| /// the session still running.█*79| Detached,█80| /// This controller was DISPLACED by a different operator's `--take`/`kick`█81| /// (REQ-KICK-1): the loud `Displaced` notice was sent and the stream█82| /// finished. The endpoint stays driven (by the taker) - no dormant edge.█...█141|/// hard flood). The next frame this reader sees is then a FORWARD gap that the█*142|/// strict reject-gap (`read_event`, legacy single-session path) FATALS. A█143|/// controller CANNOT snap-above like a viewer (B2: it is authoritativ [...6233ch elided...] BrokerEvent::Error { message } => {█619| if role == Some(ServeRole::Controller) {█*620| feed_rest(endpoint, crate::resting::RestEvent::Detach);█621| }█*622| return Err(io::Error::other(message));█623| }█624| // Applied/NetSent acks and unrelated events: not ours to handle.█625| _ => {}█...█630|/// Operator-side: open an attach stream on a dialed connection and send the█*631|/// [`AttachRecord::Request`]. Returns the stream id the caller then consumes█632|/// ([`AttachRecord::Output`] frames, deduped by `seq`) and writes█633|/// ([`AttachRecord::Input`] with operator-minted op ids). `open_op` is the█634|/// durable id for the exactly-once stream open (safe to block for its ack -\u000f█</out>█# Tool call ¶█//Locating pool tests█grep(pattern=\"MAX_DISPATCH_WORKERS|pool_has_room|active_workers|bounded worker|O\\\\(active\", path=\"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/█## dispatch.rs█446| let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));█*447| // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh█448| // with a large backlog) claims at most this many rows per poll - the█449| // rest wait for the next tick, batching the cold enumeration instead of█450| // spawning one thread per row. No claim lock is held across any I/O:█...█452| // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]█*453| let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));█454|█455| while !stop.load(Ordering::Relaxed) {█456| if breaker.lock().unwrap().open(Instant::now()) {█...█472| }█*473| if !pool_has_room(active_workers.load(Ordering::Relaxed)) {█474| break; // pool full: the rest of the table waits for the next poll█475| }█476| let attempts = {█...█495| let breaker_done = Arc::clone(&breaker);█*496| let pool = Arc::clone(&active_workers);█497| pool.fetch_add(1, Ordering::Relaxed);█498| thread::spawn(move || {█499| let started [...844ch elided...] he rest wait for the next tick; a freed slot re-admits.█1477| #[test]█1478| fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {█...█1481| for _row in 0..50 {█*1482| if !pool_has_room(active) {█1483| break; // the loop's cold-table break█1484| }█1485| active += 1;█...█1488| assert_eq!(█*1489| claimed, MAX_DISPATCH_WORKERS,█1490| \"a 50-row cold table claims exactly the pool bound in one poll\"█1491| );█1492| // A worker finishing frees a slot for the next tick.█1493| active -= 1;█*1494| assert!(pool_has_room(active), \"a freed slot re-admits next poll\");█1495| }█1496|█1497| // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\u000f█</out>█# Tool call ¶█//Locating connection telemetry█grep(pattern=\"stream-sub-attach|DISPATCH_EV|CONN_|conn=.*open|conn=.*close|connection.*count|thread\", path=\"docs/REDISPATCH-STALL-TRIAGE.md; docs/KNOWN-HAZARDS.md; crates/spt-daemon/src/conn.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# docs/█## REDISPATCH-STALL-TRIAGE.md█15|**Field incident:** live v0.34 boxes, recurrent 20-30s freezes, DISPATCH tails█*16|17-62s, poison interarrival median 95.5s; 33 `CONN_WRITE_POISONED` windows all█*17|15,000-15,154 ms (the round-4 SharedSend deadline); 4361 stream-sub-attach█18|records/generation; per-stream, not broker-wide (unrelated conns progressed█19|mid-poison - broker-wide theory falsified).█20|█...█29|| Stale-worker detach | serve/detach path (`detach_if` by ptr identity only) | no ownership/generation validation before attach/detach - a stale worker can clear a replacement controller (the UNFINISHED-stale steal shape, kin emphasys C2 raw-close no-FIN viewports) |█*30|| Dispatcher hygiene | `dispatch.rs` per-stream `thread::spawn`, claims/sessions locks near I/O | unbounded worker fan-out on cold enumeration; lock-across-I/O kin KH 7.12 |█31|█32|## Fix set (P0, accepted as filed - hertz design, doyle-ruled)█33|█...█93|**Missing-coverage census (fair, rides the wave):** D3 never poisons an█*94|installed subscriber; `conn_blackhole` exercises conns, not StreamLog█95|subscribers; `daemon_refresh` doesn't cover cross-node attach continuity -█96|the matrix above [...23513ch elided...] deadline (timed_out) class;█*709| // an organic write failure renders the distinct CONN_WRITE_RETIRED token█710| // carrying the error kind - and (doyle amendment 1) the SAME attribution█711| // fields as POISONED: conn id, accreted role/endpoint/session facts, wall +█712| // monotonic stamps. Quieter class, never thinner.█...█721| assert!(█*722| poisoned.starts_with(\"CONN_WRITE_POISONED:\"),█723| \"deadline class keeps the loud wedge-observable token: {poisoned}\"█724| );█725| let retired = conn█...█728| assert!(█*729| retired.starts_with(\"CONN_WRITE_RETIRED:\"),█730| \"organic class emits the DISTINCT retired token: {retired}\"█731| );█732| assert!(█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading current history tail█read(path=\".git/logs/HEAD\", selector=\"1350-1502\")█<out>█\u000ef16e2e0282c48192f01d94f0f87f4267388e511b 206884f8b6d91307b1faac1ce7d5943131d4858a Reavo End <decidel@gmail.com> 1783572988 -0700 commit: docs(pump-truth): round-2 empirical lock - it's the UPDATE leg (propagate.rs:373-375), not sync█206884f8b6d91307b1faac1ce7d5943131d4858a 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573138 -0700 commit: triage(pump-truth): mark REQ-PUMP-DIAL-FASTFAIL re-scoped - dial exonerated, fix is the reply-read poison█134f0fea01d9fe606ee08b9db0013274ade7862c 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573219 -0700 reset: moving to HEAD█134f0fea01d9fe606ee08b9db0013274ade7862c 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783576224 -0700 commit: triage(f-034): register 3 core legs for /sptc:live bringup cascade (perri/hertz field finding)█55556482ebca6a2d3c6c453edcbc9b261eb27c15 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783658323 -0700 checkout: moving from main to triage/update-wedge-rca-trace█55556482ebca6a2d3c6c453edcbc9b261eb27c15 59cf48212449e4f198826e14f2808c014db2f99e Reavo End <decidel@gmail.com> 1783658332 -0700 commit: chore(rca): tra [...29264ch elided...] 1784263680 -0700 checkout: moving from fix/redispatch-stall-w1-replay-order to main█0c7025338db716a5ed9cc4ceaa8d85dd04a38840 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263681 -0700 pull --ff-only: Fast-forward█fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784263736 -0700 checkout: moving from main to main█fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 Reavo End <decidel@gmail.com> 1784270311 -0700 checkout: moving from main to docs/v0360-notes-plain-language█fa3e474783a6e3ebbb178a478f03e4ebc8ce3928 d903b5de1a5728b8b5938714b52bf64017551fbc Reavo End <decidel@gmail.com> 1784270368 -0700 commit: docs(changelog): rewrite v0.36.0 notes in plain end-user language█\u000f█</out>█# Tool call ¶█//Reading branch histories█read(path=\".git/logs/refs/remotes/origin/build/redispatch-stall-w1\", selector=\"raw\")█<out>█\u000e0000000000000000000000000000000000000000 cb70090bf20963a89d814cf8f5d8e750d4413afa Reavo End <decidel@gmail.com> 1784254964 -0700 update by push█cb70090bf20963a89d814cf8f5d8e750d4413afa 73b29b858bbffb2c758382c80d74def0aabb328f Reavo End <decidel@gmail.com> 1784255747 -0700 update by push█\u000f█</out>█# Assistant ¶█_**Inspecting Drop implementation for watchdog thread cleanup**_█# Tool call ¶█//Tracing watchdog teardown█grep(pattern=\"impl Drop for BrokerConn|fn watchdog|stop|shutdown|dog|JoinHandle|Condvar|wait_timeout|while\", path=\"crates/spt-daemon/src/conn.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 9|//! path with **no supported write timeout** (`set_timeout` -> `Unsupported`).█*10|//! A connected consumer that stops reading (an `rc --take` whose process█11|//! wedged; a suspended brain) therefore blocks the write INDEFINITELY, and the█12|//! logical stall-evict (`stall_evict_controller`) releases only the controller█13|//! ROLE - it never cancels the in-flight pipe write or closes the physical█...█25|//! write);█*26|//! 2. arms an INDEPENDENT per-connection watchdog thread with that absolute█27|//! deadline, then performs the blocking [`write_frame`] OUT of every lock;█28|//! 3. on success, returns the half to the gate.█29|//!█*30|//! The watchdog fires OUT OF BAND at the deadline - it never relies on the█31|//! write returning, nor on opportunistic stall-evict sampling: it poisons the█32|//! connection and aborts the in-flight I/O (`CancelIoEx` on Windows - the█33|//! completion APC makes the wedged `SleepEx` loop return with█*34|//! `ERROR_OPERATION_ABORTED`; `shutdown(SHUT_RDWR)` on Unix - the blocked█35|//! write returns `EPIPE`). A gate-waiter that exhausts its own deadline fires█36|//! the same abort (the stuck op it waited behind is the wedge).█37|/ [...9178ch elided...] rned pin drops. Test-only; never a production path.█629| pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {█...█638|█*639|impl Drop for BrokerConn {█640| fn drop(&mut self) {█641| // Once-per-conn `transport-close` lifecycle record (leg d): the handle█642| // is about to close - the terminal record of this conn id's life, with█...█652| {█*653| let mut d = recover(&self.inner.dog);█*654| d.shutdown = true;█*655| self.inner.dog_cv.notify_all();█656| }█*657| if let Some(dog) = self.dog.take() {█*658| let _ = dog.join();█659| }█*660| // Halves (gate or graveyard) drop AFTER the watchdog is gone - no raw█661| // handle use can race the CloseHandle/limbo path.█662| }█663|}\u000f█</out>█# Tool call ¶█//Reading broker connection watchdog█read(path=\"crates/spt-daemon/src/conn.rs\", selector=\"130-480\")█<out>█\u000estruct Gate {█...█}█/// Watchdog bookkeeping: the currently in-flight op and its ABSOLUTE deadline.█struct Dog {█/// `Some((op_id, deadline))` while a write is in flight.█inflight: Option<(u64, Instant)>,█/// Raised by [`BrokerConn::drop`]; the watchdog exits.█shutdown: bool,█}█/// The state shared with the watchdog thread (no `Arc<BrokerConn>` cycle -█/// the watchdog must not keep the conn alive).█struct Inner {█/// Stable per-physical-conn id ([`CONN_ID_SEQ`]) - on every lifecycle record.█id: u64,█/// Accreted attribution facts (subscriber role, endpoint/session where█/// known - [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].█label: Mutex<String>,█/// Latch: the once-per-conn `write-start` lifecycle record fired.█first_write_logged: AtomicBool,█gate: Mutex<Gate>,█gate_cv: Condvar,█dog: Mutex<Dog>,█dog_cv: Condvar,█/// Idempotently latched on the first deadline/failure/cancel. A poisoned█/// conn refuses every future write and is never re-gated.█poisoned: AtomicBool,█/// Latched when the retirement was DEADLINE-driven (watchdog fired, or a█/// gate-waiter exhausted its bound) - as opposed to an [...13099ch elided...] endpoint/session where known - e.g. `role=brain`,█/// `controller session=7 by=cli@host`). Facts accumulate space-separated█/// and ride EVERY subsequent lifecycle record for this conn; growth is█/// bounded by [`LABEL_CAP`] (one `...` marker, then no-op).█// [impl->REQ-CONN-POISON-ATTRIBUTION]█pub(crate) fn describe(&self, fact: &str) {█let mut l = recover(&self.inner.label);█if l.ends_with('...') {█return;█}█if l.len() + fact.len() + 1 > LABEL_CAP {█l.push('...');█return;█}█if !l.is_empty() {█l.push(' ');█}█l.push_str(fact);█}█/// Emit a bounded per-conn lifecycle record through this conn's shared█...█}█[Showing lines 129-483 of 815. Use :484 to continue]\u000f█</out>█# Tool call ¶█//Reading broker handler cleanup█read(path=\"crates/spt-daemon/src/broker.rs\", selector=\"2560-2650,2780-2825,2910-2935,3015-3220\")█<out>█\u000e/// the brain (Spike #6 constraint 1).█pub struct Broker {█listener: LocalSocketTransport,█/// The namespaced listener name - held so [`Broker::stop`] can dial a throwaway█/// connection to unblock the blocked `accept()` in [`serve`](Self::serve) (there is█/// no other wakeup for the accept loop).█name: String,█/// Graceful-stop latch (C1, REQ-HAZARD-DAEMON-STOP-BARRIER teardown-hygiene):█/// [`serve`](Self::serve) checks it after each accept and returns. Without it█/// `serve` is an unstoppable accept loop, so a test (or any host) that stands a█/// broker up leaks the serve thread AND every accepted-conn handler thread at█/// teardown - the load population that starved the seed stop barrier.█stop: AtomicBool,█sessions: Arc<Mutex<HashMap<u64, HostedSession>>>,█next_id: AtomicU64,█log_cap_chunks: usize,█/// The exactly-once anchor for idempotent (`op_id`-carrying) effects.█journal: Arc<EffectJournal>,█/// The broker-owned net endpoint + live QUIC connections (D4a, ADR-0004 §B).█/// Empty when bound without a network host (net-disabled / most tests) OR█/// when net is still being brought up (REQ-DAEMON-9 boot [...17409ch elided...] E): re-assert█// each live session's control/viewer stamps to broker truth,█// divergence-gated + OFF the log lock. Closes the stamp-before-bind█// loss for a picker-created endpoint (its spawn stamp was swallowed█// pre-bind); converges within one reconcile-poll window after bind.█// [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]█for s in &snaps {█converge_perch_stamps(█&s.endpoint,█s.controller_by.as_deref(),█s.has_controller,█s.viewers,█);█}█...█}█...█}█}█...█}\u000f█</out>█# Tool call ¶█//Reading retirement implementation█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"910-980,1760-1860,1935-1990\")█<out>█\u000e█// Local alias so the stage constants read namespaced at the use sites without█// a wildcard import.█use crate::seedproofx as spt_seedproof_stage;█use crate::seedproofx::DialStage;█fn register_conn(█shared: &Arc<NetShared>,█conn: Connection,█proven_subnets: HashSet<String>,█) -> (u64, String) {█let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())█.expect(\"an iroh EndpointId is always a valid ed25519 public key\")█.to_hex();█let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);█shared.conns.lock().unwrap().insert(█id,█ConnEntry {█kind: ConnKind::Quic(conn.clone()),█remote_id_hex: remote_id_hex.clone(),█proven_subnets,█},█);█// Presence: announce the row BEFORE spawning the closed-watcher, so the█// `connected` event can never race behind its own `disconnected` (D4c). The█// event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a█// non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply█// did (id-only / path-less -> `null`, nothing worth seeding).█let observed = observed_addr_json(&conn);█sha [...8023ch elided...] (the \"got seq 134 want 6\" re-serve red).█// A replay write failure poisons the seat writer-side; the next█// producer enqueue halt-and-removes it (the T1 discipline).█// [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]█log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;█Ok(())█}█/// Detach `sub` from a stream iff it is the current subscriber (brain█/// disconnect cleanup; the stream + its ring persist).█pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {█let log = {█let streams = self.shared.streams.lock().unwrap();█streams.get(&stream_id).map(|e| Arc::clone(&e.log))█};█if let Some(log) = log {█log.lock().unwrap().detach_if(sub);█}█}█\u000f█</out>█# Tool call ¶█//Reading retire dispatch█read(path=\"crates/spt-daemon/src/broker.rs\", selector=\"4360-4410\")█<out>█\u000e fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {█...█match req.op_id {█...█Some(op) => {█...█Ok(())█}█}█}█/// List every stream the broker holds (D4b) - the query a re-attaching█/// brain uses to find streams that arrived while it was down.█fn dispatch_net_streams(&self, send: &SharedSend) -> Result<(), String> {█let host = self.net.get().ok_or(\"net disabled\")?;█let frame = Envelope::new(█KIND_NET_STREAMS_REPLY,█serde_json::to_value(NetStreamsReply {█streams: host.stream_infos(),█})█.expect(\"NetStreamsReply serializes\"),█);█send_frame(send, &frame);█Ok(())█}█/// Retire a completed stream row from redispatch eligibility (ADR-0038 A):█/// the dispatcher reports a serve loop that ran to completion, and the row█/// leaves the table so no future dispatcher generation can rediscover and█/// replay it (the KNOWN-HAZARDS 7.41 control-steal class).█// [impl->REQ-REDISPATCH-FINISHED-RETIRE]█fn dispatch_net_stream_retire(&self, env: Envelope, send: [...748ch elided...] nstead of peeking ring seq 0.█// [impl->REQ-STREAM-OPENER-DURABLE]█fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {█let req: NetStreamOpenerReq = serde_json::from_value(env.payload)█.map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;█let host = self.net.get().ok_or(\"net disabled\")?;█let opener = host█.stream_opener(req.stream_id)█.ok_or_else(|| format!(\"no such stream {}\", req.stream_id))?;█let frame = Envelope::new(█KIND_NET_STREAM_OPENER_REPLY,█serde_json::to_value(NetStreamOpenerReply {█...█})█.expect(\"NetStreamOpenerReply serializes\"),█);█...█}█[Showing lines 4359-4413 of 6298. Use :4414 to continue]\u000f█</out>█# Tool call ¶█//Reading brain connection construction█read(path=\"crates/spt-daemon/src/brain.rs\", selector=\"210-370,1430-1505,1725-1760\")█<out>█\u000eenum BrainConn {█/// The ordinary blocking carrier - every non-pump brain.█Whole(Stream),█/// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over█/// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).█Split {█send: SendHalf,█frames: Receiver<io::Result<Envelope>>,█/// The reader-thread handle - held so the thread is owned, not detached█/// silently. On a supervised restart the whole [`Brain`] drops; this█/// handle drops with it (the thread, blocked in `read_frame`, is█/// abandoned and exits when the old conn closes - KH 7.6).█_reader: JoinHandle<()>,█},█}█impl BrainConn {█/// Split a connected+handshaked stream into the pump carrier: spawn the█/// `pump-ipc-reader` thread (blocking [`read_frame`] -> channel) and keep the█/// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)█/// reader is diagnosable in the field (KH 7.6).█// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]█fn split_with_reader(stream: Stream) -> BrainConn {█let (mut recv, send) = stream.split();█let (tx, frames) = mpsc::channel();█let read [...11891ch elided...] er: the reader thread already owns█/// the blocking read, so a bounded read is just [`Receiver::recv_timeout`]█/// on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]█/// - the poison signal the pump escalates to a supervised restart (NEVER a█/// per-peer retry). A `Disconnected` channel means the reader thread exited█/// (the conn hung up); surface it as `UnexpectedEof`.█// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]█fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {█match &mut self.conn {█BrainConn::Whole(stream) => read_frame(stream),█BrainConn::Split { frames, .. } => match deadline {█Some(dl) => {█...█}█...█},█}█}█...█}\u000f█</out>█# Tool call ¶█//Reading attach two-connection path█read(path=\"crates/spt-daemon/src/attach.rs\", selector=\"270-430,570-630\")█<out>█\u000e█/// Serve one attach stream against this brain's broker, blocking until the█/// session exits, the operator detaches, or the gate refuses.█///█/// `origin_node` MUST be the handshake-proven remote id from the broker's█/// stream table (`NetStreamInfo::remote_id_hex`) - never payload bytes█/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session█/// is being attached, for the gate. `net_from_seq` is this brain's resume█/// cursor on the attach stream (0 cold - also the safe post-restart choice:█/// the replayed `Request`/`Input` records re-gate and dedup at the PTY█/// journal, and re-pumped output dedups at the operator's render cursor).█///█/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,█/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint->our█/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so█/// the wire's placeholder `session_id` (0) is overridden here with the resolved█/// id. It is passed down - never re-queried mid-loop - because a `sessions()`█/// round-trip inside the event loop would swallow interleaved attach records█/// (`Input`/`Subscribed`) the loop must see. `None` = the lo [...9614ch elided...] encode_line(&AttachRecord::Exit { code });█wire.net_stream_send(stream_id, &line, None, true)?;█if role == Some(ServeRole::Controller) {█feed_rest(endpoint, crate::resting::RestEvent::Detach);█}█return Ok(AttachServeOutcome::Exited(code));█}█BrokerEvent::Error { message } => {█if role == Some(ServeRole::Controller) {█feed_rest(endpoint, crate::resting::RestEvent::Detach);█}█return Err(io::Error::other(message));█}█// Applied/NetSent acks and unrelated events: not ours to handle.█_ => {}█}█}█}█/// Operator-side: open an attach stream on a dialed connection and send the\u000f█</out>█# Tool call ¶█//Reading pool unit coverage█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=\"1270-1530\")█<out>█\u000e█// [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:█// a TRANSIENT failure releases the claim and requeues it after a backoff;█// TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter█// - a persistently-failing stream retries exactly its bounded budget and█// stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet█// abandoned the stream on failure #1 forever.█#[test]█fn transient_failure_requeues_bounded_and_terminal_never_reenters() {█let now = Instant::now();█// Fresh row -> claimable with zero prior failures.█assert_eq!(should_claim(None, now), Some(0));█// In flight / terminal -> never.█assert_eq!(should_claim(Some(&ClaimState::InFlight { attempts: 0 }), now), None);█assert_eq!(should_claim(Some(&ClaimState::Terminal), now), None);█// Failure #1 releases with backoff - NOT claimable before next_at,█// claimable at/after it, carrying the attempt count.█let failed = DispatchOutcome::Failed(\"io\".into());█let s1 = outcome_transition(&failed, 0, now, Duration::ZERO);█let ClaimState::Retry { attempts: 1, next_at [...10645ch elided...] ];█for (family, line, want) in cases {█assert_eq!(█line_endpoint(family, &line).as_deref(),█want,█\"family {family:?} line {line}\"█);█}█}█// [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family█// discriminator (gate round 1): a finished ATTACH is terminal for█// redispatch (the operator detached - replay = the 7.41 controller█// steal), while a finished REQUEST/REPLY stream is NOT - sync and update█// requesters FIN their send WITH the request (sync.rs request_sync /█// propagate.rs fetch_update) and await the reply, so a bare finished█// filter would break every pull that arrives while the brain is down.█[Showing lines 1269-1533 of 1558. Use :1534 to continue]\u000f█</out>█# Assistant ¶█_**Tracing BrokerConn watchdog thread creation**_█# Tool call ¶█//Locating handler cleanup tail█grep(pattern=\"for id in my_stream_subs|my_stream_subs|my_presence_sub|detach_stream_sub|detach_if\\\\(&send|return Ok\\\\(\\\\(\\\\)\\\\)\", path=\"crates/spt-daemon/src/broker.rs\", selector=\"3025-3350\", case=true, gitignore=true, skip=null)█<out>█\u000e 3051| let mut my_subs: Vec<u64> = Vec::new();█*3052| let mut my_stream_subs: Vec<u64> = Vec::new();█*3053| let mut my_presence_sub = false;█3054|█3055| loop {█3056| let env = match read_frame(&mut recv) {█...█3245| KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {█*3246| Ok(id) => my_stream_subs.push(id),█3247| Err(msg) => send_error(&send, &msg),█3248| },█3249| KIND_NET_PRESENCE_SUBSCRIBE => {█3250| match self.dispatch_net_presence_subscribe(env, &send) {█*3251| Ok(()) => my_presence_sub = true,█3252| Err(msg) => send_error(&send, &msg),█3253| }█3254| }█...█3275| for log in &logs {█*3276| recover_log(log).detach_if(&send);█3277| }█3278| // Same for net streams: the streams + their rings persist (D4b).█3279| if let Some(host) = self.net.get() {█*3280| for id in &my_stream_subs {█*3281| host.detach_stream_sub(*id, &send);█3282| }█3283| // And presence: the liveness log + its ring persist (D4c).█*3284| if my_presence_sub {█3285| host.detach_presence_sub(&send);█3286| }█3287| }\u000f█</out>█# Tool call ¶█//Reading handler cleanup section█read(path=\"crates/spt-daemon/src/broker.rs\", selector=\"3300-3410\")█<out>█\u000e let req: SpawnReq =█serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;█// Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the█// sole spawner post-W3, so its own spawn choke is the single-flight authority.█// Dedup a duplicate wake/resume for an endpoint that already has a live session█// OR a spawn in flight - closing the check-then-spawn TOCTOU that let one wake█// spawn two identical launch trees (perri's flynn dup: duplicate perch writers█// then stomped info.json, routing injects to a contended record). The claim +█// the live-session check are read together under one atomic critical section so█// a concurrent spawn either sees our claim or its registered session - never a█// gap; NO I/O under the lock (process_id reads a cached pid). An empty endpoint█// is an ad-hoc/legacy spawn (no id to dedup on) -> never gated. `_wake_claim`█// holds the claim to fn end, releasing (RAII) after the session is table-visible█// or on any early error.█// [impl->REQ-SPAWN-COLLISION-GUARD-LIVE-DUP]█let _wa [...4178ch elided...] let log = Arc::new(Mutex::new(OutputLog::new(█id,█self.log_cap_chunks,█req.endpoint.clone(),█(req.rows, req.cols),█)));█log.lock()█.unwrap()█.become_controller(Arc::clone(send), None, 0, 0);█// Output drain -> append under the log lock (fast: ring + viewer fan-out),█// then deliver to the controller OFF the lock with a bounded deadline█// (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no█// longer inline under the log lock, so a backed-up controller can never█// wedge the session; a fully-stalled controller is detached after the█// deadline (bounded wedge, never permanent).█[Showing lines 3299-3413 of 6298. Use :3414 to continue]\u000f█</out>█# Tool call ¶█//Checking worker thread gauges█grep(pattern=\"conn_handler_count|active_workers|stream-sub-attach|transport-close|worker-outcome|breaker-trip\", path=\"crates/spt-daemon/tests; crates/spt-daemon/src\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/█## tests/█### brain_decouple.rs█401|// - the black-holed conn's `handle_conn` thread FINISHES within the write bound█*402|// of logical release (broker.conn_handler_count() falls to 0) WITHOUT the█403|// client ever dropping its end; <- RED pre-fix█404|// - a write on the old client connection FAILS (it was physically retired). <- RED pre-fix█405|// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the█...█677|█*678| // -- Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads█679| // ZERO (a zero sample tolerates a transient query conn briefly bumping it),█680| // bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed█681| // conn's handler never exits until the client drops) = the RED. --█...█685| while Instant::now() < step7_deadline {█*686| if broker.conn_handler_count() == 0 {█687| handler_released = true;█688| break;█689| }█...█810| handler_released,█*811| \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\█812| -> 0) within [...6457ch elided...] ebbie\",█1454| );█1455| for key in [█...█1460| \"conn=41\",█*1461| \"event=worker-outcome\",█1462| \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",█1463| ] {█1464| assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");█...█1468| // Pre-classification events name the family honestly.█*1469| let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");█1470| assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");█1471| }█1472|█### nethost.rs█541| sub.lifecycle_event(█*542| \"stream-sub-attach\",█543| &format!(\"stream={} from_seq={from_seq}\", self.stream_id),█544| );█545| let mut replay: Vec<Envelope> = self\u000f█</out>█# Tool call ¶█//Checking refresh regression coverage█grep(pattern=\"refresh|generation|conn_handler_count|thread|stream-sub-attach|retire\", path=\"crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs; crates/spt-daemon/tests/daemon_refresh.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## redispatch.rs█3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child -█*4|//! a dispatcher GENERATION whose death drops its workers' broker conns█5|//! exactly like a production brain death). NO manual re-serve anywhere: the█6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)█7|//! hand-stepped a known stream and bypassed exactly the rediscovery/█...█10|//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same█*11|//! endpoint/origin; restart the dispatcher generation only -> the█12|//! historical stream neither takes nor clears the current controller█13|//! (post-restart input still renders; no EOF; no replay-storm log line;█*14|//! the retired row left the broker's enumeration).█15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;█16|//! restart the dispatcher -> durable classification (the pinned opener)█17|//! resumes the SAME operator stream.█...█27|use std::sync::{Arc, OnceLock};█*28|use std::thread;█29|use std::time::{Duration, Instant};█30|█31|use spt_daemon::attach::{request_attach, send_attach_input};█...█73| let serve = Arc::clone(&broker) [...6299ch elided...] 3: the producer stays FLAT while the poisoned seat halts. Every ack'd█358| // send round-trips B->A (QUIC transfer + ring append) - pre-fix each█...█396|// REFUSED (\"lease canceled\") - the leg terminates instead of feeding an█*397|// orphan forever. A new subscriber generation then renews the lease and a█398|// fresh send serves again (recovery, the C2-lease seam this wave builds).█399|#[test]█400|fn poisoned_subscriber_cancels_the_paired_forwarding_leg_then_recovery_serves() {█...█415| let (fw_tx, fw_rx) = std::sync::mpsc::channel::<(u32, String)>();█*416| let forwarder = thread::spawn(move || {█417| let mut fw = connect_retry(&fw_name);█418| let mut ok = 0u32;█419| for i in 0..600u64 {█Skipped missing paths: crates/spt-daemon/tests/daemon_refresh.rs\u000f█</out>█# Tool call ¶█//Finding runtime RCA artifacts█glob(path=\".claude/reports/**/*redispatch*\", hidden=true, gitignore=false, limit=200)█<out>█\u000e# .claude/reports/█## 2026-07-16-redispatch-stall/█## 2026-07-16-redispatch-truth/\u000f█</out>█# Tool call ¶█//Finding performance scripts█glob(path=\"**/*{thread,cpu,io,redispatch}*.{ps1,rs,md,txt}\", hidden=true, gitignore=true, limit=300)█<out>█\u000e# crates/█## spt-daemon/█### tests/█redispatch_stall.rs█redispatch.rs█psyche_residency_expectation_e2e.rs█### src/█translation.rs█## spt/█### tests/█live_adapt_translation_swap_e2e.rs█run_no_dup_session_e2e.rs█nested_resolution_e2e.rs█### src/█elevation.rs█## spt-store/src/█sessions.rs█rotation.rs█## spt-term/src/█projection.rs█## spt-proto/src/█version.rs█# docs/adr/█0039-peer-route-resolution-truth.md█0036-private-fork-distribution.md█0022-spt-hosted-idle-delivery-translation-binary.md█0032-boundary-rotation-credential.md█0027-unbound-endpoint-state-and-attach-on-session.md█0026-multi-subnet-home-at-endpoint-run-creation.md█0021-harness-hosted-adapter-agnostic-seed-and-bind-time-resolution.md█0019-digest-adapter-declared-extractor-session-spanning.md█0018-broker-brain-process-isolation-restoration.md█0007-subnet-notification-primitive.md█# docs-site/src/harness-contract/█integration-checklist.md\u000f█</out>█# Tool call ¶█//Finding dispatch generation metrics█grep(pattern=\"DISPATCH_EV|stream-sub-attach|conn_handler_count|MAX_DISPATCH_WORKERS\", path=\"scripts; tools; crates/spt-daemon/tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## brain_decouple.rs█401|// - the black-holed conn's `handle_conn` thread FINISHES within the write bound█*402|// of logical release (broker.conn_handler_count() falls to 0) WITHOUT the█403|// client ever dropping its end; <- RED pre-fix█404|// - a write on the old client connection FAILS (it was physically retired). <- RED pre-fix█405|// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the█...█677|█*678| // -- Step 7: WITHOUT dropping R's Stream, poll conn_handler_count() until it reads█679| // ZERO (a zero sample tolerates a transient query conn briefly bumping it),█680| // bounded at t_release + 2 s. PRE-FIX this stays ≥1 forever (the black-holed█681| // conn's handler never exits until the client drops) = the RED. --█...█685| while Instant::now() < step7_deadline {█*686| if broker.conn_handler_count() == 0 {█687| handler_released = true;█688| break;█689| }█...█810| handler_released,█*811| \"the black-holed connection's handle_conn thread must finish (conn_handler_count \\█812| -> 0) within 2 s o [...2122ch elided...] break;█...█656| // -- INVARIANT 3: ITS WRITER EXITS - the black-holed conn's handle_conn thread█*657| // finishes (conn_handler_count -> the <=1 unrelated-viewer floor) within 2 s█658| // of logical release, WITHOUT the client dropping its end. --█659| assert!(█660| handler_released,█661| \"INVARIANT 3: the black-holed connection's writer must EXIT - \\█*662| conn_handler_count must fall to the unrelated-viewer floor (<= 1) within 2 s \\█663| of logical release WITHOUT the client dropping its end (while R's handler is \\█664| alive the global count is >= 2, so <= 1 proves R's handler finished). A stuck \\█665| count means the detached writer stays wedged in write_frame and the handler \\█Skipped missing paths: scripts, tools\u000f█</out>█# Assistant ¶█_**Examining RCA artifact summaries**_█# Tool call ¶█//Listing stall RCA artifacts█read(path=\".claude/reports/2026-07-16-redispatch-stall\", selector=null)█<out>█\u000e.█- rca-summary.md 3.3KB 6h ago\u000f█</out>█# Tool call ¶█//Listing truth RCA artifacts█read(path=\".claude/reports/2026-07-16-redispatch-truth\", selector=null)█<out>█\u000e.█- hertz-refresh-rca.txt 6.3KB 20h ago\u000f█</out>█# Tool call ¶█//Searching RCA retained workers█grep(pattern=\"thread|worker|replay|attach|conn|churn|CPU|I/O|4361|retained|leak\", path=\".claude/reports/2026-07-16-redispatch-stall; .claude/reports/2026-07-16-redispatch-truth\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000eSkipped due to queued user message. Do not count this skipped result as completed work or verification. After the queued message is handled on the next step, retry the skipped tool if it is still needed.\u000f█</out>█# Tool call ¶█//Reading exact handler cleanup█read(path=\"crates/spt-daemon/src/broker.rs\", selector=\"3255-3295\")█<out>█\u000e fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {█...█loop {█...█match env.kind.as_str() {█...█KIND_NET_PRESENCE_SUBSCRIBE => {█...█}█KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),█// An unknown kind is tolerated (forward-compat): report, don't die.█other => send_error(&send, &format!(\"unknown command kind {other:?}\")),█}█}█// Brain gone: detach as subscriber but leave the sessions running.█// Clone the log Arcs out from under the sessions lock FIRST (ADR-0038█// Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json█// (`viewer_count`/`driven_by`) and emits a lifecycle record - file +█// conn I/O that must never run under the shared sessions table lock,█// or one slow perch write stalls every spawn/attach/kill dispatch.█// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]█let logs: Vec<Arc<Mutex<OutputLog>>> = {█let sessions = recover(&self.sessions);█my_subs█.iter()█.filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.l [...422ch elided...] ).█if my_presence_sub {█host.detach_presence_sub(&send);█}█}█Ok(())█}█/// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning█/// connection, start its output drain and exit waiter, and reply `spawned`.█/// Every hosted session gets its **own** drain + exit-waiter threads (KH█/// 7.4): a hung child blocks only its own threads - shell instances ride█/// exactly this isolation (one owner's hung shell binary stalls neither█/// another owner's delivery nor the broker's control dispatch).█// [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]█fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {█...█}█[Showing lines 3254-3298 of 6298. Use :3299 to continue]\u000f█</out>█# Tool call ¶█//Reading watchdog drop exact█read(path=\"crates/spt-daemon/src/conn.rs\", selector=\"630-665\")█<out>█\u000eimpl BrokerConn {█...█pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {█let mut g = recover(&self.inner.gate);█let half = g.half.take().expect(\"pin: gate half present\");█TestGatePin {█inner: Arc::clone(&self.inner),█half: Some(half),█}█}█}█impl Drop for BrokerConn {█fn drop(&mut self) {█// Once-per-conn `transport-close` lifecycle record (leg d): the handle█// is about to close - the terminal record of this conn id's life, with█// how it ended (organic EOF vs poisoned vs deadline-poisoned).█self.inner.lifecycle(█\"transport-close\",█&format!(█\"poisoned={} timed_out={}\",█self.inner.poisoned.load(Ordering::Acquire),█self.inner.timed_out.load(Ordering::Acquire)█),█);█{█let mut d = recover(&self.inner.dog);█d.shutdown = true;█self.inner.dog_cv.notify_all();█}█if let Some(dog) = self.dog.take() {█let _ = dog.join();█}█// Halves (gate or graveyard) drop AFTER the watchdog is gone - no raw█// handle use can race the CloseHandle/limbo path.█}█}█fn poisoned_err() -> io::Error {█io::Error::new(█io::ErrorKind::BrokenPipe,█\"broker conn poisoned (bounded write retired the physical connection)\",█)█}█[Showing lines 629-668 of 815. Use :669 to continue]\u000f█</out>█# Tool call ¶█//Reading subscriber unit seams█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"2380-2505,2506-2805\")█<out>█\u000e fn opener_capture_gives_up_bounded_on_a_newline_less_stream() {█let mut log = StreamLog::new(8, 4);█log.append(&vec![b'x'; OPENER_PIN_MAX + 1]);█assert_eq!(log.opener_line(), None, \"over the cap without a newline: gave up\");█log.append(b\"late-line\\n\");█assert_eq!(log.opener_line(), None, \"Oversize is terminal\");█}█// [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the█// named row from dispatch eligibility: the retired stream leaves the█// dispatch enumeration (stream_infos) while the active sibling stays█// claimable, a second retire is a no-op, and the pinned-opener query█// answers no-such-stream afterwards. Retirement is visibility, not█// teardown - the transport half of the contract is the sibling test█// `retired_row_still_flushes_a_late_reply`.█#[test]█fn retire_hides_the_row_while_active_rows_stay() {█let host = NetHost::start(hermetic(&Identity::generate())).expect(\"host start\");█let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");█host.send_stream(owner, b\"{\\\"kind\\\":\\\"request\\\",\\\"session_id\\\":1}\\n\", false)█.expect(\"send o [...18452ch elided...]█lease_a.is_canceled(),█\"the displaced writer cancels its own generation's lease\"█);█// ...and the SUCCESSOR's lease stays live - its serve leg keeps█// forwarding (send_stream reads this current Arc).█assert!(█!lease_b.is_canceled(),█\"the displaced writer's death must never cancel the successor's lease\"█);█}█// [unit->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE] the PresenceLog rides the█// SAME seat discipline (Amendment fixes 2+3 mirrors - gate ruling 5█// counts these sites): push through a poisoned seat halts-and-removes,█// retained events replay on attach, and the poisoned-not-gone reinstall█// refusal matches StreamLog's.█[Showing lines 2379-2808 of 2865. Use :2809 to continue]\u000f█</out>█# Tool call ¶█//Reading dispatch final worker outcomes█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=\"900-1220\")█<out>█\u000efn worker(█...█) -> WorkerReport {█...█let outcome = match family {█...█StreamFamily::Attach => {█...█match endpoint {█...█None => {█...█DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())█}█}█}█StreamFamily::Xfer => {█// The wire names whose whitelist gates (the rule set); the origin█// it is checked against stays handshake-proven. Blank refuses.█let endpoint = line█.get(\"endpoint\")█.and_then(|v| v.as_str())█.filter(|e| !e.is_empty())█.map(str::to_string);█match endpoint {█Some(endpoint) => {█let root = spt_store::xfer::transfers_dir();█let progress = paths.scratch.join(\"xfer-progress\");█match serve_xfer(█&mut brain, stream_id, &endpoint, origin, 0, &root, &progress,█) {█Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),█Err(e) => DispatchOutcome::Failed(e.to_string()),█[...12363ch elided...] JoinHandle<()> {█thread::spawn(move || {█let paths = DispatchPaths::production();█if let Err(e) =█run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)█{█eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");█}█})█}█#[cfg(test)]█mod tests {█use super::*;█use serde_json::json;█// [unit->REQ-NET-1] the demux table: every family's first record█// classifies correctly, including the `request` kind collision.█#[test]█fn classifies_every_family_by_first_line_shape() {█let sync = json!({\"kind\": \"request\", \"sync_id\": \"pull-1\", \"refs\": [], \"have_tips\": {}});█assert_eq!(classify_first_line(&sync), StreamFamily::Sync);█...█}█...█}█[Showing lines 899-1223 of 1558. Use :1224 to continue]\u000f█</out>█# User ¶█Your current interruptible wait was interrupted because an IRC message arrived from your parent agent `Main`.█Parent IRC message:█Fresh v0.36 evidence: repeated Registry-family stream subscriber poison. Examples stream 524/1161/1347/3053/3810: CONN_WRITE_POISONED after ~15s, DISPATCH_EV family=Registry endpoint=- elapsed 15.5-16.4s. Current broker ~550 threads; brain reads ~64 MiB/s. Investigate Registry stream lifecycle/replay specifically.█# Assistant ¶█_**Tracing registry feed semantics**█**Inspecting peerloop registry send**_█# Tool call ¶█//Tracing registry stream producers█grep(pattern=\"RegistryFeedRecord|RegistryUpdate|net_stream_send\\\\(|open_stream|registry.*feed|send_registry|push_registry|encode_line\", path=\"crates/spt-daemon/src\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/█## attach.rs█254| if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {█*255| wire.net_stream_send(stream_id, &[], None, true)?;█256| return Ok(Some(AttachServeOutcome::Refused));█257| }█258| let sid = remote_session.unwrap_or(session_id);█...█413| b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";█*414| let line = ndjson::encode_line(&AttachRecord::Output {█415| seq: brain.controller_resume_floor(),█416| data_b64: encode_bytes(notice),█417| });█*418| let _ = wire.net_stream_send(stream_id, &line, None, true);█419| feed_rest(endpoint, crate::resting::RestEvent::Detach);█420| return Err(e);█421| }█...█538| // renders the busy guidance client-side from driven_by.█*539| wire.net_stream_send(stream_id, &[], None, true)?;█540| return Ok(AttachServeOutcome::Refused);█541| [...35183ch elided...] use crate::registryhost::RegistryHost;█...█63|/// The registry leg as a [`PumpWorker`]: own-row re-advertisement█*64|/// ([`RegistryHost::advertise_local`] -> one feed stream per peer). The node█65|/// re-advertises its own rows; peers reconverge within one cadence█66|/// (replay-is-recovery). Own rows only - a relayed third-party row would carry█67|/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).█...█83| /// Adverts computed in `pre_round`, consumed per-subnet in `peer_step`.█*84| adverts: Vec<RegistryFeedRecord>,█85|}█86|█87|impl RegistryWorker {█...█171| .filter(|u| u.subnet() == subnet)█*172| .flat_map(|u| u.encode_line())█173| .collect();█174| if !lines.is_empty() {█175| io.push_feed(&lines)?;\u000f█</out>█# Tool call ¶█//Finding registry replay tests█grep(pattern=\"Registry|registry.*stream|serve_registry_feed|registry feed|stream.*registry|redispatch\", path=\"crates/spt-daemon/tests; crates/spt-daemon/src/dispatch.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## access.rs█21|use spt_daemon::nethost::{NetConfig, NetHost};█*22|use spt_daemon::registryhost::RegistryHost;█23|use spt_daemon::wan::{receive_wan, WanOutcome};█24|use spt_daemon::Broker;█25|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};█...█111| owlery: &std::path::Path,█*112| registry: &RegistryHost,█113|) -> WanOutcome {█114| loop {█115| match brain.read_event().expect(\"read event\") {█...█121| if let Some(rec) = decoder.push(&bytes).into_iter().next() {█*122| return receive_wan(&rec, origin, owlery, registry);█123| }█124| }█125| BrokerEvent::NetStreamEof { .. } => panic!(\"eof before the record arrived\"),█...█158| let owlery = perch::owlery_dir();█*159| // An empty registry: no origin advertises a Gateway type, so the user-msg█160| // gate fail-closes - these tests assert the access gate, not the type gate.█*161| let registry = RegistryHost::new(\"b-access\");█162|█163| // Restrict the target BEFORE any traffic: whitelist names some other node,█164| // so sender A is non-listed.█...█196| assert_eq!(█*197| [...27951ch elided...] ' broker connections exactly like a production█5|//! brain death, which no in-process thread can simulate (a parked serve-loop█...█15| let broker = std::env::args().nth(1).expect(\"usage: dispatch_fixture <broker_name>\");█*16| let registry = Arc::new(spt_daemon::registryhost::RegistryHost::new_at(█17| \"fixturenode\",█18| spt_store::epoch::EpochSource::load_from(&spt_store::perch::epoch_file()),█*19| spt_daemon::presence::registry_snapshot_dir(),█20| ));█21| let paths = spt_daemon::dispatch::DispatchPaths {█22| owlery: spt_store::perch::owlery_dir(),█...█29| &paths,█*30| registry,█31| &stop,█32| Duration::from_millis(50),█33| ) {█Showing files 1-20 of 23+. Use skip=20 for the next page, or narrow paths/pattern.\u000f█</out>█# Tool call ¶█//Reading stall RCA summary█read(path=\".claude/reports/2026-07-16-redispatch-stall/rca-summary.md\", selector=null)█<out>█\u000e# REDISPATCH-STALL - v0.34 field-stall RCA summary (hertz 2026-07-16, doyle seam-verified same day)█Repo-side condensation of the hertz RCA delivered 2026-07-16 (full text rode the█doyle sptc-drain spill; fix design 1-9, the seven-test matrix, and the█fast-field-discriminator were accepted as filed and ride█`docs/REDISPATCH-STALL-TRIAGE.md` verbatim).█## Field shape█- Live v0.34 boxes: recurrent 20-30s PTY/RC freezes, observed DISPATCH tails█17-62s; poison interarrival median 95.5s.█- 33 observed `CONN_WRITE_POISONED` windows, ALL 15,000-15,154 ms exactly -█the round-4 SharedSend bounded-write deadline (15s) firing back-to-back.█- 4361 `stream-sub-attach` records per brain generation, 584 `PUMP_PEER_FAIL`.█- Per-stream, not broker-wide: unrelated conns progressed mid-poison█(broker-wide-wedge theory FALSIFIED).█## Mechanism (composition, not one new timer)█REDISPATCH-TRUTH W1's retryable claims (500ms/1s ×3) compose with the broad█`Err(_)` opener fallback (`dispatch.rs:414` - the comment intends█old-broker-only, the arm catches ANY error including transport█timeout/EOF/poison) to repeatedly manufacture throwaway peek subscribers.█Each `StreamLog::attach` (`nethost.rs:322 [...1346ch elided...] .012s█exact** - dead center of the fingerprint) -> `DISPATCH:7058 Failed(fill whole█buffer)` +1.046s post-poison -> retry subscriber conn 18273 +3.034s█post-poison, clean close in 8ms. `daemon status` answered in 0.39s DURING the█window (net_up=true, heartbeat current) - per-stream transient wedge█CONFIRMED, broker-wide IPC freeze falsified a second time on live data.█Identity caveat: logs do not bind stream 7058's opener to webbie - timing█correlation only; fix 9's endpoint-keyed telemetry closes that gap█(counter-63 catch = bound proof). This trace is the before-baseline for the█field discriminators.█## Where the full design lives█`docs/REDISPATCH-STALL-TRIAGE.md` (fix set 1-9, seven-test matrix, gate█rulings, field discriminators) + ADR-0038 Amendment (2026-07-16) +█KNOWN-HAZARDS 7.43.█\u000f█</out>█# Tool call ¶█//Reading redispatch truth RCA█read(path=\".claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt\", selector=null)█<out>█\u000eHERTZ RCA - `spt daemon refresh` / `spt update` freezes existing remote session PTYs█Verdict: target-brain refresh exposes a production redispatch bug. Hosted PTYs and broker-held QUIC streams survive, but active remote attach handlers are brain-owned and must be reconstructed. The fresh dispatcher indiscriminately replays every historical peer stream still in the broker table-including finished attach streams-and can let stale workers steal and then clear current controllers. A second adjacent defect prevents recovery when an active stream's opener has rolled out of its bounded history.█Causal chain:█1. `spt daemon refresh` raises the supervisor brain-restart signal. Broker and hosted PTY child/master resources remain alive; only the brain process and its dispatcher/serve workers die. `spt update` uses the same brain-cycle path after apply.█2. Every active cross-node `spt rc` has a peer-initiated broker-held net stream. Its target-side `serve_attach` worker owns a broker connection labelled `role=brain stream-subscriber stream=X controller session=Y`; therefore that semantic relay dies with the old brain even though the PTY and QUIC stream remain.█3. The new brain intentionally [...4415ch elided...] , served, and finished streams.█D. Add production-path regressions:█1. Create a finished historical Attach plus a current active Attach for the same endpoint/origin, restart only target brain/dispatcher, and prove the historical stream neither takes nor clears the current controller; current input/output remains exactly once without detach.█2. Push >4096 transport chunks on an active Attach so seq0/opener is evicted, restart dispatcher, and prove durable classification resumes the same operator stream.█3. Inject one transient worker-start failure and prove claim retry recovers without duplicate controller/output.█This is the source-level RCA. No live refresh/update was rerun during diagnosis; the live incident logs plus code path were used to avoid freezing active sessions again.\u000f█</out>█# Tool call ¶█//Locating registry cadence loop█grep(pattern=\"due_reg|REGISTRY|registry\", path=\"crates/spt-daemon/src/peerloop.rs; crates/spt-daemon/src/pump.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000ePath not found: crates/spt-daemon/src/peerloop.rs, crates/spt-daemon/src/pump.rs\u000f█</out>█# Tool call ¶█//Mapping peer loop declarations█read(path=\"crates/spt-daemon/src/peerloop.rs\", selector=null)█<out>█\u000ePath 'crates/spt-daemon/src/peerloop.rs' not found\u000f█</out>█# Tool call ¶█//Mapping pump declarations█read(path=\"crates/spt-daemon/src/pump.rs\", selector=null)█<out>█\u000ePath 'crates/spt-daemon/src/pump.rs' not found\u000f█</out>█# Assistant ¶█_**Locating pump module and reading push semantics**_█# Tool call ¶█//Mapping pump module█read(path=\"crates/spt-daemon/src/pump\", selector=null)█<out>█\u000e.█- mod.rs 91.5KB 6h ago█- health.rs 14.5KB 6h ago█- update.rs 7.9KB 1w ago█- sync.rs 4.4KB 1w ago█- registry.rs 14.6KB 1w ago█- notif.rs 1.9KB 1w ago\u000f█</out>█# Tool call ¶█//Reading registry pump worker█read(path=\"crates/spt-daemon/src/pump/registry.rs\", selector=\"1-220\")█<out>█\u000e//! The registry pump worker (pump-seam P2) - the first leg cut behind the█//! [`super::PumpWorker`] seam.█//!█//! `poll_wake` consumes the event-driven advertisement marker (force a round█//! NOW on an endpoint online/offline transition); `pre_round` runs the█//! node-local sweep (repair-evictions -> silent-peer eviction -> due seed█//! rotations -> re-advertise) ONCE per due round and holds the produced adverts█//! as worker state; `peer_step` pushes this node's own rows for the given█//! subnet over the peer's feed stream. Construction-captured deps: the shared█//! [`RegistryHost`], its `PathBuf` slices out of [`super::PumpPaths`], and the█//! two registry cadence knobs.█use std::io;█use std::path::PathBuf;█use std::sync::Arc;█use std::time::Duration;█use spt_net::net::replicate::RegistryFeedRecord;█use spt_store::subnet::SubnetStore;█use crate::registryhost::RegistryHost;█use super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};█/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,█/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the█/// prior generation one-deep for the re-seed grace) and clear its pending entry.█/ [...7950ch elided...] fire_due_rotations(&subnets_path, &rotations_path, 1_000);█let after = SubnetStore::load_from(&subnets_path);█let home = after.find(\"home\").unwrap();█assert_eq!(home.epoch, before_home.epoch + 1, \"due subnet rotated\");█assert_eq!(█home.prev_seed_hex.as_deref(),█Some(before_home.seed_hex.as_str()),█\"prior seed retained for the grace\"█);█assert_eq!(after.find(\"work\").unwrap().epoch, 1, \"not-due subnet untouched\");█let pend = spt_store::rotation::RotationPending::load_from(&rotations_path);█assert!(!pend.subnets.contains_key(\"home\"), \"fired entry cleared\");█assert!(pend.subnets.contains_key(\"work\"), \"pending entry kept\");█}█...█}█[Showing lines 1-223 of 334. Use :224 to continue]\u000f█</out>█# Tool call ¶█//Reading push feed transport█read(path=\"crates/spt-daemon/src/pump/mod.rs\", selector=\"700-850,1040-1140\")█<out>█\u000epub fn run_peer_pump(█...█) -> io::Result<()> {█...█while !stop.load(Ordering::Relaxed) {█...█// broker stopped answering the stream (dead carrier) -> bubble -> supervised█// restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still█// restarts the pump; a dead PEER never does).█// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]█let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;█loop {█let read_deadline = if pending.is_empty() {█Instant::now() // no outstanding submits - sweep only what is queued█} else {█round_deadline█};█match classify_drain_read(events.read_event_until(Some(read_deadline))) {█DrainStep::Apply(ev) => handle_presence_event(█ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,█&mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,█)?,█DrainStep::Skip => continue, // the event carrier only carries presence█DrainStep::RoundDone => break, // quiet (TimedOut) - dra [...8975ch elided...] et_open_stream(conn_id, None)?;█brain.net_stream_send(opened.stream_id, feed, None, true)?;█Ok(())█}█/// Connect a fresh brain IPC client in PUMP mode (retrying through broker█/// startup): the carrier is split at construction with the per-call deadline█/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the█/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).█// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]█fn connect(broker_name: &str) -> io::Result<Brain> {█let mut last = io::Error::other(\"broker never answered\");█for _ in 0..300 {█match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {█Ok(b) => return Ok(b),█Err(e) => last = e,█}█thread::sleep(Duration::from_millis(10));█}█Err(last)█}\u000f█</out>█# Tool call ¶█//Tracing pump connection reuse█grep(pattern=\"struct PeerIo|trait PumpIo|push_feed|net_dial|conn_id|disconnect|peer_step|run_peer_pump|cold_start\", path=\"crates/spt-daemon/src/pump\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/pump/█## health.rs█132|█*133| /// A peer disconnected (no failure - redial-eligible). Only the live█134| /// count moves.█*135| pub fn note_disconnected(&mut self, live_peers: usize) {█136| self.live_peers = live_peers;█137| }█138|█## mod.rs█7|//! whom* (every roster member of every non-detached member subnet); each worker█*8|//! owns *what* (its node-local `pre_round` and its per-peer `peer_step`), a thin█9|//! call over an already-shipped, idempotent driver. The four legs, in fan-out█10|//! order, each in its own module:█11|//!█...█34|//! choreography table below): [`compute_due`] (prime-all / wake-forces-one),█*35|//! [`pre_rounds`] (once per due round, before any peer_step), [`run_due`]█36|//! (abort-on-first-fail), [`mark_ran`] (the §V4 stagger), [`fan_targets`]█37|//! (detached-skip). LLM-bearing work is never a [`PumpWorker`] (KNOWN-HAZARDS█38|//! 7.4) - see the trait's contract.█...█78|use crate::msg::{█*79| NetPresenceEvent, PRESENCE_CONNECTED, PRESENCE_DIAL_FAILED, PRESENCE_DISCONNECTED,█80|};█81|use crate::registryhost::RegistryHost;█82|use crate::sync::{SyncPolicy, SyncPullReport};█...█140|/// dial-eligible [...6660ch elided...] let report = request_sync(&mut *io.brain, io.conn_id, &want, open_op, &cs, &self.scratch)?;█108| (self.hooks.on_pull)(peer_hex, &report);█109| }█110| Ok(())█## update.rs█2|//!█*3|//! `peer_step` runs the REQ-UPD-1/4/6 leg under the PRODUCTION gate: the█4|//! VerifyPolicy floor arithmetic (keys + channel pin from `release-keys.json`,█5|//! version floor = the staged release as the rollback gate, wall clock for█6|//! expiry only), the [`request_update`] pull, and - on a fresh stage - the█...█84|█*85| fn peer_step(█86| &mut self,█87| io: &mut PeerIo,█88| subnet: &str,█...█114| &mut *io.brain,█*115| io.conn_id,█116| open_op,█117| &BrokerAbi::current(),█118| &policy,\u000f█</out>█# Tool call ¶█//Checking feed completion retirement█grep(pattern=\"push_feed|finish=true|retire|stream_infos|registry feed\", path=\"crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs; crates/spt-daemon/tests/mesh_recovery.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## mesh.rs█119|█*120|/// The inert/registry-only cadence: only registry feeds flow (notif/sync/█121|/// update are pushed far enough out to never fire), so the shared canonical█122|/// notif/context stores stay untouched across nodes.█123|fn cadence() -> PumpCadence {\u000f█</out>█# Assistant ¶█_**Analyzing feed stream lifecycle and EOF handling**█**Investigating registry feed blocking causes**█**Assessing Brain read blocking by pipe buffer**█**Identifying SubscriberSeat replay bug**█**Analyzing broker connection churn and write failures**█**Examining BrokerConn drop retention issue**█**Assessing broker worker retention risks**█**Investigating Git commit discrepancies**_█# Tool call ¶█//Checking current checkout█read(path=\".git/HEAD\", selector=\"raw\")█<out>█\u000eref: refs/heads/docs/v0360-notes-plain-language█\u000f█</out>█# Tool call ¶█//Checking v036 tag metadata█read(path=\".git/packed-refs\", selector=\"raw\")█<out>█\u000e# pack-refs with: peeled fully-peeled sorted█1941eb823b36cc6ecc95dcbffe49b5a2047335fd refs/heads/design/endpoint-creation-flow█c5887db27f6c8576decec071c073e76c5144ba66 refs/heads/dev-freeform█f747e1e7048831b51c8aed18a611d10238c3eda4 refs/heads/docs/msg-envelope-adr-0020█461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/doyle-work█aa5fa2a42e7ab13045d1827eefcddade797bac73 refs/heads/feat/translate-proof█3da4aa269253b7b8bf0b3c9dca31c6e5ee2f51d1 refs/heads/field-bug-milestone█b4275c3c4fcce720bf244b32f42a34b18d419443 refs/heads/fix/digest-proof-session-id-keys█4fd028d5ceda811557f361162f50987db929eb02 refs/heads/fix/f019-translation-install-dir-resolve█60fadd25f3b62712607822692d4b7596b4a68de3 refs/heads/fix/v0143-raw-inject-removal█be3d207723b2e59b01bbbc58151221af0504c58f refs/heads/fix/win-program-resolve█5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/heads/m10-digest-extractor█672b9288e5669c023bb3774abf8a339165e70082 refs/heads/m11-shell-substrate█2da139504d9abeaae4ad0430b47a0dc1f64648ef refs/heads/m12-w1-bringup-rc█461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/main█08c476fe81d3c4c26812917d9acc28ef6000611c refs/heads/pump-carrier-fix█74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 ref [...6237ch elided...] 0dd604d378c77233876ff9f9d895d6ff7e refs/tags/v0.7.0█cb0560052c7392d386d9e68db359bbbf3138dcfe refs/tags/v0.7.1█1582e32ba704e7cad770a42265b5c90e044ace37 refs/tags/v0.7.2█22a9c58cfb7b5259dff99670012108095cb3e376 refs/tags/v0.7.3█70294ef2c20f3b07866eb97f9e9ac60ee61460e9 refs/tags/v0.8.0█325431aeaa321d26bca4cc83e8769bb84b77d9c7 refs/tags/v0.8.1█4ac17c29978c0a087682a343cef4befc1aff9c3d refs/tags/v0.8.2█8ed7fcf4ec1db98a36b3baf9e34a162a2a9a1d96 refs/tags/v0.8.3█^b6470c1c4ababb407d44ed2c38b4354485f0b6b1█29a71705483f0fc3f5a9c726b7972d1142276e0e refs/tags/v0.8.4█^c9ff19f1fd313b312395cdf4eace60cbe677e007█a1243ee1585e94674ee38b0858ef1aafb2465875 refs/tags/v0.9.0█^c448b321bef3ea452184f974f2c6b547de058653█ab1d738361d9c607e0a918f9839fda96da723839 refs/tags/v0.9.1█^7f93ecd59a900f4890f787dd56e2c69d8ededaec█\u000f█</out>█# Tool call ¶█//Finding registry feed scaling█grep(pattern=\"advertise_local|save_snapshot|save_to|apply_feed_flips|for .*updates|for .*labels|heard|registry snapshot\", path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"200-360\", case=false, gitignore=true, skip=null)█<out>█\u000e 220| ) -> Vec<RegistryApplyVerdict> {█*221| self.apply_feed_flips(origin_node, updates, policy).0█222| }█223|█224| /// [`apply_feed`](Self::apply_feed) that ALSO reports **attention█...█240| // [impl->REQ-INST-3]█*241| pub fn apply_feed_flips(█242| &self,█243| origin_node: &str,█244| updates: &[RegistryUpdate],█...█288| if admitted_any {█*289| self.heard█290| .lock()█291| .unwrap()█292| .insert(origin_node.to_string(), wall_ms());█...█299| /// Apply inbound node-level label updates (REQ-SUBNET-3) under the SAME█*300| /// fail-closed gate as [`apply_feed_flips`]: a non-member subnet or an█301| /// untrusted origin is refused; an admitted update merges via█302| /// [`SubnetRegistry::merge_node_label`] (epoch lease) and stamps the█303| /// origin's gossip-recency (an admitted label feed proves the peer is up,█...█335| if admitted_any {█*336| self.heard█337| .lock()█338| .unwrap()█339| .insert(origin_node.to_string(), wall_ms());\u000f█</out>█# Tool call ¶█//Reading registry apply path█read(path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"200-360\")█<out>█\u000e /// otherwise issue the same id from independent counters. (Field bug: the█/// pump minted consent notif_ids from the stream-open op counter█/// `pump-ops.json` - a different file - so on a node whose two counters█/// were both low the consent notif and a presence/notify notif collided on█/// `node:1`. The rig's presence-redirect rung caught it: `UNIQUE█/// constraint failed: notifs.notif_id`.) Locks the shared cursor so the█/// dispatcher's registry arm and the pump never double-mint.█// [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]█pub fn with_epoch<R>(&self, f: impl FnOnce(&mut EpochSource) -> R) -> R {█f(&mut self.epoch.lock().unwrap())█}█/// Apply one inbound feed under the gate. `origin_node` MUST be the█/// handshake-proven stream-table origin, never payload bytes.█// [impl->REQ-INST-7]█// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]█pub fn apply_feed(█&self,█origin_node: &str,█updates: &[RegistryUpdate],█policy: &RegistryGatePolicy,█) -> Vec<RegistryApplyVerdict> {█self.apply_feed_flips(origin_node, updates, policy).0█}█/// [`apply_feed`](Self::apply_feed) that ALSO rep [...5170ch elided...] self.stamp_admit();█}█self.write_snapshots();█verdicts█}█/// Record an admitted inbound feed (any origin) for the health surface.█// [impl->REQ-PUMP-STAGE-TRUTH]█fn stamp_admit(&self) {█self.last_admit_ms█.store(wall_ms(), std::sync::atomic::Ordering::Relaxed);█}█/// Wall-clock ms of the last admitted inbound registry/label feed, if any█/// this process lifetime (ADR-0039 Decision 5 status field).█// [impl->REQ-PUMP-STAGE-TRUTH]█pub fn last_admit_ms(&self) -> Option<u64> {█match self█.last_admit_ms█.load(std::sync::atomic::Ordering::Relaxed)█{█0 => None,█ms => Some(ms),█}█}█[Showing lines 199-363 of 2238. Use :364 to continue]\u000f█</out>█# Tool call ¶█//Reading pump brain send behavior█read(path=\"crates/spt-daemon/src/brain.rs\", selector=\"1395-1435\")█<out>█\u000e pub fn net_open_stream(█...█) -> io::Result<NetStreamOpened> {█...█loop {█...█}█}█/// Write bytes to a held stream over the broker (D4b). With `op_id` the█/// write is exactly-once and this blocks for the `net-sent` ack (returned);█/// without one it is a direct fire-and-forget write (`None` returned).█/// `finish` closes the send side after the write.█// [impl->REQ-HAZARD-RESTART-IDEMPOTENT]█pub fn net_stream_send(█&mut self,█stream_id: u64,█bytes: &[u8],█op: Option<MintedOp>,█finish: bool,█) -> io::Result<Option<NetSent>> {█self.send(█KIND_NET_STREAM_SEND,█serde_json::to_value(NetStreamSendReq {█stream_id,█data_b64: encode_bytes(bytes),█op_id: op.map(|o| o.seq),█minter: op.map(|o| o.minter),█finish,█})█.expect(\"NetStreamSendReq serializes\"),█)?;█if op.is_none() {█return Ok(None);█}█let want_seq = op.map(|o| o.seq);█let deadline = self.call_deadline();█loop {█match self.read_event_until(deadline)? {█BrokerEvent::NetSent(ev) if Some(ev.op_id) == want_seq => return Ok(Some(ev)),█BrokerEvent::Error { message } => return Err(io::Error::other(message)),█_ => continue,█}█}█}█/// List the streams the broker holds (D4b) - how a re-attaching brain█/// finds streams (incl. peer-initiated ones) that arrived while it was down.█pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {█self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;█loop {█...█}█}█[Showing lines 1394-1438 of 2247. Use :1439 to continue]\u000f█</out>█# Tool call ¶█//Finding Registry feed counts█grep(pattern=\"stream-sub-attach.*Registry|family=Registry|stream=524|stream=1161|stream=1347|stream=3053|stream=3810|DISPATCH_EV\", path=\".claude; *.log; **/*.log\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000e# .claude/█## todlando-commune.md█27|- nethost: SubscriberSeat (bounded queue 1024 + writer thread, halt-at-first-write-failure, poisoned/done flags) in StreamLog + PresenceLog; begin_attach snapshot + replay-enqueue OUTSIDE mutex; drop_seat (poison cancels StreamLease); send_stream refuses canceled lease; POISON-only reinstall refusal (healthy displacement stays legal - blanket refusal TIMEOUTed applyhost, learned hard).█*28|- dispatch: is_unsupported_verb narrows Err(_) fallback; classify_failure + CircuitBreaker (2s doubling cap 30s, global, reset on Served); outcome_transition SeatBlocked = no-budget-burn breaker-paced retry; MAX_DISPATCH_WORKERS=8 pool; dispatch_event keyed telemetry (gen/stream/family/attempt/conn) + WorkerReport{outcome,family,endpoint} + line_endpoint; pre-serve terminal exclusion ('no such stream' -> Served terminal).█29|- gen tokens (fix 6): AttachRecord.gen (client mints now_ms) -> SubscribeReq/UnsubscribeReq.gen -> ControllerSink.attach_gen; same-identity re-take REFUSED when gen!=0 && incumbent!=0 && gen<incumbent (equal = reconstruction OK, 0 = legacy/N-1); detach_if_gen stale never clears controller; serve_attach serve_gen -> detach_session_gen.█30|</ [...24ch elided...] .█35|(2) Read memory redispatch-stall-progress.md (BUILD CHECKPOINT + REMAINING TODO) - it is the authoritative state.█*36|(3) Continue REMAINING TODO in order: fix-8 sessions clone-then-drop audit (broker.rs serve() end ~3211 + info.json-under-sessions-lock sites) -> units (seat halt/remove per site; overflow-detach+cursor; breaker trip/gate/reset; is_unsupported_verb + classify_failure; dispatch_event key completeness; stale-gen refuse ×2) -> ints tests/redispatch_stall.rs T1-T7 (pump.rs/mesh_recovery.rs harness; proving() mixed-pair blackhole = wedge; T7 real-traffic N-1) -> HEAVY ×3 strings -> preflight (redispatch...█37|(4) Box discipline: check CI/gate ownership before local suites; spt commands may auto-start the stopped daemon - fine.█38|!!wake!!█[Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Verifying v0.36 tag and subscriber seat cleanup**█**Identifying sender stream entry retention issue**█**Linking stream scanning to high CPU and IPC load**█**Clarifying I/O counter interpretations**█**Identifying stream lifecycle leak in registry feed**█**Linking stream growth to dispatcher clogging**█**Analyzing broker thread behavior and connections**█**Investigating thread count discrepancy and connection leaks**█**Planning stream retirement and connection cleanup fix**█**Assessing command retirement timing risks**█**Examining stream ownership in register_stream**_█# Tool call ¶█//Reading stream entry registration█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"760-850,980-1060,1600-1720\")█<out>█\u000eimpl SendHalf {█...█async fn finish(&mut self) -> io::Result<()> {█match self {█...█SendHalf::Loopback(w) => w.shutdown().await,█}█}█}█/// A stream's read half - fed into the single read pump in [`register_stream`].█/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.█/// Both append to the same [`StreamLog`], so the subscriber (and everything█/// above it) cannot tell the transports apart.█enum RecvHalf {█Quic(RecvStream),█Loopback(ReadHalf<DuplexStream>),█}█/// One live stream the broker holds (QUIC or loopback): the send half (brain█/// writes ride it over IPC) + the sequenced read log.█struct StreamEntry {█conn_id: u64,█remote_id_hex: String,█/// Whether THIS node opened the stream (`open_stream`) vs the peer█/// (accept loop). The inbound dispatcher (D9-1) claims only█/// peer-initiated streams; without this fact the table is ambiguous -█/// it holds locally-opened outbound streams too (gotcha: a dispatcher█/// that served its own requester stream would deadlock on itself).█initiated_locally: bool,█/// The send half, serialized behind an async mutex (writes happen on the█[...11534ch elided...] plit(peer_io);█// Register the peer (inbound) row first so it is in the table█// before the operator can write its Request onto the wire - the█// dispatcher must never miss the stream.█register_stream(█&shared,█conn_id,█&remote_id_hex,█SendHalf::Loopback(peer_w),█RecvHalf::Loopback(peer_r),█false,█);█Ok(register_stream(█&shared,█conn_id,█&remote_id_hex,█SendHalf::Loopback(op_w),█RecvHalf::Loopback(op_r),█true,█))█}),█}█}\u000f█</out>█# Tool call ¶█//Finding physical stream counts█grep(pattern=\"stream_count|streams\\\\.len|test_.*stream|stream_infos\\\\(\\\\).*len|retired\", path=\"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/tests/pump.rs; crates/spt-daemon/tests/mesh.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/█## nethost.rs█792| room: Arc<tokio::sync::Notify>,█*793| /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:█794| /// retire must never sever the transport - the worker's reply send is█795| /// fire-and-forget, so a retire that dropped this entry could overtake the█796| /// still-queued reply inside the broker and reset the never-finished QUIC█...█799| // [impl->REQ-REDISPATCH-FINISHED-RETIRE]█*800| retired: std::sync::atomic::AtomicBool,█801|}█802|█803|/// Tables shared between the host's sync surface and its runtime tasks█...█1019| room: Arc::clone(&room),█*1020| retired: std::sync::atomic::AtomicBool::new(false),█1021| }),█1022| );█1023|█...█1802| /// streams (including peer-initiated ones that arrived while it was down).█*1803| /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this█1804| /// enumeration is exactly what a future dispatcher generation would replay.█1805| pub fn stream_infos(&self) -> Vec<NetStreamInfo> {█1806| let streams = self.shared.streams.lock().unwrap();█...█1808| .iter()█*1809| [...3278ch elided...] hermetic(&Identity::generate())).expect(\"host start\");█2437| let (owner, shell) = host.open_loopback_pair().expect(\"open pair\");█2438| // The requester's one-shot request: write + FIN (the rest/sync shape -█...█2451| host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)█*2452| .expect(\"a retired row still accepts the in-flight reply\");█2453| let mut got = Vec::new();█2454| let mut finished = false;█2455| for _ in 0..400 {█...█2465| String::from_utf8_lossy(&got).contains(\"\\\"outcome\\\":\\\"edge\\\"\"),█*2466| \"the reply flushed through the retired row (got {got:?})\"█2467| );█2468| assert!(finished, \"the reply's FIN reached the requester side (clean end, not torn)\");█2469| }\u000f█</out>█# Tool call ¶█//Finding push feed API tests█grep(pattern=\"push_feed\\\\(|one stream per feed|fire-and-forget|finished on send\", path=\"crates/spt-daemon/src/pump; crates/spt-daemon/tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/█## src/pump/█### mod.rs█48|//!█*49|//! Every pump write is fire-and-forget (`op_id: None`) - a journaled█50|//! `net_stream_send` blocks on its ack and DISC\u000eARDS interleaved events█51|//! (gotcha #1). Stream opens for push feeds carry no op either: the feeds█52|//! are idempotent by construction. The sync pull's `open_op` IS durable█...█366|/// **KH 7.4 - never an LLM-bearing worker.** A pump worker is cadenced,█*367|/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile█368|/// turn) runs at activation paths with a manifest runtime to ride (ADR-0013),█369|/// NEVER on the pump thread - the seam must not become the invitation to put█370|/// it here.█...█492|impl PeerIo<'_> {█*493| /// Open a fresh stream and write one whole feed fire-and-forget (the█*494| /// push-feed discipline - `op_id: None`, finished on send).█*495| fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {█*496| push_feed(self.brain, self.conn_id, feed)█497| }█498|█499| /// Mint the next durable open-op from the pump's sole counter (exactly-once█...█1115|█*1116|/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget, [...2145ch elided...] broker.test_session_viewer_count(sid);█346| assert!(█### pump.rs█672|/// Ok on stop - a dead PEER never bubbles Err, so it NEVER triggers a supervised█*673|/// restart. (`cadence.sync/update` are parked long so only the fire-and-forget█674|/// registry/notif push legs fire - the live peer stays connected + advertised█675|/// without a reply-read, keeping the round fast and the connect signal stable.)█676|#[test]█### redispatch_stall.rs█478| f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");█*479| // Give the fire-and-forget subscribe a beat to process, then█480| // probe the lease with a real send on a separate conn.█481| thread::sleep(Duration::from_millis(300));█482| let mut s = connect_retry(&name_a);\u000f█</out>█# Tool call ¶█//Finding stream removal semantics█grep(pattern=\"streams\\\\.remove|retain\\\\(\\\\|_, e\\\\| e\\\\.conn_id|retire_stream|retired\\\\.swap|finished.*remove\", path=\"crates/spt-daemon/src/nethost.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 966| .unwrap()█*967| .retain(|_, e| e.conn_id != id);█968| sh_close█969| .presence█970| .lock()█...█1835| // [impl->REQ-REDISPATCH-FINISHED-RETIRE]█*1836| pub fn retire_stream(&self, stream_id: u64) -> bool {█1837| let Some(entry) = self█1838| .shared█1839| .streams█...█2413|█*2414| assert!(host.retire_stream(shell), \"first retire hides the row\");█2415| let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();█2416| assert!(!ids.contains(&shell), \"retired row left the enumeration\");█2417| assert!(ids.contains(&owner), \"the active sibling row stays\");█*2418| assert!(!host.retire_stream(shell), \"second retire is a no-op\");█2419| assert!(█2420| host.stream_opener(shell).is_none(),█2421| \"a retired stream answers no-such-stream\"█...█2447| // Retire FIRST - the ordering the race loses in the field.█*2448| assert!(host.retire_stream(shell), \"retire the serving row\");█2449|█2450| // The worker's reply lands AFTER retirement and must still deliver.█2451| host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\u000f█</out>█# Assistant ¶█_**Prioritizing communication to Main**_█# Tool call ¶█//Sending Registry root cause█irc(op=\"send\", to=\"Main\", message=\"Registry-specific RCA: periodic pump opens+FINs a fresh bidi stream every 30s/peer/subnet (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`) but sender local StreamEntry is never retired; receiver `serve_registry_feed` returns on EOF without FINning reverse half (`dispatch.rs:984-1046`). `retire [...674ch elided...] spatcher, N registry-only rounds, assert eligible enumeration/physical stream count+broker conn_handler_count plateau and post-refresh stream-sub-attach Registry count is O(active), no poison/replay.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- Main: injected\u000f█</out>█# Assistant ¶█_**Investigating thread cleanup and stream retirement**█**Identifying overflow blocking worker reads**█**Tracing worker thread retention causes**█**Tracing replay loop cause**█**Analyzing feed size and decoding impact**█**Estimating historical row volume**█**Inspecting write_snapshots atomicity**_█# Tool call ¶█//Locating snapshot writes█grep(pattern=\"fn write_snapshots|write_snapshots\\\\(|snapshot\", path=\"crates/spt-daemon/src/registryhost.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 23|//! consumers (`spt send`'s WAN resolution, D9-1-4) the host mirrors each█*24|//! subnet's registry to an atomic JSON snapshot under█25|//! `identity/registry/` - readers tolerate staleness (the 4.3█26|//! stale-clean posture), never block on the daemon.█27|// [impl->REQ-INST-7]█...█111| /// a probe; only stale rows get dialed. Local-only (never on the wire);█*112| /// mirrored to `heard.meta` beside the snapshots for the CLI reader.█113| heard: Mutex<BTreeMap<String, u64>>,█114| /// Wall-clock ms of the LAST admitted inbound registry/label feed from█115| /// ANY origin - the \"last admitted registry update\" health field█...█122| epoch: Mutex<EpochSource>,█*123| /// Where snapshots land (`identity/registry/<subnet>.json`).█*124| snapshot_dir: PathBuf,█125|}█126|█127|impl RegistryHost {█128| /// Stand the host up for this node, with the canonical epoch counter and█*129| /// snapshot dir under the spt home. Reads the OS hostname here - daemon█130| /// startup IS the label's re-check point (REQ-SUBNET-3).█131| pub fn new(node_hex: &str) -> RegistryHost {█132| RegistryHost::new_at(█...█141| /// Explicit-paths constructor [...9783ch elided...] let p = policy(\"home\", \"bb22\");█...█2118|█*2119| // Past the grace the aged Offline ghost evicts and the snapshot is rewritten.█2120| let later = 1_000 + 300_000 + 1;█2121| assert_eq!(h.evict_aged_offline_rows_at(grace, later), 1);█2122| assert!(h.rows(\"home\", \"ghost\").is_empty(), \"aged Offline ghost evicted\");█*2123| let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");█2124| let parsed: SubnetRegistry =█2125| serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();█2126| assert!(█2127| parsed.instances(\"ghost\").is_empty(),█*2128| \"snapshot rewritten without the aged Offline ghost\"█2129| );█2130|█2131| // Idempotent: a second sweep finds nothing.\u000f█</out>█# Tool call ¶█//Reading snapshot write implementation█read(path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"630-730\")█<out>█\u000e pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█...█if let Some(label) = self.node_label.clone() {█...█}█drop(epoch);█drop(regs);█self.write_snapshots();█out█}█/// Evict every registry row authored by a **silent** peer node█/// (KNOWN-HAZARDS 4.10): a node not heard - no admitted inbound feed -█/// within `ttl` has its rows removed from every subnet registry and the█/// snapshots rewritten, so a dead node identity can no longer poison█/// bare-id resolution with phantom ambiguity. Own rows never decay (this█/// node authors them fresh each pump tick and is never in its own heard█/// map). A row whose author has NO heard stamp is treated as silent -█/// rows only enter via admitted feeds (which stamp) or `advertise_local`█/// (own rows, excluded), so an unstamped foreign row is already an█/// anomaly and fails toward eviction. A merely-offline peer reconverges█/// within one pump cadence of its return (re-advertisement from its█/// durable epoch). Returns the number of rows evicted.█// [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]█pub fn evict_silent_peers( [...2775ch elided...]█}█evicted += n;█}█drop(regs);█if evicted > 0 {█self.write_snapshots();█}█evicted█}█/// Every instance row for `endpoint_id` in `subnet` (the reconcile█/// election's input; empty when unknown).█pub fn rows(&self, subnet: &str, endpoint_id: &str) -> Vec<Instance> {█self.regs█.lock()█.unwrap()█.get(subnet)█.map(|r| r.instances(endpoint_id).to_vec())█.unwrap_or_default()█}█/// A point-in-time clone of one subnet's registry (resolution input).█pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {█self.regs.lock().unwrap().get(subnet).cloned()█}█[Showing lines 629-733 of 2238. Use :734 to continue]\u000f█</out>█# Tool call ¶█//Finding advertised row payload fields█grep(pattern=\"fn advertise_local|read_to_string|read\\\\(|load|for .*read_dir|instances|resources\", path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"360-640\", case=false, gitignore=true, skip=null)█<out>█\u000e 372| // [impl->REQ-INST-7]█*373| pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█*374| let subnets = SubnetStore::load();█*375| let mut vis = VisibilityStore::load();█376| // First-join home adoption (D9-5, REQ-INST-15): within one█377| // advertisement cadence of the node's first join (a pair lands the█378| // subnet record; no pairing-protocol threading needed), unhomed Self█...█388| // agent refines - CONTEXT §resource advertisement).█*389| let node_blurb = crate::config::DaemonConfig::load().resources_blurb;█390| // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT█391| // §Discovery scope): registered kind=\"shell\" adapters that chose█392| // broadcast=subnet ride every row this node authors - the other-node█...█431| let info = spt_store::info::read_info(&perch_path);█*432| let resources = info█433| .as_ref()█*434| .and_then(|i| i.resources.clone())█435| .or_else(|| node_blurb.clone());█436| // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat█437| // sta [...463ch elided...] riven_by=None so that WHO datum undercounts it).█...█498| epoch: ep,█*499| resources: resources.clone(),█500| last_active_ms,█501| shell_adapters: shell_adapters.clone(),█502| // The node label rides every row this node authors█...█568| .filter(|id| {█*569| reg.instances(id)█570| .iter()█571| .any(|i| i.node == self.node_hex && i.status.routable())█572| })█...█580| epoch: ep,█*581| resources: None,█582| last_active_ms: None,█583| shell_adapters: Vec::new(),█584| node_label: self.node_label.clone(),\u000f█</out>█# Tool call ¶█//Reading advertise scan█read(path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"360-640\")█<out>█\u000e pub fn last_admit_ms(&self) -> Option<u64> {█match self█...█.load(std::sync::atomic::Ordering::Relaxed)█{█0 => None,█ms => Some(ms),█}█}█/// Scan the owlery and (re-)advertise every self perch into every member█/// subnet it is visible in, each row stamped with a fresh epoch. Returns█/// the updates to push to peers (the node's own rows only - peers' rows█/// reconverge from their own re-advertisement, no transitive gossip in█/// v1: trust is pairwise per subnet, a relayed third-party row would█/// carry an origin the receiver can't gate honestly).█// [impl->REQ-INST-7]█pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█let subnets = SubnetStore::load();█let mut vis = VisibilityStore::load();█// First-join home adoption (D9-5, REQ-INST-15): within one█// advertisement cadence of the node's first join (a pair lands the█// subnet record; no pairing-protocol threading needed), unhomed Self█// perches adopt the sole subnet + seed their sync scope. Strictly█// adopt-if-unset; a multi-subnet store adopts nothing. [...14024ch elided...] : ep,█}));█}█}█drop(epoch);█drop(regs);█self.write_snapshots();█out█}█/// Evict every registry row authored by a **silent** peer node█/// (KNOWN-HAZARDS 4.10): a node not heard - no admitted inbound feed -█/// within `ttl` has its rows removed from every subnet registry and the█/// snapshots rewritten, so a dead node identity can no longer poison█/// bare-id resolution with phantom ambiguity. Own rows never decay (this█/// node authors them fresh each pump tick and is never in its own heard█/// map). A row whose author has NO heard stamp is treated as silent -█/// rows only enter via admitted feeds (which stamp) or `advertise_local`█[Showing lines 359-643 of 2238. Use :644 to continue]\u000f█</out>█# Tool call ¶█//Finding pump loop sleep cadence█grep(pattern=\"thread::sleep|PUMP_POLL|DEFAULT.*POLL|Duration::from_millis\", path=\"crates/spt-daemon/src/pump/mod.rs\", selector=\"500-700\", case=true, gitignore=true, skip=null)█<out>█\u000e 611| if !any_due {█*612| thread::sleep(TICK);█613| continue;█614| }█615|\u000f█</out>█# Tool call ¶█//Reading pump loop cadence█read(path=\"crates/spt-daemon/src/pump/mod.rs\", selector=\"500-680\")█<out>█\u000eimpl PeerIo<'_> {█...█/// Mint the next durable open-op from the pump's sole counter (exactly-once█/// open identity - a restart can never reuse one), tagged with the `pump`█/// journal minter (ADR-0034): the pump is the sole writer of `pump-ops.json`,█/// so its ops namespace under `pump` and never collide with cli/rc/shell ops.█fn open_op(&mut self) -> io::Result<MintedOp> {█Ok(MintedOp::new(Minter::Pump, self.ops.next_epoch()?))█}█}█/// Run the pump until `stop` is set. Owns one brain IPC connection and a█/// per-peer connection cache (an IO failure against a peer drops its cache█/// row; the next tick redials - conns are rebuilt freely, nothing leaks█/// because the broker's closed-watcher reaps dead conns).█///█/// The pure scheduler + fan-out (pump-seam P4, all four legs behind the seam):█/// heartbeat -> poll every worker's wake -> the [`due`] kernel -> each due█/// worker's `pre_round` (once, before any peer_step) -> per-subnet × per-peer█/// (`ensure_conn` -> each due worker's `peer_step` in fan-out order,█/// abort-on-first-fail) -> mark-after-round with a POST-round instant. No leg█/// logic lives here - the shell owns only *when* and *toward who [...8641ch elided...] nsert(peer_hex.clone(), next_peer_backoff(prev, round_start));█}█}█} else {█// No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage█// `address-resolution`). Transition-logged: the first█// unroutable round per peer is loud, repeats are quiet -█// the peer retries on its own cadence like before.█if health.note_failed(█peer_hex,█crate::seedproofx::STAGE_RESOLVE,█conns.len(),█now_ms(),█) {█...█}█}█}█}█...█}█...█}█[Showing lines 499-683 of 1982. Use :684 to continue]\u000f█</out>","textHead":"# User ¶█Complete the assignment below, thoroughly:█# Target█v0.36 REDISPATCH changes and tests/history available in repository. Non-goal: unrelated lifecycle code.█# Change█Assess whether stream retirement/reconstruction fixes can cause retained broker workers, replay loops, or connection churn after update/refresh. Compare intended O(active) behavior with thread/process evidence.█# Acceptance█Give a source-grounded causal assessment and targeted regression-test seam.█# Assistant ¶█_**Planning skill-based code exploration**_█# Tool call ¶█//Reading diagnosis workflow█read(path=\"skill://diagnose\", selector=null)█<out>█\u000e---█name: diagnose█description: 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.█---█# Diagnose█A discipline for hard bugs. Skip phases only when explicitly justified.█When exploring the codebase, use the project's domain glossary to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.█## Phase 1 - Build a feedback loop█**This is the skill.** Everything else is mechanical. If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause - bisection, hypothesis-testing, and instrumentation all just consume that signal. If you don't have one, no amount of staring at code will save you.█Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**█### Ways to construct one - try them in roughly this order█1. **Failing test** at whatever seam reaches the bug - unit, integration, e2e.█2. **Curl / HTTP script** against a running dev server [...5116ch elided...] # Phase 6 - Cleanup + post-mortem█Required before declaring done:█- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)█- [ ] Regression test passes (or absence of seam is documented)█- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)█- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)█- [ ] The hypothesis that turned out correct is stated in the commit / PR message - so the next debugger learns█**Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the `/improve-codebase-architecture` skill with the specifics. Make the recommendation **after** the fix is in, not before - you have more information now than when you started.█\u000f█</out>█# Tool call ¶█//Locating redispatch changes█grep(pattern=\"REDISPATCH|redispatch|re-dispatch|redispatching\", path=\".\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000eGrep timed out after 30s; narrow paths or pattern, or scope with `glob` first\u000f█</out>█# Tool call ¶█//Mapping broker sources█glob(path=\"**/*{broker,stream,dispatch,worker}*\", hidden=true, gitignore=true, limit=300)█<out>█\u000e# crates/█## spt-daemon/█### src/█dispatch.rs█broker.rs█### tests/█redispatch_stall.rs█broker.rs█redispatch.rs█net_worker_starve.rs█netstream.rs█netbroker.rs█dispatch.rs█#### fixtures/█dispatch_fixture.rs█## spt/█### src/api/█worker.rs█### tests/█worker_visibility_e2e.rs█worker_lifecycle_e2e.rs█## spt-store/src/█worker_reap.rs█worker_seq.rs█## spt-term/█### tests/█stream.rs█### src/█stream.rs█# docs/█## adr/█0038-broker-stream-lifecycle-truth.md█0018-broker-brain-process-isolation-restoration.md█0004-single-daemon-broker-brain-split-and-self-update.md█## spikes/█SPIKE-01-broker-handoff.md\u000f█</out>█# Tool call ¶█//Mapping repository history█glob(path=\".git/{logs,refs}/**/*\", hidden=true, gitignore=false, limit=300)█<out>█\u000e# .git/█## logs/█HEAD█### refs/█#### remotes/█##### origin/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█gateway-liveness█pump-w2-design█lifecycle-truth█###### docs/█v0360-notes-plain-language█readme-install-channel█reorg-plans-process█###### fix/█redispatch-stall-w1-replay-order█changelog-0330-heading█update-wedge-bounded-conn-write█inject-settle-rearm█###### release/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.30.6█v0.30.5█v0.30.4█v0.30.2█v0.30.1█0.29.1█###### build/█redispatch-stall-w1█mesh-recovery-w1█redispatch-truth-w1█project-index-w3-reader-cutover█project-index-w2-daemon-writer█project-index-w1-identity-store█###### triage/█update-wedge-rca-trace█musl-tier█###### test/█uwedge-r4-stale-guard-swap█###### release-merge/█uwedge-r4█###### feat/█musl-w3-release-artifact█musl-w1-platform-registry█###### spec/█brain-handoff-pilot█###### rca/█pump-w1-fastfail█##### mirror/█main█#### heads/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█pump-w2-design█pump-w1-fix█musl-w2█musl-w1█gate-inject-rearm█##### docs/█v0360-notes-plain-language█reorg-plans-process█##### fix/█update-wedge-bounded-conn-write█inject-settle-rearm█##### [...1170ch elided...] 1-fastfail█#### mirror/█main█### heads/█main█uwedge2-resume-viewer█update-wedge-drain-drive█f-035-window-drain-honor█pump-w2-design█pump-w1-fix█musl-w2█musl-w1█gate-inject-rearm█#### docs/█v0360-notes-plain-language█reorg-plans-process█#### fix/█update-wedge-bounded-conn-write█inject-settle-rearm█#### release/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.30.4█0.29.1█#### triage/█update-wedge-rca-trace█update-wedge-rca-capture█musl-tier█#### test/█uwedge-r4-stale-guard-swap█#### release-merge/█uwedge-r4█#### spec/█brain-handoff-pilot█#### rca/█pump-w1-fastfail█#### feat/█musl-w3-release-artifact█musl-w1-platform-registry█### tags/█v0.36.0█v0.35.0█v0.34.0█v0.33.0█v0.32.0█v0.31.0█v0.30.6█v0.30.5█v0.30.4█v0.30.3█v0.30.2█v0.30.1█v0.30.0█v0.29.1█[200 results limit reached. Use limit=400 for more]\u000f█</out>█# Assistant ¶█_**Planning detailed daemon inspection**_█# Tool call ¶█//Tracing retirement semantics█grep(pattern=\"retir|reconstruct|replay|redispatch|spawn|thread|worker|connection\", path=\"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/redispatch_stall.rs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/█## src/█### broker.rs█4|//! would lose continuity on if the logic restarted: the PTY master+writer and█*5|//! the spawned child (both via [`spt_term::PtySession`]), and the accepted█6|//! client / listening sockets. It runs **no logic** - no routing, registry,█*7|//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,█8|//! forward its output to the brain, inject the brain's input, report exit.█9|//!█10|//! ## Gapless handoff (B2 - the load-bearing invariant)█...█18|//! [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held█*19|//! in a bounded ring. The current subscriber (a brain connection) receives█20|//! chunks live; all subscriber writes happen *under the log lock*, so frames█21|//! reach the brain in strict `seq` order with no interleave.█22|//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its█*23|//! last-seen `seq + 1`; the broker replays the ring from there, then streams█24|//! live. The brain dedups by `seq`. Output produced *while no brain was█*25|//! attached* sits in the ring and is replayed on the next subscribe - so a█26|//! brain restart loses nothing. [...15272ch elided...] thread::spawn(move || {█89| let _ = serve.serve();█90| });█91| broker█...█98| }█*99| thread::sleep(Duration::from_millis(10));█100| }█101| panic!(\"brain could not connect\");█102|}█103|█*104|fn echo_spawn_req(endpoint: &str) -> SpawnReq {█105| #[cfg(unix)]█106| let (program, args) = (\"cat\".to_string(), Vec::<String>::new());█107| #[cfg(windows)]█108| let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);█*109| SpawnReq {█110| program,█111| args,█112| rows: 24,█...█122|█*123|/// One REAL dispatcher generation (the redispatch.rs harness): the█124|/// `dispatch_fixture` child running production `run_dispatch_loop`. Killed =█125|/// brain death. Stderr lands in `log`.█126|struct Generation {\u000f█</out>█# Tool call ¶█//Reading stream lifecycle ADR█read(path=\"docs/adr/0038-broker-stream-lifecycle-truth.md\", selector=null)█<out>█\u000e# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)█- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day); **amended 2026-07-16 (REDISPATCH-STALL - see Amendment below)**█- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█## Context█The broker's `NetShared.streams` table is append-only in practice: rows are█inserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF -█no removal or retirement path exists. Stream *classification* is derived at█redispatch time by peeking ring seq 0, but the `StreamLog` data ring is█bounded (4096 transport chunks) and evicts the protocol opener under█traffic. Dispatcher claims are a per-process one-shot `HashSet` insert█performed before worker spawn, never cleared or retried.█After any brain cycle (`spt daemon refresh`, `spt update` apply - same█path), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY█broker-held peer stream, including finished historical attach streams. A█replayed historical Attach silently steals the live controller█(same-identity `become_controller`, no `Displaced`) and its replayed EOF█[...5665ch elided...] ver█detach a replacement controller. This also covers the UNFINISHED-stale-row█steal shape (raw-close no-FIN viewports) that Decision 1's finished-row█retirement cannot see.█6. **(new - dispatcher hygiene + telemetry)** The redispatch worker pool is█bounded with batched cold enumeration and NO claim/sessions locks held█across I/O (clone-then-drop before `detach_if`/`info.json` writes, KH 7.12█kin); dispatch telemetry is keyed per█gen/stream/family/endpoint/attempt/conn with█replay/poison/cancel/worker-outcome events and gauges.█Rejected here: reverting retryable claims (restores permanent abandonment);█widening any write deadline (hides the composition instead of removing it);█tearing down all subscribers on any poison (punishes healthy streams for a█wedged conn).█\u000f█</out>█# Tool call ¶█//Mapping broker declarations█read(path=\"crates/spt-daemon/src/broker.rs\", selector=null)█<out>█\u000e//! The broker kernel (B1 + B2) - the stable half of the broker/brain split.█//!█//! Per ADR-0004 §B the broker holds **only** the resources a live consumer█//! would lose continuity on if the logic restarted: the PTY master+writer and█//! the spawned child (both via [`spt_term::PtySession`]), and the accepted█//! client / listening sockets. It runs **no logic** - no routing, registry,█//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,█//! forward its output to the brain, inject the brain's input, report exit.█//!█//! ## Gapless handoff (B2 - the load-bearing invariant)█//!█//! A brain may be killed and restarted at any moment (a routine self-update, a█//! crash). The broker - and every hosted child - must survive that untouched,█//! and the new brain must re-attach to the output stream **gaplessly and█//! exactly-once** (Spike #1 made real). Two mechanisms:█//!█//! - **Sequenced, buffered output.** Each session's output is appended to an█//! [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held█//! in a bounded ring. The current subscriber (a brain connection) receives█//! chunks live; all subscriber writes happen *under the log l [...14785ch elided...] .█const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);█/// The effective commit deadline. Production is the const above; the int gate█/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`█/// so the structural outcome (floor releases -> buffered input flushes -> binary█/// faults) is asserted in well under a second rather than a 5 s wall-clock wait -█/// the deadline's PRESENCE is the invariant, not its exact length.█fn inject_commit_deadline() -> Duration {█match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {█Ok(ms) => ms█.parse::<u64>()█.map(Duration::from_millis)█.unwrap_or(INJECT_COMMIT_DEADLINE),█Err(_) => INJECT_COMMIT_DEADLINE,█}█}█[Showing lines 1-300 of 6298. Use :301 to continue]\u000f█</out>█# Tool call ¶█//Mapping dispatch declarations█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=null)█<out>█\u000e//! Inbound net-stream dispatch (D9-1) - the daemon-side acceptor that turns█//! the hand-wired serve loops of D5-D8 into production behavior.█//!█//! Every E2E test so far drove the serve half manually (a probe brain calling█//! `serve_sync` / `serve_update` / `apply_notif_feed` / `receive_wan` against█//! a known stream). In production nothing did - a peer could dial and write█//! and the bytes sat in the broker's stream log unread. This module is the█//! missing consumer: a daemon-hosted loop that watches the broker's stream█//! table for **peer-initiated** streams, classifies each by its first NDJSON█//! record, and hands it to the right serve loop on its own worker thread.█//!█//! ## Direction (the gotcha-#9 fact, now first-class)█//!█//! The stream table holds locally-opened outbound streams too. The dispatcher█//! claims only rows with `initiated_locally == false` - serving our own█//! requester stream would deadlock the requester against ourselves. The flag█//! is broker-recorded at registration (`open_stream` vs the accept loop),█//! never inferred.█//!█//! ## Classification (first-record demux)█//!█//! Streams are single-purpose; the opener's first record names the protoc [...11307ch elided...] leteness is a unit fact.█fn dispatch_event(█gen: u64,█stream_id: u64,█family: Option<StreamFamily>,█attempt: u32,█conn_id: u64,█event: &str,█detail: &str,█) -> String {█format!(█\"DISPATCH_EV: gen={gen} stream={stream_id} family={} attem","textTail":"\u000eARDS interleaved events█51|//! (gotcha #1). Stream opens for push feeds carry no op either: the feeds█52|//! are idempotent by construction. The sync pull's `open_op` IS durable█...█366|/// **KH 7.4 - never an LLM-bearing worker.** A pump worker is cadenced,█*367|/// fire-and-forget peer IO. LLM-bearing work (the bounded Psyche reconcile█368|/// turn) runs at activation paths with a manifest runtime to ride (ADR-0013),█369|/// NEVER on the pump thread - the seam must not become the invitation to put█370|/// it here.█...█492|impl PeerIo<'_> {█*493| /// Open a fresh stream and write one whole feed fire-and-forget (the█*494| /// push-feed discipline - `op_id: None`, finished on send).█*495| fn push_feed(&mut self, feed: &[u8]) -> io::Result<()> {█*496| push_feed(self.brain, self.conn_id, feed)█497| }█498|█499| /// Mint the next durable open-op from the pump's sole counter (exactly-once█...█1115|█*1116|/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget, [...2145ch elided...] broker.test_session_viewer_count(sid);█346| assert!(█### pump.rs█672|/// Ok on stop - a dead PEER never bubbles Err, so it NEVER triggers a supervised█*673|/// restart. (`cadence.sync/update` are parked long so only the fire-and-forget█674|/// registry/notif push legs fire - the live peer stays connected + advertised█675|/// without a reply-read, keeping the round fast and the connect signal stable.)█676|#[test]█### redispatch_stall.rs█478| f.net_stream_subscribe(sid_a, tail).expect(\"recovery subscribe\");█*479| // Give the fire-and-forget subscribe a beat to process, then█480| // probe the lease with a real send on a separate conn.█481| thread::sleep(Duration::from_millis(300));█482| let mut s = connect_retry(&name_a);\u000f█</out>█# Tool call ¶█//Finding stream removal semantics█grep(pattern=\"streams\\\\.remove|retain\\\\(\\\\|_, e\\\\| e\\\\.conn_id|retire_stream|retired\\\\.swap|finished.*remove\", path=\"crates/spt-daemon/src/nethost.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 966| .unwrap()█*967| .retain(|_, e| e.conn_id != id);█968| sh_close█969| .presence█970| .lock()█...█1835| // [impl->REQ-REDISPATCH-FINISHED-RETIRE]█*1836| pub fn retire_stream(&self, stream_id: u64) -> bool {█1837| let Some(entry) = self█1838| .shared█1839| .streams█...█2413|█*2414| assert!(host.retire_stream(shell), \"first retire hides the row\");█2415| let ids: Vec<u64> = host.stream_infos().iter().map(|s| s.stream_id).collect();█2416| assert!(!ids.contains(&shell), \"retired row left the enumeration\");█2417| assert!(ids.contains(&owner), \"the active sibling row stays\");█*2418| assert!(!host.retire_stream(shell), \"second retire is a no-op\");█2419| assert!(█2420| host.stream_opener(shell).is_none(),█2421| \"a retired stream answers no-such-stream\"█...█2447| // Retire FIRST - the ordering the race loses in the field.█*2448| assert!(host.retire_stream(shell), \"retire the serving row\");█2449|█2450| // The worker's reply lands AFTER retirement and must still deliver.█2451| host.send_stream(shell, b\"{\\\"kind\\\":\\\"reply\\\",\\\"outcome\\\":\\\"edge\\\"}\\n\", true)\u000f█</out>█# Assistant ¶█_**Prioritizing communication to Main**_█# Tool call ¶█//Sending Registry root cause█irc(op=\"send\", to=\"Main\", message=\"Registry-specific RCA: periodic pump opens+FINs a fresh bidi stream every 30s/peer/subnet (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`) but sender local StreamEntry is never retired; receiver `serve_registry_feed` returns on EOF without FINning reverse half (`dispatch.rs:984-1046`). `retire [...674ch elided...] spatcher, N registry-only rounds, assert eligible enumeration/physical stream count+broker conn_handler_count plateau and post-refresh stream-sub-attach Registry count is O(active), no poison/replay.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- Main: injected\u000f█</out>█# Assistant ¶█_**Investigating thread cleanup and stream retirement**█**Identifying overflow blocking worker reads**█**Tracing worker thread retention causes**█**Tracing replay loop cause**█**Analyzing feed size and decoding impact**█**Estimating historical row volume**█**Inspecting write_snapshots atomicity**_█# Tool call ¶█//Locating snapshot writes█grep(pattern=\"fn write_snapshots|write_snapshots\\\\(|snapshot\", path=\"crates/spt-daemon/src/registryhost.rs\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e 23|//! consumers (`spt send`'s WAN resolution, D9-1-4) the host mirrors each█*24|//! subnet's registry to an atomic JSON snapshot under█25|//! `identity/registry/` - readers tolerate staleness (the 4.3█26|//! stale-clean posture), never block on the daemon.█27|// [impl->REQ-INST-7]█...█111| /// a probe; only stale rows get dialed. Local-only (never on the wire);█*112| /// mirrored to `heard.meta` beside the snapshots for the CLI reader.█113| heard: Mutex<BTreeMap<String, u64>>,█114| /// Wall-clock ms of the LAST admitted inbound registry/label feed from█115| /// ANY origin - the \"last admitted registry update\" health field█...█122| epoch: Mutex<EpochSource>,█*123| /// Where snapshots land (`identity/registry/<subnet>.json`).█*124| snapshot_dir: PathBuf,█125|}█126|█127|impl RegistryHost {█128| /// Stand the host up for this node, with the canonical epoch counter and█*129| /// snapshot dir under the spt home. Reads the OS hostname here - daemon█130| /// startup IS the label's re-check point (REQ-SUBNET-3).█131| pub fn new(node_hex: &str) -> RegistryHost {█132| RegistryHost::new_at(█...█141| /// Explicit-paths constructor [...9783ch elided...] let p = policy(\"home\", \"bb22\");█...█2118|█*2119| // Past the grace the aged Offline ghost evicts and the snapshot is rewritten.█2120| let later = 1_000 + 300_000 + 1;█2121| assert_eq!(h.evict_aged_offline_rows_at(grace, later), 1);█2122| assert!(h.rows(\"home\", \"ghost\").is_empty(), \"aged Offline ghost evicted\");█*2123| let snap = RegistryHost::snapshot_path(&dir.path().join(\"registry\"), \"home\");█2124| let parsed: SubnetRegistry =█2125| serde_json::from_slice(&std::fs::read(&snap).unwrap()).unwrap();█2126| assert!(█2127| parsed.instances(\"ghost\").is_empty(),█*2128| \"snapshot rewritten without the aged Offline ghost\"█2129| );█2130|█2131| // Idempotent: a second sweep finds nothing.\u000f█</out>█# Tool call ¶█//Reading snapshot write implementation█read(path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"630-730\")█<out>█\u000e pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█...█if let Some(label) = self.node_label.clone() {█...█}█drop(epoch);█drop(regs);█self.write_snapshots();█out█}█/// Evict every registry row authored by a **silent** peer node█/// (KNOWN-HAZARDS 4.10): a node not heard - no admitted inbound feed -█/// within `ttl` has its rows removed from every subnet registry and the█/// snapshots rewritten, so a dead node identity can no longer poison█/// bare-id resolution with phantom ambiguity. Own rows never decay (this█/// node authors them fresh each pump tick and is never in its own heard█/// map). A row whose author has NO heard stamp is treated as silent -█/// rows only enter via admitted feeds (which stamp) or `advertise_local`█/// (own rows, excluded), so an unstamped foreign row is already an█/// anomaly and fails toward eviction. A merely-offline peer reconverges█/// within one pump cadence of its return (re-advertisement from its█/// durable epoch). Returns the number of rows evicted.█// [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]█pub fn evict_silent_peers( [...2775ch elided...]█}█evicted += n;█}█drop(regs);█if evicted > 0 {█self.write_snapshots();█}█evicted█}█/// Every instance row for `endpoint_id` in `subnet` (the reconcile█/// election's input; empty when unknown).█pub fn rows(&self, subnet: &str, endpoint_id: &str) -> Vec<Instance> {█self.regs█.lock()█.unwrap()█.get(subnet)█.map(|r| r.instances(endpoint_id).to_vec())█.unwrap_or_default()█}█/// A point-in-time clone of one subnet's registry (resolution input).█pub fn snapshot(&self, subnet: &str) -> Option<SubnetRegistry> {█self.regs.lock().unwrap().get(subnet).cloned()█}█[Showing lines 629-733 of 2238. Use :734 to continue]\u000f█</out>█# Tool call ¶█//Finding advertised row payload fields█grep(pattern=\"fn advertise_local|read_to_string|read\\\\(|load|for .*read_dir|instances|resources\", path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"360-640\", case=false, gitignore=true, skip=null)█<out>█\u000e 372| // [impl->REQ-INST-7]█*373| pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█*374| let subnets = SubnetStore::load();█*375| let mut vis = VisibilityStore::load();█376| // First-join home adoption (D9-5, REQ-INST-15): within one█377| // advertisement cadence of the node's first join (a pair lands the█378| // subnet record; no pairing-protocol threading needed), unhomed Self█...█388| // agent refines - CONTEXT §resource advertisement).█*389| let node_blurb = crate::config::DaemonConfig::load().resources_blurb;█390| // The node's subnet-broadcast shell adapters (M5-D8c, CONTEXT█391| // §Discovery scope): registered kind=\"shell\" adapters that chose█392| // broadcast=subnet ride every row this node authors - the other-node█...█431| let info = spt_store::info::read_info(&perch_path);█*432| let resources = info█433| .as_ref()█*434| .and_then(|i| i.resources.clone())█435| .or_else(|| node_blurb.clone());█436| // The presence datum (M5-D6a, REQ-PRES-1): the perch's heartbeat█437| // sta [...463ch elided...] riven_by=None so that WHO datum undercounts it).█...█498| epoch: ep,█*499| resources: resources.clone(),█500| last_active_ms,█501| shell_adapters: shell_adapters.clone(),█502| // The node label rides every row this node authors█...█568| .filter(|id| {█*569| reg.instances(id)█570| .iter()█571| .any(|i| i.node == self.node_hex && i.status.routable())█572| })█...█580| epoch: ep,█*581| resources: None,█582| last_active_ms: None,█583| shell_adapters: Vec::new(),█584| node_label: self.node_label.clone(),\u000f█</out>█# Tool call ¶█//Reading advertise scan█read(path=\"crates/spt-daemon/src/registryhost.rs\", selector=\"360-640\")█<out>█\u000e pub fn last_admit_ms(&self) -> Option<u64> {█match self█...█.load(std::sync::atomic::Ordering::Relaxed)█{█0 => None,█ms => Some(ms),█}█}█/// Scan the owlery and (re-)advertise every self perch into every member█/// subnet it is visible in, each row stamped with a fresh epoch. Returns█/// the updates to push to peers (the node's own rows only - peers' rows█/// reconverge from their own re-advertisement, no transitive gossip in█/// v1: trust is pairwise per subnet, a relayed third-party row would█/// carry an origin the receiver can't gate honestly).█// [impl->REQ-INST-7]█pub fn advertise_local(&self, owlery: &Path) -> Vec<RegistryFeedRecord> {█let subnets = SubnetStore::load();█let mut vis = VisibilityStore::load();█// First-join home adoption (D9-5, REQ-INST-15): within one█// advertisement cadence of the node's first join (a pair lands the█// subnet record; no pairing-protocol threading needed), unhomed Self█// perches adopt the sole subnet + seed their sync scope. Strictly█// adopt-if-unset; a multi-subnet store adopts nothing. [...14024ch elided...] : ep,█}));█}█}█drop(epoch);█drop(regs);█self.write_snapshots();█out█}█/// Evict every registry row authored by a **silent** peer node█/// (KNOWN-HAZARDS 4.10): a node not heard - no admitted inbound feed -█/// within `ttl` has its rows removed from every subnet registry and the█/// snapshots rewritten, so a dead node identity can no longer poison█/// bare-id resolution with phantom ambiguity. Own rows never decay (this█/// node authors them fresh each pump tick and is never in its own heard█/// map). A row whose author has NO heard stamp is treated as silent -█/// rows only enter via admitted feeds (which stamp) or `advertise_local`█[Showing lines 359-643 of 2238. Use :644 to continue]\u000f█</out>█# Tool call ¶█//Finding pump loop sleep cadence█grep(pattern=\"thread::sleep|PUMP_POLL|DEFAULT.*POLL|Duration::from_millis\", path=\"crates/spt-daemon/src/pump/mod.rs\", selector=\"500-700\", case=true, gitignore=true, skip=null)█<out>█\u000e 611| if !any_due {█*612| thread::sleep(TICK);█613| continue;█614| }█615|\u000f█</out>█# Tool call ¶█//Reading pump loop cadence█read(path=\"crates/spt-daemon/src/pump/mod.rs\", selector=\"500-680\")█<out>█\u000eimpl PeerIo<'_> {█...█/// Mint the next durable open-op from the pump's sole counter (exactly-once█/// open identity - a restart can never reuse one), tagged with the `pump`█/// journal minter (ADR-0034): the pump is the sole writer of `pump-ops.json`,█/// so its ops namespace under `pump` and never collide with cli/rc/shell ops.█fn open_op(&mut self) -> io::Result<MintedOp> {█Ok(MintedOp::new(Minter::Pump, self.ops.next_epoch()?))█}█}█/// Run the pump until `stop` is set. Owns one brain IPC connection and a█/// per-peer connection cache (an IO failure against a peer drops its cache█/// row; the next tick redials - conns are rebuilt freely, nothing leaks█/// because the broker's closed-watcher reaps dead conns).█///█/// The pure scheduler + fan-out (pump-seam P4, all four legs behind the seam):█/// heartbeat -> poll every worker's wake -> the [`due`] kernel -> each due█/// worker's `pre_round` (once, before any peer_step) -> per-subnet × per-peer█/// (`ensure_conn` -> each due worker's `peer_step` in fan-out order,█/// abort-on-first-fail) -> mark-after-round with a POST-round instant. No leg█/// logic lives here - the shell owns only *when* and *toward who [...8641ch elided...] nsert(peer_hex.clone(), next_peer_backoff(prev, round_start));█}█}█} else {█// No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage█// `address-resolution`). Transition-logged: the first█// unroutable round per peer is loud, repeats are quiet -█// the peer retries on its own cadence like before.█if health.note_failed(█peer_hex,█crate::seedproofx::STAGE_RESOLVE,█conns.len(),█now_ms(),█) {█...█}█}█}█}█...█}█...█}█[Showing lines 499-683 of 1982. Use :684 to continue]\u000f█</out>"}}}
{"type":"message","id":"6dc09afb","parentId":"2a737e5d","timestamp":"2026-07-17T07:04:04.917Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing broker IPC bandwidth usage**\n\n**Identifying persistent retry loop cause**\n\n**Tracing subscriber replay mechanics**"},{"type":"thinking","thinking":"**Analyzing run loop net_streams behavior**"},{"type":"toolCall","id":"call_h5D2N9H0klSEIWhhfQYRqLNj|fc_01546d621137b6cd016a59d3e42bc481959e26f1ebcb737173","name":"read","arguments":{"i":"Reading enumeration hot loop","path":"crates/spt-daemon/src/dispatch.rs","selector":"430-560"}},{"type":"toolCall","id":"call_OZAUDA5SWpr2Wlm9ccPLlBNn|fc_01546d621137b6cd016a59d3e42bd881959249e7921aae0708","name":"read","arguments":{"i":"Reading claim state core","path":"crates/spt-daemon/src/dispatch.rs","selector":"230-365"}},{"type":"toolCall","id":"call_tKPTnBs7B3zfz8SQVLCLhq9j|fc_01546d621137b6cd016a59d3e42be08195bb3e5e05afce14fa","name":"read","arguments":{"i":"Reading stream info serialization","path":"crates/spt-daemon/src/nethost.rs","selector":"1780-1855"}},{"type":"toolCall","id":"call_tpJKXBlKqEizQJEYAyoCLnFn|fc_01546d621137b6cd016a59d3e42be4819599087f287048cb64","name":"read","arguments":{"i":"Reading broker command loop","path":"crates/spt-daemon/src/broker.rs","selector":"3020-3300"}},{"type":"toolCall","id":"call_bDOxZQBxJVkNM8Pv1Qn84LlE|fc_01546d621137b6cd016a59d3e42bec8195ad5681629e25d6ba","name":"read","arguments":{"i":"Reading subscriber attach core","path":"crates/spt-daemon/src/nethost.rs","selector":"430-610"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":56638,"output":785,"cacheRead":0,"cacheWrite":0,"totalTokens":57423,"reasoningTokens":582,"cost":{"input":0.28319,"output":0.02355,"cacheRead":0,"cacheWrite":0,"total":0.30674}},"stopReason":"toolUse","timestamp":1784271815728,"responseId":"resp_01546d621137b6cd016a59d3c8c4048195b6454760ab4f6d3e","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_01546d621137b6cd016a59d3cfc4e081958658b9f7f8a9152e","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdPfBLeLc-gLm059N8B-wM7h0GJNkv2hyU8ZwYk9EbocreBFiahOhemttMHPQb1lgK7D_RHxt8NLqWJsDzE-MAhBeiuaD-b9fsDsG5035l95BRfjDMJMHdKcozYmgXO1-1u1LiNpaIyK4Ak5yl0lMSlhxkS-8jgv-KFasChAawUGSnsKP6FY32XAGyaC2FouaXv70RogEeuT1s4OISQG3KJWtZ2RdOLEgiW51eGHTU9EVfmrvuPfZ22oFuBtNYaNozFQGUCBjCli7rY9erkfbmui6zdNDX4YDkEBIfRzleL1JWb4jF6J8o_NGaRZK1NI3qpYRUhf3M_eBNLengjFtb_BUDPkaWv9g4oEAP92F2fTAGePbsjSRUKSo7GQpSmhqIvzmYc-EB14EvMfIE8WDzhb8MSR3JSmr1AU9A3KP3QEGw7pYKgeJLmCmpnomhHs_9wg8JEX70kKOO9_mpAneSztcyql3quYOuD9Qp39pizdxebAnz9rn62WD9P7KW3w_4sEWGkqToJTU0kr7q5rvKhmLiYuTAUxIV7RR92K0cFPtxVr_Dc0RHiEKGH26dXLvbe-3BwMnwGBwaX6WlrHrXmks3d-4jUByZLJZqy-UMNKx7_B5TRqudVjuV31mVLSazfsq0xI-SWifbVzahD3KFnDNtZ1WVI6nyLqqBNOIcOV4wPSccIX8sjtXsln3zBXpgg4wKys9t75UHB3RNeUcbwueUNQtGM8iOoodH7zNuZyl0AokYgOB181hS7652U1GiHffm_uXkZiYPPM9Cqm6Lt1RmvcEceC4TG_WLkjClt1hthEV-1CRPTnux8_qUi8TKJNixIEf_s04fS2hqwW1dEBoAbnqMOmxGZsID6PJh7LbOsmOglWl50e-fgqScuGwwH_YWTJctuFHul6EQmPq6Uq_mwupD9xinUZvcUVNo0Ho0Z6mYCxc8HU-FIqSt0IcJpm7RcfAP2T_xS1EDk_2z2UjzJEvXEk3VX1wy4CWvLDOQZyWmHfNzJpnfHRxiEPz1oP03yIxdnRchYqKFNuSQMy0N-NuysjKXIMTJK-eigpRnhjrhHy7aYxy9e6ptZvWnRV6hhTSfrtQku3zBn_Xwt926accrzHQYPi5iXI1ooOarBnMcWmiC6LuzdeZmjJK7gERHJ9pRX29SM_f0QSdk92Jc9e74If18IDI97a-Zh1_RWygzKWfRA6KpjW5i7uMV5iMrwvQ0X98cYiajBYLI8leQZYXEbOehdUa5x7bhywdwpn3UnBtAl8oMS_3jmsfq4jXuG6chF5rooI0n987_TCc_nC7HPebaLbr9u2f1HlMMUBnnT1W7pHdlMX4p6xU2YAZOVjFJzCPwUo36qZxrnZw1IVW7ek6x3QjeDKOGEFdDy_HHTVLjniJl9xf6SsSJ3VbgfXiRE17ymLHMRyD9Cao-gfIyiaw1UPyjQNe8wiWRL1qXs8a9wqFZIC1TH5A157e40VgzZSHqUeH8cH-EX8PvmI4aop7zjgG4lGZJliTZCR3-e7_P57N_nUFvsTjT_X-dJteYiQTrCXcq8lQ4ryFXMfKf8h3Cnw4x_efzsujoaeNl-7x_L7Edy0ZxpHd0Q7hr0Kn7wCk8Dv1udDl1bjrMByl7xCZovdYLOsYfrokV_yHhnG2OKGgUaW0Wc9E2fbm82Kmo74v7-NJZSHwFp3WohUKCfKu68py40yc7uuLhuFZSc6khKDvFnTf6w0QeFTiyiWI52bIaUzUlFLFhJuySBDqqqM-yBsV6G_oLY68lSFS5Ql5KE_sxfn2TclXbjYXYGsPp-AH-_8OOqX5mru8-F1q3qiZDZtYYZGM2fUm8yHLSluKdbrL3_XaupzswlEYYW8Zb78Hg1KZBoz8fpgUTgYzUpb294WZ7huEnWWxJvZck8Rl5re2rxD_rH3BJAGBm2ZcuT15z9LkxfEt9tKwvd5iUvVF0Nk9UGmN19JEzkNgwvNUum7RFji4Gog7dB3jC2LvHDSmCdZL1XRE-3iXtHoWcK4U36t5F396wpg0u7hyK2jv5jHtkGho0fJQB6m_EekoTUw6NCLzxa_iYr-phrfoFQX7HffLuN2AfD7WvPdBI6HlRTQCx-ae6wnQh20xo9cfxgawwLjdNkph-7VCdJR7uZD3DZmeCmOZBiTQYwKJkJkpCJUo9LWh6kHKe9-DfBWWui20XqMc9R7kRomuuB4ZfWq59pBGePlr1CLNqA-XN5rR89dt-XkZczm3_Xj5vIiO7B36pm6ZDEfbAW972ZMnezkAtGgWtvg9lZI0P7SPY5nTSKzdNzhhmOjB_swf2fYNVsk2j8mtMkNc77EhBW29ArdBU1jGVnIuzo9LTluyGSnm0kLdNhr5Qub61lRb-43SrJ0pgukQGQ2cP0_JKp2Lk5r7q9auje2FmusDqTseIBYUEIL88yvxN23u_m6_VZHd26oHXom1GzXXnRvh5ni1DVXSm8azOZHYnXjRxA_4P8i0gUwEmudeUGZCuK_BrcCibqcB3KLhOGdkIIAtyaitaXyDsLfLs-m07GtWrjnq7xWroxXaUfi-73ZL2k_CvL8iLhVwhvwCSHvfRSqdwYPcv31t1VWH-3FA6BJaKAVrO2LPoeZU7yyorQId3hz-n0t5gD8DDu_rvHlx3NApJo7QMWtk3sJ7BZQg3KiVwfBnmk5VlAqIXFVWN2Kj7YvuaDQjLgThgJnlpgd0YdL26zMxJ0pPzuAwMtCmA-3jR1MmkbGoQFvg-OzSS3nlyaJKQ9Nbtm0nuhwY13a_xLl7jsN4T8vhgd3urMpziCtztA59E0HSPQlUJw0tRpWkwKcI39dBbM45NtttKAZajcm0R1Pzi9c2wfvvP2r7gIRHwzAsnbVje7iKegVntPczTOkDKO_n5ZJpe2iQxm4P8erl03QipCNVca8DGYQCo4Y-Fi2tZIId_AQ-nCEBsxz8KeKAkuE8RLhi1JDLKCidjp7RULYwC2cgThtzFgvdZPB3hk-dF1D--74S8SMjSjMvVwx2g4mFvvYPAwwLze6Pn5KgeNvGwWTN9SFUXVVMP1OVlB_jUhahukRlQKr51VRWGML7f4lZqNvwqIAjmNAdz599RSot4hwkHLSiB3XDc4uzYC4Ug_EILkBj_bOwVbSgLsQcwpf-YVdwM-C3h_xEfPeVyZw9G0bleTX2EN_3ESVYh1CxW8av6pEHxziJGYC9Xi5WfXB0TXqONCM09-ATefcZryJlUZ8M_xzcUUa3MRe3WcUqzg_iGByrCMWwV_Mj2GGRvGCM3pBLJAZ8t6qHoReR4E8eG0lgY5Mpw4FMxhSd01G1qVeZYE6HXLySCgBMdzLafXQhY8lroa-PKtvmX3kbouEp1uRwjCC4MkWp_7eDFY5HI38kLXVa8pymXbSulhd8y0is2-o2rDkNxtdGFj-AAmJQYdJYy5UDMW9AURxizDgnK1MuOsYDlbQb2yMltYTIaNSP3zZA2AcQG_Nq2H1AOQAKb8smiAVMCadXq2z5qsL46wMqLRptLTqPWOg3JhhX_5Hi6jpHC4yVL909ybuT3ysr7fu3a2BKs0rlcE9dZjXzZXXu8u7rzxjefw1qIRoIeImaJzlKQ9IEkFpUo-kYd6A54iDAEhUOR-3Cgmuq_waHn88wT2wp9uUXSu8D18-siZOD4FU3AWkTwXtMRYbF5u-XwXXlGyh1d41vRQfUrs4rkqH1Ab13dja8l-cWzxK-1AeS81s_xhgFG2-671e7KW8J2Zxg3VVQ0J5XAjzHNZVLxLeAVlNUR2_M3pw1Z7FbY0HE8L0nR1tVGHn9UzzF6WDiVQkYubm8EsyZFcWospPbE624pUv3DLvn81pjEI-J5JKXygOt0MdZr8dAwUl7uyU-teTixSR9EXv6HEX7uU6oytQ-aMMy_qKKaVSmWeHQ5HkrlHWCqkLKO7-oOZOPvbR0q3k_tLNEyHLtQ1upjuGvqkqkfjaByHm0HpH2WO12CTrxsabx9kgb6BQKwgEfwfb2iAKcJKccGJqIL3EP_rm8vTjtQUdzf9qG0sCqdrwFnkmVAbCiS7cMWbf_6Dsq1hC7N5wOubM8msaOgXt2fIbKtf5A94eZMbAwiZuNDsa_2oDE0x1sS6fR80WoTYes-2TZSuEDWYLN06TK3GvTFM=","summary":[{"type":"summary_text","text":"**Analyzing broker IPC bandwidth usage**"},{"type":"summary_text","text":"**Identifying persistent retry loop cause**"},{"type":"summary_text","text":"**Tracing subscriber replay mechanics**"}]},{"id":"rs_01546d621137b6cd016a59d3df8a708195b3611f97c1bfc52b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdPkM0_zGfoMT-fsXoyWZ_fmpHax5xxMZd0zsgplJqf-Zv1RYFEFMZ_APiJ2xMXMC8qKonFuY5kSqH5fdFlmkITMfNXSHYgetSQ9vRGOPY_qxPkOrfEjXjy5DBy6tYvk5-5UFdDQhKo0tJ0ZJOj998DFP7RT2ydv0QKtaLUjrFN57VvruYJUZmhXpveILERedGKdYQKmcO6AVjksYwn7gnLS4TrBR0jxvaW9oovXNsykbiHkXL6BJH-BGyUH3Jge-AXgj-MRlKjk-Z4AcLzqk2GmWLAKyEV_KeDsCJ4Fi7BOEXZwAOOj4pt-RI4rHtu2DOuBuo2WDqR8O31HPpaUpQvbb_HBO-p-A6MdHN9SWn6OhSDS0_CYxQrD3JG4BD4QUYG5ebEBj5wpigmcv7IzVTNl4VppmPQl1YV_LiBWsgHvOk8jodTw944XNlMt2byRZbc2sgpd1r3jjTH52eFxPjRfEZaJJmhPdpJQeDuIsiEfqJzr9qKvPt6KDD6Z8NJoW5nl9e7J-BnEotMTU2_pZl9tJfAJTUVkSDCiDTSQq-2TP0VuVr-ng9Islbg95-8liBLXJxhGK7qRtr8F6CNYlETupuTXABu7Fma5FJrD_vS2HB-xnnzrjl2NhW07S5wcfhfuZ8KCOcmkk3VgRUVWe0enbsI9LM97fmP-Xa5d2SBd_RgA9xHJgZKkvWOmHMgi5Ar-JV9-1IOPrvZqGBqVj8ngyt-OWk7Tk6pNo7fTX9fWWwRAKf-2fUKhW5128MN8GxcE1FEjbta2ngEUjq5AJgeZcs2Q0fFB-iCp1cWIcGF-xSakJbKlCMSayp1uGaVoZTMh3BobP-KcyMD00TCv8Cs6cIB2Bcoq8EjPj22-iWjioedgH91cufI3aK4_M4gfhanPIaKpnuz20ypnqYkcDbSiUzBSYAhO2LjAHBdLI4gOCT4Je7bJkRVbITCuELaMcaEj95zr6QWCfetZVxvBx1w7s_ErJLWqAYxWTqgDiWqNUP2xwrtBzzUs2lWyQfOmsSRHw4pO1DLWWEhicz_ZxY7-64OJXw3j68nC0MxseZZFGxJjwnpyhWjH-KpF6rtL_23v_oX_Ff0VbTw1Vk5rZH7iKP0gRuodTrUcGdZJcVBrPcl3r33e_StH_A1rK7PB0dWW7xFbl1WGlvs2sOMYPgD3ckvIE8gyLKowCb_2gz6ktVXf0HJlNGZYaOp-CLg_dowDsg7FE9ZstAv1qUPXLguff_ZDWbb3F_KPW_jiShzITsSTbmPdvEHglrUk8ExXOx05r4D6DbWR7co9o1x0_1MVxXlRIMZfV4uM4CRH_400mfE=","summary":[{"type":"summary_text","text":"**Analyzing run loop net_streams behavior**"}]},{"id":"fc_01546d621137b6cd016a59d3e42bc481959e26f1ebcb737173","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading enumeration hot loop\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"430-560\"}","call_id":"call_h5D2N9H0klSEIWhhfQYRqLNj","name":"read"},{"id":"fc_01546d621137b6cd016a59d3e42bd881959249e7921aae0708","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading claim state core\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"230-365\"}","call_id":"call_OZAUDA5SWpr2Wlm9ccPLlBNn","name":"read"},{"id":"fc_01546d621137b6cd016a59d3e42be08195bb3e5e05afce14fa","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream info serialization\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1780-1855\"}","call_id":"call_tKPTnBs7B3zfz8SQVLCLhq9j","name":"read"},{"id":"fc_01546d621137b6cd016a59d3e42be4819599087f287048cb64","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading broker command loop\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"3020-3300\"}","call_id":"call_tpJKXBlKqEizQJEYAyoCLnFn","name":"read"},{"id":"fc_01546d621137b6cd016a59d3e42bec8195ad5681629e25d6ba","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading subscriber attach core\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"430-610\"}","call_id":"call_bDOxZQBxJVkNM8Pv1Qn84LlE","name":"read"}]},"duration":29188.20149999857,"ttft":8050.159700006247,"contextSnapshot":{"promptTokens":56638,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_h5D2N9H0klSEIWhhfQYRqLNj|fc_01546d621137b6cd016a59d3e42bc481959e26f1ebcb737173","toolName":"read","startedAt":"2026-07-17T07:04:04.917Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading enumeration hot loop"},"id":"1192b08a","parentId":"6dc09afb","timestamp":"2026-07-17T07:04:04.917Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_OZAUDA5SWpr2Wlm9ccPLlBNn|fc_01546d621137b6cd016a59d3e42bd881959249e7921aae0708","toolName":"read","startedAt":"2026-07-17T07:04:04.917Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading claim state core"},"id":"21ea5122","parentId":"1192b08a","timestamp":"2026-07-17T07:04:04.917Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tKPTnBs7B3zfz8SQVLCLhq9j|fc_01546d621137b6cd016a59d3e42be08195bb3e5e05afce14fa","toolName":"read","startedAt":"2026-07-17T07:04:04.917Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream info serialization"},"id":"2bf5ed5f","parentId":"21ea5122","timestamp":"2026-07-17T07:04:04.917Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tpJKXBlKqEizQJEYAyoCLnFn|fc_01546d621137b6cd016a59d3e42be4819599087f287048cb64","toolName":"read","startedAt":"2026-07-17T07:04:04.917Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading broker command loop"},"id":"3c8d3de7","parentId":"2bf5ed5f","timestamp":"2026-07-17T07:04:04.917Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bDOxZQBxJVkNM8Pv1Qn84LlE|fc_01546d621137b6cd016a59d3e42bec8195ad5681629e25d6ba","toolName":"read","startedAt":"2026-07-17T07:04:04.917Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading subscriber attach core"},"id":"48e60db8","parentId":"3c8d3de7","timestamp":"2026-07-17T07:04:04.917Z"}
{"type":"message","id":"0666090f","parentId":"48e60db8","timestamp":"2026-07-17T07:04:04.949Z","message":{"role":"toolResult","toolCallId":"call_OZAUDA5SWpr2Wlm9ccPLlBNn|fc_01546d621137b6cd016a59d3e42bd881959249e7921aae0708","toolName":"read","content":[{"type":"text","text":"/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}\n\n[Showing lines 229-368 of 1558. Use :369 to continue]"}],"details":{"truncation":{"content":"/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach","truncated":true,"truncatedBy":"lines","totalLines":1330,"totalBytes":6041,"outputLines":140,"outputBytes":6041,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n/// transient — Failed → bounded requeue, never a second replay subscriber.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn is_unsupported_verb(e: &io::Error) -> bool {\n    e.to_string().contains(\"unknown command kind\")\n}\n\n/// The dispatcher's GLOBAL circuit breaker (ADR-0038 Amendment fix 5): a\n/// seat-blocked failure trips it — claiming pauses dispatcher-wide for a\n/// doubling window (base 2s, cap 30s), so retry machinery cannot hammer a\n/// wedged conn with fresh replay subscribers. Any successful serve resets it.\n/// PURE (instant-in, decision-out) — unit-tested trip/gate/reset.\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n#[derive(Debug, Default)]\nstruct CircuitBreaker {\n    until: Option<Instant>,\n    consecutive: u32,\n}\n\nconst BREAKER_BASE: Duration = Duration::from_secs(2);\nconst BREAKER_CAP: Duration = Duration::from_secs(30);\n\nimpl CircuitBreaker {\n    /// A seat-blocked failure landed: open (or extend) the window.\n    fn trip(&mut self, now: Instant) -> Duration {\n        let window = (BREAKER_BASE * 2u32.saturating_pow(self.consecutive)).min(BREAKER_CAP);\n        self.consecutive = self.consecutive.saturating_add(1);\n        self.until = Some(now + window);\n        window\n    }\n\n    /// Whether claiming is currently paused.\n    fn open(&self, now: Instant) -> bool {\n        self.until.is_some_and(|t| now < t)\n    }\n\n    /// Real progress (a Served outcome): close and reset.\n    fn reset(&mut self) {\n        self.until = None;\n        self.consecutive = 0;\n    }\n}\n\n/// Keyed dispatch telemetry record (ADR-0038 Amendment fix 9,\n/// REQ-DISPATCH-HYGIENE-TELEMETRY): every event names generation, stream,\n/// family (once classified), attempt, and conn — the field-discriminator\n/// surface (poison-window census, per-generation attach counts). PURE\n/// formatting so key completeness is a unit fact.\nfn dispatch_event(\n    gen: u64,\n    stream_id: u64,\n    family: Option<StreamFamily>,\n    attempt: u32,\n    conn_id: u64,\n    event: &str,\n    detail: &str,\n) -> String {\n    format!(\n        \"DISPATCH_EV: gen={gen} stream={stream_id} family={} attempt={attempt} conn={conn_id} event={event} detail={detail}\",\n        family.map_or_else(|| \"unclassified\".to_string(), |f| format!(\"{f:?}\")),\n    )\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n/// A `SeatBlocked` failure requeues WITHOUT burning the bounded budget —\n/// its pacing comes from the global breaker window (`breaker_window`), and\n/// the stream recovers once the wedged seat clears (T5's not-abandonment\n/// discriminator). A `Transient` keeps the ×3 budget (D3 unchanged).\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn outcome_transition(\n    outcome: &DispatchOutcome,\n    attempts: u32,\n    now: Instant,\n    breaker_window: Duration,\n) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(msg) => match classify_failure(msg) {\n            FailClass::SeatBlocked => ClaimState::Retry {\n                attempts,\n                next_at: now + breaker_window,\n            },\n            FailClass::Transient => {\n                let attempts = attempts + 1;\n                if attempts >= MAX_DISPATCH_ATTEMPTS {\n                    ClaimState::Terminal\n                } else {\n                    ClaimState::Retry {\n                        attempts,\n                        next_at: now + retry_backoff(attempts),\n                    }\n                }\n            }\n        },\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}","startLine":229,"lineNumbers":[229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":6041,"outputLines":140,"outputBytes":6041,"shownRange":{"start":229,"end":368},"nextOffset":369}}},"isError":false,"timestamp":1784271844948}}
{"type":"message","id":"40b8fe33","parentId":"0666090f","timestamp":"2026-07-17T07:04:04.949Z","message":{"role":"toolResult","toolCallId":"call_h5D2N9H0klSEIWhhfQYRqLNj|fc_01546d621137b6cd016a59d3e42bc481959e26f1ebcb737173","toolName":"read","content":[{"type":"text","text":"pub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n…\n                    }\n…\n                };\n…\n            });\n        }\n…\n    }\n…\n}\n\n[Showing lines 429-563 of 1558. Use :564 to continue]"}],"details":{"truncation":{"content":"pub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),","truncated":true,"truncatedBy":"lines","totalLines":1130,"totalBytes":7107,"outputLines":135,"outputBytes":7107,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n    // Dispatcher generation id (Amendment fix 9): every telemetry record this\n    // loop emits carries it, so per-generation attach/poison censuses line up.\n    let generation = now_ms();\n    // Global circuit breaker (Amendment fix 5) + its current window, shared\n    // with workers so a seat-blocked retry paces on it.\n    let breaker: Arc<Mutex<CircuitBreaker>> = Arc::new(Mutex::new(CircuitBreaker::default()));\n    // Bounded worker pool (Amendment fix 7): a cold table (daemon refresh\n    // with a large backlog) claims at most this many rows per poll — the\n    // rest wait for the next tick, batching the cold enumeration instead of\n    // spawning one thread per row. No claim lock is held across any I/O:\n    // enumeration runs lock-free, the claim map is touched in short scopes.\n    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n    let active_workers = Arc::new(std::sync::atomic::AtomicUsize::new(0));\n\n    while !stop.load(Ordering::Relaxed) {\n        if breaker.lock().unwrap().open(Instant::now()) {\n            // Circuit open: a poisoned replay just happened — pause ALL\n            // claiming for the window instead of hammering the wedged conn\n            // with fresh replay subscribers (Amendment fix 5).\n            thread::sleep(poll);\n            continue;\n        }\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n                break; // pool full: the rest of the table waits for the next poll\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let conn_id = s.conn_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            let breaker_done = Arc::clone(&breaker);\n            let pool = Arc::clone(&active_workers);\n            pool.fetch_add(1, Ordering::Relaxed);\n            thread::spawn(move || {\n                let started = Instant::now();\n                let report = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                pool.fetch_sub(1, Ordering::Relaxed);\n                let outcome = report.outcome;\n                let elapsed_ms = started.elapsed().as_millis();\n                if let DispatchOutcome::Served(detail) = &outcome {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                    // Breaker reset = REAL progress only (gate round 1): a row\n                    // that vanished before serve proves nothing about the\n                    // wedge — a sibling's row-gone would close the breaker\n                    // AND zero its doubling ladder mid-wedge. A real serve\n                    // (including a finished-attach retirement) resets.\n                    // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n                    if !detail.contains(\"terminal pre-serve\") {\n                        breaker_done.lock().unwrap().reset();\n                    }\n                } else {\n                    // Keyed worker-outcome record (Amendment fix 9) — the\n                    // legacy DISPATCH:<id> token is kept as a prefix so\n                    // existing field greps keep matching.\n                    // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                    eprintln!(\n                        \"{}\",\n                        dispatch_event(\n                            generation,\n                            stream_id,\n                            report.family,\n                            attempts,\n                            conn_id,\n                            \"worker-outcome\",\n                            &format!(\n                                \"{outcome:?} elapsed_ms={elapsed_ms} endpoint={}\",\n                                report.endpoint.as_deref().unwrap_or(\"-\")\n                            ),\n                        )\n                    );\n                }\n                // Seat-blocked failures trip the GLOBAL breaker; the claim\n                // requeue paces on the breaker window (never burning the\n                // bounded transient budget — T5's recovery discriminator).\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n                        if classify_failure(msg) == FailClass::SeatBlocked =>\n                    {\n                        let mut b = breaker_done.lock().unwrap();\n                        let window = b.trip(Instant::now());\n                        eprintln!(\n                            \"{}\",\n                            dispatch_event(\n                                generation,\n                                stream_id,\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n…\n                    }\n…\n                };\n…\n            });\n        }\n…\n    }\n…\n}","startLine":429,"lineNumbers":[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,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,null,567,null,569,null,575,576,null,578,null,580]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":7107,"outputLines":135,"outputBytes":7107,"shownRange":{"start":429,"end":563},"nextOffset":564}}},"isError":false,"timestamp":1784271844948}}
{"type":"message","id":"c063a978","parentId":"40b8fe33","timestamp":"2026-07-17T07:04:04.968Z","message":{"role":"toolResult","toolCallId":"call_tKPTnBs7B3zfz8SQVLCLhq9j|fc_01546d621137b6cd016a59d3e42be08195bb3e5e05afce14fa","toolName":"read","content":[{"type":"text","text":"    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n…\n        self.bounded_block_on(async move {\n            let mut send = entry.send.lock().await;\n            if !bytes.is_empty() {\n                send.write_all(&bytes).await?;\n            }\n            if finish {\n                send.finish().await?;\n            }\n            Ok(())\n        })\n    }\n\n    /// A stream's serve lease (ADR-0038 Amendment fix 4) — the cancellation\n    /// handle the DAEMON-LIFECYCLE C2 work consumes. `None` = no such stream.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .get(&stream_id)\n            .map(|e| Arc::clone(&e.log.lock().unwrap().lease))\n    }\n\n    /// Every held stream's info — the query a re-attaching brain uses to find\n    /// streams (including peer-initiated ones that arrived while it was down).\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n    /// enumeration is exactly what a future dispatcher generation would replay.\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .iter()\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n            .map(|(id, e)| {\n                let log = e.log.lock().unwrap();\n                NetStreamInfo {\n                    stream_id: *id,\n                    conn_id: e.conn_id,\n                    remote_id_hex: e.remote_id_hex.clone(),\n                    next_seq: log.next_seq,\n                    finished: log.finished,\n                    initiated_locally: e.initiated_locally,\n                    floor_seq: log.floor_seq(),\n                }\n            })\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n\n[Showing lines 1779-1858 of 2865. Use :1859 to continue]"}],"details":{"truncation":{"content":"        self.bounded_block_on(async move {\r\n            let mut send = entry.send.lock().await;\r\n            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r\n            if finish {\r\n                send.finish().await?;\r\n            }\r\n            Ok(())\r\n        })\r\n    }\r\n\r\n    /// A stream's serve lease (ADR-0038 Amendment fix 4) — the cancellation\r\n    /// handle the DAEMON-LIFECYCLE C2 work consumes. `None` = no such stream.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .get(&stream_id)\r\n            .map(|e| Arc::clone(&e.log.lock().unwrap().lease))\r\n    }\r\n\r\n    /// Every held stream's info — the query a re-attaching brain uses to find\r\n    /// streams (including peer-initiated ones that arrived while it was down).\r\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\r\n    /// enumeration is exactly what a future dispatcher generation would replay.\r\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .iter()\r\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\r\n            .map(|(id, e)| {\r\n                let log = e.log.lock().unwrap();\r\n                NetStreamInfo {\r\n                    stream_id: *id,\r\n                    conn_id: e.conn_id,\r\n                    remote_id_hex: e.remote_id_hex.clone(),\r\n                    next_seq: log.next_seq,\r\n                    finished: log.finished,\r\n                    initiated_locally: e.initiated_locally,\r\n                    floor_seq: log.floor_seq(),\r\n                }\r\n            })\r\n            .collect()\r\n    }\r\n\r\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\r\n    /// serve loop ran to completion — its exchange is over, so no future\r\n    /// dispatcher generation may rediscover and replay it (the control-steal\r\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\r\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\r\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\r\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\r\n    /// reply sends when it removed the entry (see the `retired` field note).\r\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\r\n    /// call newly retired the row (idempotent; absent row = false).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\r\n        let Some(entry) = self\r\n            .shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(Arc::clone)\r\n        else {\r\n            return false;\r\n        };\r\n        let newly = !entry\r\n            .retired\r\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\r\n        if newly {\r\n            // A retired exchange's request bytes are dead weight — free the\r\n            // ring; the pinned opener and cursors stay (cheap, bounded).\r\n            entry.log.lock().unwrap().ring.clear();\r\n        }\r\n        newly\r\n    }\r\n\r\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\r","truncated":true,"truncatedBy":"lines","totalLines":1087,"totalBytes":3456,"outputLines":80,"outputBytes":3456,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n…\n        self.bounded_block_on(async move {\r\n            let mut send = entry.send.lock().await;\r\n            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r\n            if finish {\r\n                send.finish().await?;\r\n            }\r\n            Ok(())\r\n        })\r\n    }\r\n\r\n    /// A stream's serve lease (ADR-0038 Amendment fix 4) — the cancellation\r\n    /// handle the DAEMON-LIFECYCLE C2 work consumes. `None` = no such stream.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    pub fn stream_lease(&self, stream_id: u64) -> Option<Arc<StreamLease>> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .get(&stream_id)\r\n            .map(|e| Arc::clone(&e.log.lock().unwrap().lease))\r\n    }\r\n\r\n    /// Every held stream's info — the query a re-attaching brain uses to find\r\n    /// streams (including peer-initiated ones that arrived while it was down).\r\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\r\n    /// enumeration is exactly what a future dispatcher generation would replay.\r\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .iter()\r\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\r\n            .map(|(id, e)| {\r\n                let log = e.log.lock().unwrap();\r\n                NetStreamInfo {\r\n                    stream_id: *id,\r\n                    conn_id: e.conn_id,\r\n                    remote_id_hex: e.remote_id_hex.clone(),\r\n                    next_seq: log.next_seq,\r\n                    finished: log.finished,\r\n                    initiated_locally: e.initiated_locally,\r\n                    floor_seq: log.floor_seq(),\r\n                }\r\n            })\r\n            .collect()\r\n    }\r\n\r\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\r\n    /// serve loop ran to completion — its exchange is over, so no future\r\n    /// dispatcher generation may rediscover and replay it (the control-steal\r\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\r\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\r\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\r\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\r\n    /// reply sends when it removed the entry (see the `retired` field note).\r\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\r\n    /// call newly retired the row (idempotent; absent row = false).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\r\n        let Some(entry) = self\r\n            .shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(Arc::clone)\r\n        else {\r\n            return false;\r\n        };\r\n        let newly = !entry\r\n            .retired\r\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\r\n        if newly {\r\n            // A retired exchange's request bytes are dead weight — free the\r\n            // ring; the pinned opener and cursors stay (cheap, bounded).\r\n            entry.log.lock().unwrap().ring.clear();\r\n        }\r\n        newly\r\n    }\r\n\r\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\r","startLine":1755,"lineNumbers":[1755,null,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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":3456,"outputLines":80,"outputBytes":3456,"shownRange":{"start":1779,"end":1858},"nextOffset":1859}}},"isError":false,"timestamp":1784271844968}}
{"type":"message","id":"a806f5af","parentId":"c063a978","timestamp":"2026-07-17T07:04:04.986Z","message":{"role":"toolResult","toolCallId":"call_bDOxZQBxJVkNM8Pv1Qn84LlE|fc_01546d621137b6cd016a59d3e42bec8195ad5681629e25d6ba","toolName":"read","content":[{"type":"text","text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n…\n                }\n                None => {\n                    buf.extend_from_slice(bytes);\n                    if buf.len() > OPENER_PIN_MAX {\n                        self.opener = OpenerCapture::Oversize;\n                    }\n                }\n            }\n        }\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        if self.retentive_cap == 0 {\n            while self.ring.len() > self.cap_chunks {\n                self.ring.pop_front();\n            }\n        }\n        if let Some(seat) = &self.subscriber {\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\n            // the seat's writer thread, outside this mutex — a wedged\n            // subscriber can no longer park this producer OR the lock.\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n            if let Err(fail) = seat.enqueue(frame) {\n                self.drop_seat(fail);\n            }\n        }\n    }\n\n    /// Remove the current seat after an enqueue failure (halt-at-first-\n    /// failure, all producer sites): a POISON also cancels the stream's\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn drop_seat(&mut self, fail: SeatFail) {\n        if let Some(seat) = self.subscriber.take() {\n            seat.sub.lifecycle_event(\n                \"stream-sub-halt\",\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\n            );\n            if fail == SeatFail::Poisoned {\n                self.lease.cancel();\n            }\n        }\n    }\n\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\n    /// pending yet.\n    fn drain(&mut self) -> (Vec<u8>, bool) {\n        let mut out = Vec::new();\n        while let Some((_, chunk)) = self.ring.pop_front() {\n            out.extend_from_slice(&chunk);\n        }\n        (out, self.finished)\n    }\n\n    /// Mark the read side finished and tell the subscriber (with the final\n    /// cursor, so it can verify completeness).\n    fn finish(&mut self) {\n        self.finished = true;\n        if let Some(seat) = &self.subscriber {\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n            // Enqueue only — same discipline as append (Amendment fix 2:\n            // ALL producer sites halt-and-remove, finish included).\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n            if let Err(fail) = seat.enqueue(frame) {\n                self.drop_seat(fail);\n            }\n        }\n    }\n\n    /// Install `sub` as the subscriber with the replay snapshot (retained\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\n    /// replay-then-live seq order is structural (see\n    /// [`SubscriberSeat::install`]), while every conn write still happens\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\n    /// under the lock with discarded write errors, the exact serial-poison\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\n    ///\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\n    /// a replacement subscriber is never installed until the prior one is\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\n        if let Some(prior) = &self.subscriber {\n            // No-reinstall-until-gone applies to the POISON class only\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\n            // 15s bounded write — installing a replacement now would hand the\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\n            // DISPLACED as before (the newer-brain-takes-over contract).\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\n                return Err(io::Error::new(\n                    io::ErrorKind::WouldBlock,\n                    format!(\n                        \"stream {} subscriber busy: prior subscriber still draining\",\n                        self.stream_id\n                    ),\n                ));\n            }\n            // Healthy displacement, prior fully gone, or same sub re-attach.\n            self.subscriber = None;\n        }\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\n        // record — this is one of the two RCA-pinned shared-carrier classes.\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\n        sub.lifecycle_event(\n            \"stream-sub-attach\",\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n        );\n        let mut replay: Vec<Envelope> = self\n            .ring\n            .iter()\n            .filter(|(s, _)| *s >= from_seq)\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\n            .collect();\n        if self.finished {\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\n        }\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\n        // prior writer — poisoned OR healthily displaced — holds only its own\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\n        // brain-swap displacement left the displaced writer and the successor\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\n        // write failure (its conn dying — every daemon refresh) canceled the\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\n        // displaced stream on exactly the post-refresh path this wave calms.\n        // One poison never dead-ends the stream (T5 recovery): the next\n        // generation's mint IS the renewal; `send_stream` reads this current\n        // Arc, old workers' handles stay theirs (C2 consumes via\n        // [`NetHost::stream_lease`]).\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        self.lease = Arc::new(StreamLease::default());\n        let replay_len = replay.len();\n        self.subscriber = Some(SubscriberSeat::install(\n            sub,\n            replay,\n            Some(Arc::clone(&self.lease)),\n        ));\n        Ok(replay_len)\n    }\n\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\n    /// not unsubscribe a newer brain).\n    fn detach_if(&mut self, sub: &SharedSend) {\n        if let Some(cur) = &self.subscriber {\n            if cur.is(sub) {\n                self.subscriber = None;\n            }\n        }\n    }\n}\n\n/// A bounded, sequenced log of connection liveness events — the broker-owned\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\n/// applied to the conn table itself. `register_conn` appends `connected`; each\n/// connection's closed-watcher appends `disconnected` after removing the row.\n/// A brain subscribes with a cursor and replays events produced while it was\n/// down, so presence survives a brain restart exactly like stream reads do.\n// [impl->REQ-EP-4]\nstruct PresenceLog {\n    next_seq: u64,\n    ring: VecDeque<NetPresenceEvent>,\n    cap_events: usize,\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\n    subscriber: Option<SubscriberSeat>,\n}\n\nimpl PresenceLog {\n    fn new(cap_events: usize) -> Self {\n        PresenceLog {\n            next_seq: 0,\n            ring: VecDeque::new(),\n            cap_events,\n            subscriber: None,\n        }\n    }\n…\n}\n\n[Showing lines 429-613 of 2865. Use :614 to continue]"}],"details":{"truncation":{"content":"                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        let seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back((seq, bytes.to_vec()));\r\n        if self.retentive_cap == 0 {\r\n            while self.ring.len() > self.cap_chunks {\r\n                self.ring.pop_front();\r\n            }\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\r\n            // the seat's writer thread, outside this mutex — a wedged\r\n            // subscriber can no longer park this producer OR the lock.\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Remove the current seat after an enqueue failure (halt-at-first-\r\n    /// failure, all producer sites): a POISON also cancels the stream's\r\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\r\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn drop_seat(&mut self, fail: SeatFail) {\r\n        if let Some(seat) = self.subscriber.take() {\r\n            seat.sub.lifecycle_event(\r\n                \"stream-sub-halt\",\r\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\r\n            );\r\n            if fail == SeatFail::Poisoned {\r\n                self.lease.cancel();\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Enqueue only — same discipline as append (Amendment fix 2:\r\n            // ALL producer sites halt-and-remove, finish included).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Install `sub` as the subscriber with the replay snapshot (retained\r\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\r\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\r\n    /// replay-then-live seq order is structural (see\r\n    /// [`SubscriberSeat::install`]), while every conn write still happens\r\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\r\n    /// under the lock with discarded write errors, the exact serial-poison\r\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\r\n    ///\r\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\r\n    /// a replacement subscriber is never installed until the prior one is\r\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // No-reinstall-until-gone applies to the POISON class only\r\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\r\n            // 15s bounded write — installing a replacement now would hand the\r\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\r\n            // DISPLACED as before (the newer-brain-takes-over contract).\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    format!(\r\n                        \"stream {} subscriber busy: prior subscriber still draining\",\r\n                        self.stream_id\r\n                    ),\r\n                ));\r\n            }\r\n            // Healthy displacement, prior fully gone, or same sub re-attach.\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        let mut replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|(s, _)| *s >= from_seq)\r\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\r\n            .collect();\r\n        if self.finished {\r\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\r\n        }\r\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\r\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\r\n        // prior writer — poisoned OR healthily displaced — holds only its own\r\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\r\n        // brain-swap displacement left the displaced writer and the successor\r\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\r\n        // write failure (its conn dying — every daemon refresh) canceled the\r\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\r\n        // displaced stream on exactly the post-refresh path this wave calms.\r\n        // One poison never dead-ends the stream (T5 recovery): the next\r\n        // generation's mint IS the renewal; `send_stream` reads this current\r\n        // Arc, old workers' handles stay theirs (C2 consumes via\r\n        // [`NetHost::stream_lease`]).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        self.lease = Arc::new(StreamLease::default());\r\n        let replay_len = replay.len();\r\n        self.subscriber = Some(SubscriberSeat::install(\r\n            sub,\r\n            replay,\r\n            Some(Arc::clone(&self.lease)),\r\n        ));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r\n    cap_events: usize,\r\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\r\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\r\n    subscriber: Option<SubscriberSeat>,\r\n}\r\n\r\nimpl PresenceLog {\r\n    fn new(cap_events: usize) -> Self {\r\n        PresenceLog {\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_events,\r\n            subscriber: None,\r\n        }\r\n    }\r","truncated":true,"truncatedBy":"lines","totalLines":2437,"totalBytes":8634,"outputLines":185,"outputBytes":8634,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n…\n                }\r\n                None => {\r\n                    buf.extend_from_slice(bytes);\r\n                    if buf.len() > OPENER_PIN_MAX {\r\n                        self.opener = OpenerCapture::Oversize;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        let seq = self.next_seq;\r\n        self.next_seq += 1;\r\n        self.ring.push_back((seq, bytes.to_vec()));\r\n        if self.retentive_cap == 0 {\r\n            while self.ring.len() > self.cap_chunks {\r\n                self.ring.pop_front();\r\n            }\r\n        }\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // ENQUEUE only (ADR-0038 Amendment fix 3): the conn I/O runs on\r\n            // the seat's writer thread, outside this mutex — a wedged\r\n            // subscriber can no longer park this producer OR the lock.\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Remove the current seat after an enqueue failure (halt-at-first-\r\n    /// failure, all producer sites): a POISON also cancels the stream's\r\n    /// lease so the paired serve/wire worker terminates (Amendment fix 4);\r\n    /// an OVERFLOW just detaches — the brain resumes from its cursor.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn drop_seat(&mut self, fail: SeatFail) {\r\n        if let Some(seat) = self.subscriber.take() {\r\n            seat.sub.lifecycle_event(\r\n                \"stream-sub-halt\",\r\n                &format!(\"stream={} reason={fail:?}\", self.stream_id),\r\n            );\r\n            if fail == SeatFail::Poisoned {\r\n                self.lease.cancel();\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(seat) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Enqueue only — same discipline as append (Amendment fix 2:\r\n            // ALL producer sites halt-and-remove, finish included).\r\n            // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n            if let Err(fail) = seat.enqueue(frame) {\r\n                self.drop_seat(fail);\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Install `sub` as the subscriber with the replay snapshot (retained\r\n    /// chunks with `seq >= from_seq`, then eof if already finished) handed to\r\n    /// its seat's WRITER THREAD, which drains it before any live frame — the\r\n    /// replay-then-live seq order is structural (see\r\n    /// [`SubscriberSeat::install`]), while every conn write still happens\r\n    /// OUTSIDE this mutex (the pre-Amendment shape replayed the whole ring\r\n    /// under the lock with discarded write errors, the exact serial-poison\r\n    /// engine of the v0.34 field stalls). Returns the replay frame count.\r\n    ///\r\n    /// Refuses while a prior seat writer is still alive (`WouldBlock`):\r\n    /// a replacement subscriber is never installed until the prior one is\r\n    /// fully gone (REQ-DISPATCH-FALLBACK-CIRCUIT no-reinstall rule).\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn begin_attach(&mut self, sub: SharedSend, from_seq: u64) -> io::Result<usize> {\r\n        if let Some(prior) = &self.subscriber {\r\n            // No-reinstall-until-gone applies to the POISON class only\r\n            // (REQ-DISPATCH-FALLBACK-CIRCUIT): a wedged writer may sit in a\r\n            // 15s bounded write — installing a replacement now would hand the\r\n            // retry machinery a fresh replay victim. A HEALTHY prior seat is\r\n            // DISPLACED as before (the newer-brain-takes-over contract).\r\n            if prior.poisoned.load(Ordering::Acquire) && !prior.gone() && !prior.is(&sub) {\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::WouldBlock,\r\n                    format!(\r\n                        \"stream {} subscriber busy: prior subscriber still draining\",\r\n                        self.stream_id\r\n                    ),\r\n                ));\r\n            }\r\n            // Healthy displacement, prior fully gone, or same sub re-attach.\r\n            self.subscriber = None;\r\n        }\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        let mut replay: Vec<Envelope> = self\r\n            .ring\r\n            .iter()\r\n            .filter(|(s, _)| *s >= from_seq)\r\n            .map(|(seq, bytes)| net_stream_data_envelope(self.stream_id, *seq, bytes))\r\n            .collect();\r\n        if self.finished {\r\n            replay.push(net_stream_eof_envelope(self.stream_id, self.next_seq));\r\n        }\r\n        // Mint a FRESH lease at EVERY install (Amendment fix 4 + gate round 1\r\n        // on the fix PR): each subscriber generation gets its OWN Arc, and a\r\n        // prior writer — poisoned OR healthily displaced — holds only its own\r\n        // generation's handle. Renewal-on-canceled alone was WRONG: a healthy\r\n        // brain-swap displacement left the displaced writer and the successor\r\n        // SHARING one Arc, so the displaced writer's eventual queued-frame\r\n        // write failure (its conn dying — every daemon refresh) canceled the\r\n        // SUCCESSOR's lease: a spurious serve-kill + breaker trip per\r\n        // displaced stream on exactly the post-refresh path this wave calms.\r\n        // One poison never dead-ends the stream (T5 recovery): the next\r\n        // generation's mint IS the renewal; `send_stream` reads this current\r\n        // Arc, old workers' handles stay theirs (C2 consumes via\r\n        // [`NetHost::stream_lease`]).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        self.lease = Arc::new(StreamLease::default());\r\n        let replay_len = replay.len();\r\n        self.subscriber = Some(SubscriberSeat::install(\r\n            sub,\r\n            replay,\r\n            Some(Arc::clone(&self.lease)),\r\n        ));\r\n        Ok(replay_len)\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if cur.is(sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r\n    cap_events: usize,\r\n    /// Behind the same bounded writer-queue seat as `StreamLog` (ADR-0038\r\n    /// Amendment fixes 2+3 — the PresenceLog mirrors ride the same change).\r\n    subscriber: Option<SubscriberSeat>,\r\n}\r\n\r\nimpl PresenceLog {\r\n    fn new(cap_events: usize) -> Self {\r\n        PresenceLog {\r\n            next_seq: 0,\r\n            ring: VecDeque::new(),\r\n            cap_events,\r\n            subscriber: None,\r\n        }\r\n    }\r\n…\n}","startLine":374,"lineNumbers":[374,null,414,null,419,420,421,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,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,null,730]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":8634,"outputLines":185,"outputBytes":8634,"shownRange":{"start":429,"end":613},"nextOffset":614}}},"isError":false,"timestamp":1784271844986}}
{"type":"message","id":"cfc7d6d6","parentId":"a806f5af","timestamp":"2026-07-17T07:04:05.023Z","message":{"role":"toolResult","toolCallId":"call_tpJKXBlKqEizQJEYAyoCLnFn|fc_01546d621137b6cd016a59d3e42be4819599087f287048cb64","toolName":"read","content":[{"type":"text","text":"    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\n…\n    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    // TEST-RIG-ONLY (T7 mixed-image N-1): with\n                    // SPT_TEST_STREAM_OPENER_UNSUPPORTED set this broker\n                    // answers the verb EXACTLY like an N-1 broker that\n                    // predates it — the forward-compat unknown-kind reply\n                    // below, byte-identical (the stable KH-2.3 wire fact the\n                    // dispatcher's fallback gate keys on). Never set in\n                    // production.\n                    if std::env::var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\").is_ok() {\n                        send_error(&send, &format!(\"unknown command kind {:?}\", env.kind));\n                    } else if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n                    let reply = SessionsReply {\n                        sessions: snaps\n                            .into_iter()\n                            .map(|s| SessionInfo {\n                                session_id: s.id,\n                                endpoint: s.endpoint,\n                                resume_seq: s.resume_seq,\n                                controller_by: s.controller_by,\n                            })\n                            .collect(),\n                    };\n                    let frame = Envelope::new(\n                        KIND_SESSIONS_REPLY,\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session\n…\n    }\n\n[Showing lines 3019-3303 of 6298. Use :3304 to continue]"}],"details":{"truncation":{"content":"    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    // TEST-RIG-ONLY (T7 mixed-image N-1): with\n                    // SPT_TEST_STREAM_OPENER_UNSUPPORTED set this broker\n                    // answers the verb EXACTLY like an N-1 broker that\n                    // predates it — the forward-compat unknown-kind reply\n                    // below, byte-identical (the stable KH-2.3 wire fact the\n                    // dispatcher's fallback gate keys on). Never set in\n                    // production.\n                    if std::env::var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\").is_ok() {\n                        send_error(&send, &format!(\"unknown command kind {:?}\", env.kind));\n                    } else if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n                    let reply = SessionsReply {\n                        sessions: snaps\n                            .into_iter()\n                            .map(|s| SessionInfo {\n                                session_id: s.id,\n                                endpoint: s.endpoint,\n                                resume_seq: s.resume_seq,\n                                controller_by: s.controller_by,\n                            })\n                            .collect(),\n                    };\n                    let frame = Envelope::new(\n                        KIND_SESSIONS_REPLY,\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session","truncated":true,"truncatedBy":"lines","totalLines":3280,"totalBytes":14169,"outputLines":285,"outputBytes":14169,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\n…\n    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    // TEST-RIG-ONLY (T7 mixed-image N-1): with\n                    // SPT_TEST_STREAM_OPENER_UNSUPPORTED set this broker\n                    // answers the verb EXACTLY like an N-1 broker that\n                    // predates it — the forward-compat unknown-kind reply\n                    // below, byte-identical (the stable KH-2.3 wire fact the\n                    // dispatcher's fallback gate keys on). Never set in\n                    // production.\n                    if std::env::var(\"SPT_TEST_STREAM_OPENER_UNSUPPORTED\").is_ok() {\n                        send_error(&send, &format!(\"unknown command kind {:?}\", env.kind));\n                    } else if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n                    let reply = SessionsReply {\n                        sessions: snaps\n                            .into_iter()\n                            .map(|s| SessionInfo {\n                                session_id: s.id,\n                                endpoint: s.endpoint,\n                                resume_seq: s.resume_seq,\n                                controller_by: s.controller_by,\n                            })\n                            .collect(),\n                    };\n                    let frame = Envelope::new(\n                        KIND_SESSIONS_REPLY,\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n        // sole spawner post-W3, so its own spawn choke is the single-flight authority.\n        // Dedup a duplicate wake/resume for an endpoint that already has a live session\n…\n    }","startLine":3013,"lineNumbers":[3013,null,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,null,3512]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":14169,"outputLines":285,"outputBytes":14169,"shownRange":{"start":3019,"end":3303},"nextOffset":3304}}},"isError":false,"timestamp":1784271845023}}
{"type":"message","id":"d1876ff5","parentId":"cfc7d6d6","timestamp":"2026-07-17T07:04:23.434Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing stream enumeration and connection leak**\n\n**Tracing stream read pump task spawning**"},{"type":"toolCall","id":"call_AMQe6mjkOk0C0CoUsNPMOyu6|fc_01546d621137b6cd016a59d3f6bcd0819580a3f9f76406e122","name":"read","arguments":{"i":"Reading host runtime construction","path":"crates/spt-daemon/src/nethost.rs","selector":"1540-1625"}},{"type":"toolCall","id":"call_Teq4j7SdyyN0QPkVn1cjPY8i|fc_01546d621137b6cd016a59d3f6bce481958cbc04ed91e34702","name":"read","arguments":{"i":"Reading stream registration tasks","path":"crates/spt-daemon/src/nethost.rs","selector":"1650-1780"}},{"type":"toolCall","id":"call_TUmegX9VuPOgMODPi5NLl9aP|fc_01546d621137b6cd016a59d3f6bcec819592260319696d088d","name":"grep","arguments":{"i":"Finding Tokio runtime sizing","pattern":"Builder::new_multi_thread|worker_threads|max_blocking_threads|Runtime::new|enable_all","path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/main.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_c9traQY4Tni9KepZKzfd5tl2|fc_01546d621137b6cd016a59d3f6bcf48195bf72bd0aa523ccd3","name":"grep","arguments":{"i":"Finding per stream thread spawns","pattern":"thread::spawn|tokio::spawn|runtime\\.spawn|rt\\.spawn","path":"crates/spt-daemon/src/nethost.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_Q81ykFBYn1obfakYOZJmkkRD|fc_01546d621137b6cd016a59d3f6bcf88195a0a23d24a05d3c1a","name":"read","arguments":{"i":"Reading broker start threads","path":"crates/spt-daemon/src/broker.rs","selector":"2600-2810"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":14443,"output":598,"cacheRead":51712,"cacheWrite":0,"totalTokens":66753,"reasoningTokens":305,"cost":{"input":0.072215,"output":0.01794,"cacheRead":0.025856,"cacheWrite":0,"total":0.116011}},"stopReason":"toolUse","timestamp":1784271845074,"responseId":"resp_01546d621137b6cd016a59d3e54ae4819583dd37049f25e9e0","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_01546d621137b6cd016a59d3e768408195a9c6a313c06c143a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdP2xKZZM4O3gkphj7PN2Yuldg6qaY4TxGbRTLBZVsL776IsLqc9UMEIj_tkjn6RUS3KxvJmTiFWKjxi2BTWymsOgTavcpblAcT-Mg70NduDz6Jfb8sR-DM7yoHI29EpcpYk9hVESCKBt4GRJWCZglzLZGtLZW75sfBa7xOWpLfYzMz59hfrZ2-2JasGT1K6eTDHmKoAe3w2tqwSZwnpOXw_PaONRK9rXMMQQahZz-KUjE9fVt_hInkqWf370pInSChpcycUTxuoDH6yJtwMKu7WhTRn3DNKKoHTJ7TQ5claRF8N5Fm3kyBN78sAGA9kJWBzhOKtZUG-uKhhzTW4yZxnzmmPVHxr_lMoNEoWFAH1pX0o3ZMUBzoCuuWIr32vUGPgyZ2aHVwxjN3x3_sBx-fzygSzcNa3oslsttbRSEUwCXIwySXeH8nru0i20ZxvkULz9hDEg8Quu__ns2y3LDCu6RTPu7YcDv11o4g4-4sZRNafgPgarWdWFf47jpFgO90NrBfSiUQeHYdSCZ8a_EZjAcnjWtglDEFSiVit89PFUrubWFpY1gI1TwRLxqSUtCYK94aghIAEI9DOmuFEroMmSU04VzLymIdWGdba9ffj3yup6AWOdAzsgmgydiU3dUYlZq3bDzzuv_eEMeUrDCoiLMbC0GM76EABxfXi08NTGdAS5wbcgyti7JYYXMSpec0c035UbIAwgvX6-lWaLPpgorGF5q6tZXGKSaLAsCveJ-h46KgyRX9WIdxYV5WW5w5xUpxAKN6gu52_12eLwsE6zm8cfNYCKt_ERf2JWftUsksy37U0reK3Kr-m0HZB1yOgMzJ_Ke1jVqfbEOHb2MIbgNefrOwV-xEUzdZlI_fYyIFEFhLFcqsXiIr1mQpWWtdDY2q6ot8Gft1WisSUslKjgthvYEnTvQfZKVcq4m9W_7BFnvS_vv7UKIVGHz0Wgh3ZlJGjhDqXffScMgTsl_LiUFZZbdiRn-oF_t4kCfOX46IO56MKz_PYjYyonvGptvzr155VzmLuJ_LLVQ09WGkveFLpaexUSvIrKEFIUtzfWMCxT7UlRlmM5eXGfqPiAfH6rcW35WvEKcvhBCHaNg4Pi2PbbinkWcPsKwbLA3T9baTjzCuwql9WW_YQm_pSL4kBTNsJdpvYd4OfXpjRTxk4AaWDwGzpfULBMitPc4jg55AuOsgKuRcAqfFoxKitgxmPF_CERfVPv_7eNMs36L6rTH34w8WhCtgnrpX32uF9ff3f0-kkROZ0G5JuNltsPmvz8vSaFIdd-H558miYKab5sEFWBTP7zFqMzE5txxmp5Q4arpzkUBukcc0Cdnz0_lhHPIvCsRuUK-RG1tGprmLGzVMfXRVRCWvNlPisQ3ody3KmAUGtH6RirqMZpTM8Zw98rpjnyMLb6mje1Hm7UQ6C7EJzetdiijEiJo7aVr16sgS6pssxkCGn2---wFSYqWfa5DWPIn_lp6_l9C9wskkK7avuW20sMlSS568u5fFiq5KHWzSaQvby2UYszxSONIK0prIGP8cAvi8Dz6E_Ap-iqCPhsCp5ELfyqjB-3Fmyk7BGPelsVB4x63MjFHaCYNyo94If73WB8qWkpqwyXZvA_T1pB1plTQMcgLpMf_zrRWRurHo6ZiFUF2g3gkjiBjS0eEsGdcZ-dJILRZkeJgNVyzNwBaFXaXP8m72KTKi77Na_dqCNu4QcTJBD_jcOHIDTi4Oz9exia8VqLctr0FwGe88Z1QhG7SAZ3WXXvRZRjgmVSM4_FUVRx3JAMIRgGWG38rJYVDG8Mqxovytta-QVDrg7fS58kqDUK2fghaexi9nKb1etgijnDYe53PS_ly1fCY-HesFl3did-gbgtYsg9ns-2hAtPG0G3GaugBZi0i15Z7P9L71Bt9MVC5rFGrVi33bP14bMnm1bwNeDntxK3LNm8YKCq2GtYluHK6PGiifzNcWMguePHOr5pufPnSxQG14WHMDMPJup9u3WV-9Bk2hvjTBchcyNT1mkKEddsGzuHuVHsC7MmaDR1CiiCVickNyGw8iuIxzopO6s06G_u7TKKZzkIu-WrXYkh-T1LXyQWiwtLR6iU19WO7PBCN8Oc8eJEC1-maUpWuoEt6kEOv1iMce_hLs3wG8CvxfaDE6zO_DW_Cz4EJVvtpv20oGiCN8CviriW2Zd2RNiqey7rQRH_XCS0oCACLCWIOaRNcmmH8qvRVKRj5ZVpZtHgBMIh2eBwW0ERzHElFt_aAH-dbsvG10uhK9zpKA9-82dc3MvZGTypMA2HnGNk_2rgF0iLiD4plsH4ByBm9I4E3HLm8DWnf_jGJ2z_vKjlq72yu-D7SCbOYqR5Qy7e_47fv-gazhbmtV8CRE8Dmy-MdH5jAsxr3CFXmQSzOmGhHB2D9hrc9lm2QbXDLmkXPNSUV9LYVFv6c6jul5HZd54e2fGroatiUIs76qBjfuiDqGqLyIbzzh_N55neaFAxHg72ulVGi_F0xA-h7l12n2orj3WROyiPT4mF7F0-Tuzf2R31SQX8aMgHlZhfi6xBQm6G1MGbql71uYYOjkFPhMjNZoZekFgxRHVtsj2LnKzcWhbuWX0P9eG--o-cetU0MvE0WNrEsPSTAyTrvXzasE_2VfO6q8TgC4sHEbmkthjQam65usPUBJIYpQLt8RD7VJ1bGZALfZrUL681djz7Ah29xClHwkv-p7NCWUc_7Cz6bSmEbDFG5W9pLLFUcZVx8LGMwfQcrH_q16Gkx23hW7Vhb16aln1kwVlOs8-Zr96TeODQon64Efbt6MYgCRk6bj1KnnrW57En53pvfRgDYUhGCQ43g==","summary":[{"type":"summary_text","text":"**Analyzing stream enumeration and connection leak**"},{"type":"summary_text","text":"**Tracing stream read pump task spawning**"}]},{"id":"fc_01546d621137b6cd016a59d3f6bcd0819580a3f9f76406e122","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading host runtime construction\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1540-1625\"}","call_id":"call_AMQe6mjkOk0C0CoUsNPMOyu6","name":"read"},{"id":"fc_01546d621137b6cd016a59d3f6bce481958cbc04ed91e34702","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream registration tasks\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1650-1780\"}","call_id":"call_Teq4j7SdyyN0QPkVn1cjPY8i","name":"read"},{"id":"fc_01546d621137b6cd016a59d3f6bcec819592260319696d088d","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding Tokio runtime sizing\",\"pattern\":\"Builder::new_multi_thread|worker_threads|max_blocking_threads|Runtime::new|enable_all\",\"path\":\"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/main.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_TUmegX9VuPOgMODPi5NLl9aP","name":"grep"},{"id":"fc_01546d621137b6cd016a59d3f6bcf48195bf72bd0aa523ccd3","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding per stream thread spawns\",\"pattern\":\"thread::spawn|tokio::spawn|runtime\\\\.spawn|rt\\\\.spawn\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_c9traQY4Tni9KepZKzfd5tl2","name":"grep"},{"id":"fc_01546d621137b6cd016a59d3f6bcf88195a0a23d24a05d3c1a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading broker start threads\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"2600-2810\"}","call_id":"call_Q81ykFBYn1obfakYOZJmkkRD","name":"read"}]},"duration":18359.596300005913,"ttft":2379.567000001669,"contextSnapshot":{"promptTokens":66155,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_AMQe6mjkOk0C0CoUsNPMOyu6|fc_01546d621137b6cd016a59d3f6bcd0819580a3f9f76406e122","toolName":"read","startedAt":"2026-07-17T07:04:23.435Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading host runtime construction"},"id":"8960faa9","parentId":"d1876ff5","timestamp":"2026-07-17T07:04:23.435Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Teq4j7SdyyN0QPkVn1cjPY8i|fc_01546d621137b6cd016a59d3f6bce481958cbc04ed91e34702","toolName":"read","startedAt":"2026-07-17T07:04:23.435Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream registration tasks"},"id":"9530c003","parentId":"8960faa9","timestamp":"2026-07-17T07:04:23.435Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_TUmegX9VuPOgMODPi5NLl9aP|fc_01546d621137b6cd016a59d3f6bcec819592260319696d088d","toolName":"grep","startedAt":"2026-07-17T07:04:23.435Z","args":{"path":"crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/main.rs"},"intent":"Finding Tokio runtime sizing"},"id":"cd4ec5cd","parentId":"9530c003","timestamp":"2026-07-17T07:04:23.435Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_c9traQY4Tni9KepZKzfd5tl2|fc_01546d621137b6cd016a59d3f6bcf48195bf72bd0aa523ccd3","toolName":"grep","startedAt":"2026-07-17T07:04:23.435Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Finding per stream thread spawns"},"id":"4b206e8d","parentId":"cd4ec5cd","timestamp":"2026-07-17T07:04:23.435Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Q81ykFBYn1obfakYOZJmkkRD|fc_01546d621137b6cd016a59d3f6bcf88195a0a23d24a05d3c1a","toolName":"read","startedAt":"2026-07-17T07:04:23.435Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading broker start threads"},"id":"fa2af86f","parentId":"4b206e8d","timestamp":"2026-07-17T07:04:23.435Z"}
{"type":"message","id":"13e937b6","parentId":"fa2af86f","timestamp":"2026-07-17T07:04:23.440Z","message":{"role":"toolResult","toolCallId":"call_c9traQY4Tni9KepZKzfd5tl2|fc_01546d621137b6cd016a59d3f6bcf48195bf72bd0aa523ccd3","toolName":"grep","content":[{"type":"text","text":" 195|        let writer_poisoned = Arc::clone(&poisoned);\n*196|        std::thread::spawn(move || {\n 197|            let poison = |halt_reason: &str| {\n 198|                writer_poisoned.store(true, Ordering::Release);\n 199|                if let Some(l) = &lease {\n...\n 953|    let hex_close = remote_id_hex.clone();\n*954|    tokio::spawn(async move {\n 955|        conn_close.closed().await;\n 956|        sh_close.conns.lock().unwrap().remove(&id);\n 957|        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n...\n 979|    let hex = remote_id_hex.clone();\n*980|    tokio::spawn(async move {\n 981|        while let Ok((send, recv)) = conn.accept_bi().await {\n 982|            register_stream(\n 983|                &sh,\n...\n 1035|    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n*1036|    tokio::spawn(async move {\n 1037|        match recv {\n 1038|            RecvHalf::Quic(mut recv) => loop {\n 1039|                await_ring_room(&log, &room).await;\n...\n 1218|        let accept_ep = Arc::clone(&endpoint);\n*1219|        runtime.spawn(async move {\n 1220|            while let Some(incoming) = ep.accept().await {\n 1221|                let sh = Arc::clone(&sh);\n 1222|                let rate = Arc::clone(&rate);\n...\n 1228|                    serde_json::to_value(accept_ep.addr()).unwrap_or(serde_json::Value::Null);\n*1229|                tokio::spawn(async move {\n 1230|                    let Ok(connecting) = incoming.accept() else {\n 1231|                        return;\n 1232|                    };\n...\n 1287|        let canary = Arc::clone(&net_canary_tick_ms);\n*1288|        runtime.spawn(async move {\n 1289|            let mut ivl = tokio::time::interval(Duration::from_millis(25));\n 1290|            loop {\n 1291|                ivl.tick().await;\n...\n 1491|        let gauge = Arc::clone(&self.active_dial_tasks);\n*1492|        self.runtime.spawn(async move {\n 1493|            gauge.fetch_add(1, Ordering::Relaxed);\n 1494|            let _dial_guard = DialGauge(gauge);\n 1495|            // Stage cell (ADR-0039 Decision 5): the connect+prove pipeline\n...\n 2258|            let sent = Arc::clone(&sent);\n*2259|            std::thread::spawn(move || {\n 2260|                for f in &frames {\n 2261|                    host.send_stream(owner, f, false).expect(\"send\");\n 2262|                    sent.fetch_add(1, AtOrd::Relaxed);"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":9,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":9}],"truncated":false,"displayContent":"  195│        let writer_poisoned = Arc::clone(&poisoned);\n *196│        std::thread::spawn(move || {\n  197│            let poison = |halt_reason: &str| {\n  198│                writer_poisoned.store(true, Ordering::Release);\n  199│                if let Some(l) = &lease {\n     │...\n  953│    let hex_close = remote_id_hex.clone();\n *954│    tokio::spawn(async move {\n  955│        conn_close.closed().await;\n  956│        sh_close.conns.lock().unwrap().remove(&id);\n  957│        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n     │...\n  979│    let hex = remote_id_hex.clone();\n *980│    tokio::spawn(async move {\n  981│        while let Ok((send, recv)) = conn.accept_bi().await {\n  982│            register_stream(\n  983│                &sh,\n     │...\n 1035│    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n*1036│    tokio::spawn(async move {\n 1037│        match recv {\n 1038│            RecvHalf::Quic(mut recv) => loop {\n 1039│                await_ring_room(&log, &room).await;\n     │...\n 1218│        let accept_ep = Arc::clone(&endpoint);\n*1219│        runtime.spawn(async move {\n 1220│            while let Some(incoming) = ep.accept().await {\n 1221│                let sh = Arc::clone(&sh);\n 1222│                let rate = Arc::clone(&rate);\n     │...\n 1228│                    serde_json::to_value(accept_ep.addr()).unwrap_or(serde_json::Value::Null);\n*1229│                tokio::spawn(async move {\n 1230│                    let Ok(connecting) = incoming.accept() else {\n 1231│                        return;\n 1232│                    };\n     │...\n 1287│        let canary = Arc::clone(&net_canary_tick_ms);\n*1288│        runtime.spawn(async move {\n 1289│            let mut ivl = tokio::time::interval(Duration::from_millis(25));\n 1290│            loop {\n 1291│                ivl.tick().await;\n     │...\n 1491│        let gauge = Arc::clone(&self.active_dial_tasks);\n*1492│        self.runtime.spawn(async move {\n 1493│            gauge.fetch_add(1, Ordering::Relaxed);\n 1494│            let _dial_guard = DialGauge(gauge);\n 1495│            // Stage cell (ADR-0039 Decision 5): the connect+prove pipeline\n     │...\n 2258│            let sent = Arc::clone(&sent);\n*2259│            std::thread::spawn(move || {\n 2260│                for f in &frames {\n 2261│                    host.send_stream(owner, f, false).expect(\"send\");\n 2262│                    sent.fetch_add(1, AtOrd::Relaxed);"},"isError":false,"timestamp":1784271863439}}
{"type":"message","id":"3940dbc2","parentId":"13e937b6","timestamp":"2026-07-17T07:04:23.442Z","message":{"role":"toolResult","toolCallId":"call_TUmegX9VuPOgMODPi5NLl9aP|fc_01546d621137b6cd016a59d3f6bcec819592260319696d088d","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## nethost.rs\n 1183|        spt_net::net::pairing::ntp::set_enabled(!matches!(cfg.scope, BindScope::Loopback));\n*1184|        let runtime = tokio::runtime::Builder::new_multi_thread()\n*1185|            .worker_threads(2)\n 1186|            .thread_name(\"spt-broker-net\")\n*1187|            .enable_all()\n 1188|            .build()?;\n 1189|        let endpoint = runtime\n 1190|            .block_on(NetEndpoint::bind_scoped(\n\nSkipped missing paths: crates/spt-daemon/src/main.rs"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs, crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":3,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":3}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## nethost.rs\n 1183│        spt_net::net::pairing::ntp::set_enabled(!matches!(cfg.scope, BindScope::Loopback));\n*1184│        let runtime = tokio::runtime::Builder::new_multi_thread()\n*1185│            .worker_threads(2)\n 1186│            .thread_name(\"spt-broker-net\")\n*1187│            .enable_all()\n 1188│            .build()?;\n 1189│        let endpoint = runtime\n 1190│            .block_on(NetEndpoint::bind_scoped(","missingPaths":["crates/spt-daemon/src/main.rs"]},"isError":false,"timestamp":1784271863442}}
{"type":"message","id":"3ca6d03a","parentId":"3940dbc2","timestamp":"2026-07-17T07:04:23.464Z","message":{"role":"toolResult","toolCallId":"call_Teq4j7SdyyN0QPkVn1cjPY8i|fc_01546d621137b6cd016a59d3f6bce481958cbc04ed91e34702","toolName":"read","content":[{"type":"text","text":"    // [impl->REQ-DAEMON-2]\n    // [impl->REQ-RC-1]\n    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\n        enum Backing {\n            Quic(Connection),\n            Loopback,\n        }\n        let (backing, remote_id_hex) = {\n            let conns = self.shared.conns.lock().unwrap();\n            let e = conns.get(&conn_id).ok_or_else(|| {\n                io::Error::new(io::ErrorKind::NotFound, format!(\"no such conn {conn_id}\"))\n            })?;\n            let backing = match &e.kind {\n                ConnKind::Quic(c) => Backing::Quic(c.clone()),\n                ConnKind::Loopback => Backing::Loopback,\n            };\n            (backing, e.remote_id_hex.clone())\n        };\n        let shared = Arc::clone(&self.shared);\n        match backing {\n            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed\n            // peer can block on stream flow-control credit the dead peer never grants.\n            Backing::Quic(conn) => self.bounded_block_on(async move {\n                let (send, recv) = conn\n                    .open_bi()\n                    .await\n                    .map_err(|e| io::Error::other(e.to_string()))?;\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Quic(send),\n                    RecvHalf::Quic(recv),\n                    true,\n                ))\n            }),\n            // Bounded like the QUIC arm (REQ-BROKER-ATTACH-JOURNAL-RESILIENT, bug\n            // #16): the loopback open is all in-memory and normally returns\n            // instantly, but a SICK NetHost runtime (saturated/wedged after a\n            // self-update brain-respawn) would otherwise hang this journaled op with\n            // no deadline — killing the per-conn reply thread so every attach\n            // silently deadlines (\"brain IPC read deadline elapsed\") while\n            // non-journaled ops keep working. `bounded_block_on` turns that into a\n            // prompt ordinary error frame; the bound never bites a healthy runtime.\n            // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\n            Backing::Loopback => self.bounded_block_on(async move {\n                // One duplex carries BOTH directions: operator writes reach the\n                // peer's read pump and vice-versa. Split each end into a read +\n                // write half so each row owns exactly its leg.\n                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);\n                let (op_r, op_w) = tokio::io::split(op_io);\n                let (peer_r, peer_w) = tokio::io::split(peer_io);\n                // Register the peer (inbound) row first so it is in the table\n                // before the operator can write its Request onto the wire — the\n                // dispatcher must never miss the stream.\n                register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(peer_w),\n                    RecvHalf::Loopback(peer_r),\n                    false,\n                );\n                Ok(register_stream(\n                    &shared,\n                    conn_id,\n                    &remote_id_hex,\n                    SendHalf::Loopback(op_w),\n                    RecvHalf::Loopback(op_r),\n                    true,\n                ))\n            }),\n        }\n    }\n\n    /// Record that journaled open `(minter, op_id)` minted `stream_id` (inside the\n    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).\n    pub fn record_stream_op(&self, minter: Minter, op_id: u64, stream_id: u64) {\n        self.stream_ops\n            .lock()\n            .unwrap()\n            .insert((minter, op_id), stream_id);\n    }\n\n    /// The stream a journaled open `(minter, op_id)` minted, if still held.\n    pub fn stream_op_id(&self, minter: Minter, op_id: u64) -> Option<u64> {\n        self.stream_ops\n            .lock()\n            .unwrap()\n            .get(&(minter, op_id))\n            .copied()\n    }\n\n    /// A held stream's `(conn_id, remote_id_hex)`.\n    pub fn stream_conn(&self, stream_id: u64) -> Option<(u64, String)> {\n        self.shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(|e| (e.conn_id, e.remote_id_hex.clone()))\n    }\n\n    /// Write `bytes` to a held stream's send side; `finish` closes the send\n    /// side after the write (the peer's read pump then sees a clean end).\n    // [impl->REQ-DAEMON-2]\n    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n        let entry = {\n            let streams = self.shared.streams.lock().unwrap();\n            Arc::clone(streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?)\n        };\n        // Serve-lease gate (ADR-0038 Amendment fix 4): a stream whose\n        // subscriber POISONED has its lease canceled — refusing the send here\n        // is what terminates the paired serve/wire worker at its next\n        // forward instead of leaving an orphan forwarding leg running.\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        if entry.log.lock().unwrap().lease.is_canceled() {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id} lease canceled (subscriber poisoned) — serve leg terminated\"\n            )));\n        }\n        let bytes = bytes.to_vec();\n        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a\n        // black-holed QUIC stream blocks on flow-control the dead peer never grants.\n        // Loopback (in-process) never hangs, so the bound is a no-op for it.\n        self.bounded_block_on(async move {\n            let mut send = entry.send.lock().await;\n            if !bytes.is_empty() {\n                send.write_all(&bytes).await?;\n            }\n…\n        })\n    }\n\n[Showing lines 1649-1783 of 2865. Use :1784 to continue]"}],"details":{"truncation":{"content":"    // [impl->REQ-DAEMON-2]\r\n    // [impl->REQ-RC-1]\r\n    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\r\n        enum Backing {\r\n            Quic(Connection),\r\n            Loopback,\r\n        }\r\n        let (backing, remote_id_hex) = {\r\n            let conns = self.shared.conns.lock().unwrap();\r\n            let e = conns.get(&conn_id).ok_or_else(|| {\r\n                io::Error::new(io::ErrorKind::NotFound, format!(\"no such conn {conn_id}\"))\r\n            })?;\r\n            let backing = match &e.kind {\r\n                ConnKind::Quic(c) => Backing::Quic(c.clone()),\r\n                ConnKind::Loopback => Backing::Loopback,\r\n            };\r\n            (backing, e.remote_id_hex.clone())\r\n        };\r\n        let shared = Arc::clone(&self.shared);\r\n        match backing {\r\n            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed\r\n            // peer can block on stream flow-control credit the dead peer never grants.\r\n            Backing::Quic(conn) => self.bounded_block_on(async move {\r\n                let (send, recv) = conn\r\n                    .open_bi()\r\n                    .await\r\n                    .map_err(|e| io::Error::other(e.to_string()))?;\r\n                Ok(register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Quic(send),\r\n                    RecvHalf::Quic(recv),\r\n                    true,\r\n                ))\r\n            }),\r\n            // Bounded like the QUIC arm (REQ-BROKER-ATTACH-JOURNAL-RESILIENT, bug\r\n            // #16): the loopback open is all in-memory and normally returns\r\n            // instantly, but a SICK NetHost runtime (saturated/wedged after a\r\n            // self-update brain-respawn) would otherwise hang this journaled op with\r\n            // no deadline — killing the per-conn reply thread so every attach\r\n            // silently deadlines (\"brain IPC read deadline elapsed\") while\r\n            // non-journaled ops keep working. `bounded_block_on` turns that into a\r\n            // prompt ordinary error frame; the bound never bites a healthy runtime.\r\n            // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\r\n            Backing::Loopback => self.bounded_block_on(async move {\r\n                // One duplex carries BOTH directions: operator writes reach the\r\n                // peer's read pump and vice-versa. Split each end into a read +\r\n                // write half so each row owns exactly its leg.\r\n                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);\r\n                let (op_r, op_w) = tokio::io::split(op_io);\r\n                let (peer_r, peer_w) = tokio::io::split(peer_io);\r\n                // Register the peer (inbound) row first so it is in the table\r\n                // before the operator can write its Request onto the wire — the\r\n                // dispatcher must never miss the stream.\r\n                register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Loopback(peer_w),\r\n                    RecvHalf::Loopback(peer_r),\r\n                    false,\r\n                );\r\n                Ok(register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Loopback(op_w),\r\n                    RecvHalf::Loopback(op_r),\r\n                    true,\r\n                ))\r\n            }),\r\n        }\r\n    }\r\n\r\n    /// Record that journaled open `(minter, op_id)` minted `stream_id` (inside the\r\n    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).\r\n    pub fn record_stream_op(&self, minter: Minter, op_id: u64, stream_id: u64) {\r\n        self.stream_ops\r\n            .lock()\r\n            .unwrap()\r\n            .insert((minter, op_id), stream_id);\r\n    }\r\n\r\n    /// The stream a journaled open `(minter, op_id)` minted, if still held.\r\n    pub fn stream_op_id(&self, minter: Minter, op_id: u64) -> Option<u64> {\r\n        self.stream_ops\r\n            .lock()\r\n            .unwrap()\r\n            .get(&(minter, op_id))\r\n            .copied()\r\n    }\r\n\r\n    /// A held stream's `(conn_id, remote_id_hex)`.\r\n    pub fn stream_conn(&self, stream_id: u64) -> Option<(u64, String)> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(|e| (e.conn_id, e.remote_id_hex.clone()))\r\n    }\r\n\r\n    /// Write `bytes` to a held stream's send side; `finish` closes the send\r\n    /// side after the write (the peer's read pump then sees a clean end).\r\n    // [impl->REQ-DAEMON-2]\r\n    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\r\n        let entry = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            Arc::clone(streams.get(&stream_id).ok_or_else(|| {\r\n                io::Error::new(\r\n                    io::ErrorKind::NotFound,\r\n                    format!(\"no such stream {stream_id}\"),\r\n                )\r\n            })?)\r\n        };\r\n        // Serve-lease gate (ADR-0038 Amendment fix 4): a stream whose\r\n        // subscriber POISONED has its lease canceled — refusing the send here\r\n        // is what terminates the paired serve/wire worker at its next\r\n        // forward instead of leaving an orphan forwarding leg running.\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        if entry.log.lock().unwrap().lease.is_canceled() {\r\n            return Err(io::Error::other(format!(\r\n                \"stream {stream_id} lease canceled (subscriber poisoned) — serve leg terminated\"\r\n            )));\r\n        }\r\n        let bytes = bytes.to_vec();\r\n        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a\r\n        // black-holed QUIC stream blocks on flow-control the dead peer never grants.\r\n        // Loopback (in-process) never hangs, so the bound is a no-op for it.\r\n        self.bounded_block_on(async move {\r\n            let mut send = entry.send.lock().await;\r\n            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r","truncated":true,"truncatedBy":"lines","totalLines":1217,"totalBytes":6199,"outputLines":135,"outputBytes":6199,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    // [impl->REQ-DAEMON-2]\r\n    // [impl->REQ-RC-1]\r\n    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {\r\n        enum Backing {\r\n            Quic(Connection),\r\n            Loopback,\r\n        }\r\n        let (backing, remote_id_hex) = {\r\n            let conns = self.shared.conns.lock().unwrap();\r\n            let e = conns.get(&conn_id).ok_or_else(|| {\r\n                io::Error::new(io::ErrorKind::NotFound, format!(\"no such conn {conn_id}\"))\r\n            })?;\r\n            let backing = match &e.kind {\r\n                ConnKind::Quic(c) => Backing::Quic(c.clone()),\r\n                ConnKind::Loopback => Backing::Loopback,\r\n            };\r\n            (backing, e.remote_id_hex.clone())\r\n        };\r\n        let shared = Arc::clone(&self.shared);\r\n        match backing {\r\n            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed\r\n            // peer can block on stream flow-control credit the dead peer never grants.\r\n            Backing::Quic(conn) => self.bounded_block_on(async move {\r\n                let (send, recv) = conn\r\n                    .open_bi()\r\n                    .await\r\n                    .map_err(|e| io::Error::other(e.to_string()))?;\r\n                Ok(register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Quic(send),\r\n                    RecvHalf::Quic(recv),\r\n                    true,\r\n                ))\r\n            }),\r\n            // Bounded like the QUIC arm (REQ-BROKER-ATTACH-JOURNAL-RESILIENT, bug\r\n            // #16): the loopback open is all in-memory and normally returns\r\n            // instantly, but a SICK NetHost runtime (saturated/wedged after a\r\n            // self-update brain-respawn) would otherwise hang this journaled op with\r\n            // no deadline — killing the per-conn reply thread so every attach\r\n            // silently deadlines (\"brain IPC read deadline elapsed\") while\r\n            // non-journaled ops keep working. `bounded_block_on` turns that into a\r\n            // prompt ordinary error frame; the bound never bites a healthy runtime.\r\n            // [impl->REQ-BROKER-ATTACH-JOURNAL-RESILIENT]\r\n            Backing::Loopback => self.bounded_block_on(async move {\r\n                // One duplex carries BOTH directions: operator writes reach the\r\n                // peer's read pump and vice-versa. Split each end into a read +\r\n                // write half so each row owns exactly its leg.\r\n                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);\r\n                let (op_r, op_w) = tokio::io::split(op_io);\r\n                let (peer_r, peer_w) = tokio::io::split(peer_io);\r\n                // Register the peer (inbound) row first so it is in the table\r\n                // before the operator can write its Request onto the wire — the\r\n                // dispatcher must never miss the stream.\r\n                register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Loopback(peer_w),\r\n                    RecvHalf::Loopback(peer_r),\r\n                    false,\r\n                );\r\n                Ok(register_stream(\r\n                    &shared,\r\n                    conn_id,\r\n                    &remote_id_hex,\r\n                    SendHalf::Loopback(op_w),\r\n                    RecvHalf::Loopback(op_r),\r\n                    true,\r\n                ))\r\n            }),\r\n        }\r\n    }\r\n\r\n    /// Record that journaled open `(minter, op_id)` minted `stream_id` (inside the\r\n    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).\r\n    pub fn record_stream_op(&self, minter: Minter, op_id: u64, stream_id: u64) {\r\n        self.stream_ops\r\n            .lock()\r\n            .unwrap()\r\n            .insert((minter, op_id), stream_id);\r\n    }\r\n\r\n    /// The stream a journaled open `(minter, op_id)` minted, if still held.\r\n    pub fn stream_op_id(&self, minter: Minter, op_id: u64) -> Option<u64> {\r\n        self.stream_ops\r\n            .lock()\r\n            .unwrap()\r\n            .get(&(minter, op_id))\r\n            .copied()\r\n    }\r\n\r\n    /// A held stream's `(conn_id, remote_id_hex)`.\r\n    pub fn stream_conn(&self, stream_id: u64) -> Option<(u64, String)> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(|e| (e.conn_id, e.remote_id_hex.clone()))\r\n    }\r\n\r\n    /// Write `bytes` to a held stream's send side; `finish` closes the send\r\n    /// side after the write (the peer's read pump then sees a clean end).\r\n    // [impl->REQ-DAEMON-2]\r\n    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\r\n        let entry = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            Arc::clone(streams.get(&stream_id).ok_or_else(|| {\r\n                io::Error::new(\r\n                    io::ErrorKind::NotFound,\r\n                    format!(\"no such stream {stream_id}\"),\r\n                )\r\n            })?)\r\n        };\r\n        // Serve-lease gate (ADR-0038 Amendment fix 4): a stream whose\r\n        // subscriber POISONED has its lease canceled — refusing the send here\r\n        // is what terminates the paired serve/wire worker at its next\r\n        // forward instead of leaving an orphan forwarding leg running.\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        if entry.log.lock().unwrap().lease.is_canceled() {\r\n            return Err(io::Error::other(format!(\r\n                \"stream {stream_id} lease canceled (subscriber poisoned) — serve leg terminated\"\r\n            )));\r\n        }\r\n        let bytes = bytes.to_vec();\r\n        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a\r\n        // black-holed QUIC stream blocks on flow-control the dead peer never grants.\r\n        // Loopback (in-process) never hangs, so the bound is a no-op for it.\r\n        self.bounded_block_on(async move {\r\n            let mut send = entry.send.lock().await;\r\n            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r\n…\n        })\n    }","startLine":1649,"lineNumbers":[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,null,1788,1789]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":6199,"outputLines":135,"outputBytes":6199,"shownRange":{"start":1649,"end":1783},"nextOffset":1784}}},"isError":false,"timestamp":1784271863464}}
{"type":"message","id":"3d8120d3","parentId":"3ca6d03a","timestamp":"2026-07-17T07:04:23.480Z","message":{"role":"toolResult","toolCallId":"call_AMQe6mjkOk0C0CoUsNPMOyu6|fc_01546d621137b6cd016a59d3f6bcd0819580a3f9f76406e122","toolName":"read","content":[{"type":"text","text":"    fn dial_plan(&self) -> DialPlan {\n        DialPlan {\n…\n            local_id: self.local_id,\n            membership: self.membership.clone(),\n            roster_rx: self.roster.lock().unwrap().clone(),\n            self_addr: serde_json::to_value(self.endpoint.addr())\n                .unwrap_or(serde_json::Value::Null),\n        }\n    }\n\n    /// Record that journaled dial `(minter, op_id)` opened `conn_id` (called inside\n    /// the `apply_once` closure, so a concurrent deduped replay always finds it).\n    /// The `minter` matches the journal key the broker built (ADR-0034 namespacing).\n    pub fn record_dial_op(&self, minter: Minter, op_id: u64, conn_id: u64) {\n        self.dial_ops.lock().unwrap().insert((minter, op_id), conn_id);\n    }\n\n    /// The connection a journaled dial `(minter, op_id)` opened, if this broker\n    /// process still holds it (a broker restart drops connections; the journal\n    /// alone survives — see `Broker::dispatch_net_dial`).\n    pub fn dial_op_conn(&self, minter: Minter, op_id: u64) -> Option<u64> {\n        self.dial_ops.lock().unwrap().get(&(minter, op_id)).copied()\n    }\n\n    /// The remote node id (hex) of a held connection.\n    pub fn conn_remote_hex(&self, conn_id: u64) -> Option<String> {\n        self.shared\n            .conns\n            .lock()\n            .unwrap()\n            .get(&conn_id)\n            .map(|e| e.remote_id_hex.clone())\n    }\n\n    /// The subnets the peer of `conn_id` proved current-epoch seed-knowledge of\n    /// at connect (Mesh-D2, REQ-MESH-1). Empty when seed-proof was disabled or\n    /// the conn is gone. The seam the D5 gate swap reads in place of\n    /// `is_trusted(subnet, origin)`; unused by any gate at D2.\n    // [impl->REQ-MESH-1]\n    pub fn conn_proven_subnets(&self, conn_id: u64) -> HashSet<String> {\n        self.shared\n            .conns\n            .lock()\n            .unwrap()\n            .get(&conn_id)\n            .map(|e| e.proven_subnets.clone())\n            .unwrap_or_default()\n    }\n\n    /// The OBSERVED peer address of a held connection (remote id + the\n    /// connection's current path addresses) as opaque JSON — the address-\n    /// seeding write-back the `net-dialed` reply carries (M8 decision 20,\n    /// REQ-CONV-1). `null` when the conn is gone, path-less, or fails to\n    /// serialize: the seed is an optimization, never an error path.\n    // [impl->REQ-CONV-1]\n    pub fn conn_remote_addr_json(&self, conn_id: u64) -> serde_json::Value {\n        let conn = {\n            let conns = self.shared.conns.lock().unwrap();\n            match conns.get(&conn_id) {\n                // A loopback conn has no path to observe — nothing to seed.\n                Some(e) => match &e.kind {\n                    ConnKind::Quic(c) => c.clone(),\n                    ConnKind::Loopback => return serde_json::Value::Null,\n                },\n                None => return serde_json::Value::Null,\n            }\n        };\n        observed_addr_json(&conn)\n    }\n\n    /// Mint (or reuse) the broker's single in-process loopback connection\n    /// (M12 W1.5, B1) and return `(conn_id, remote_id_hex)` — `remote_id_hex` is\n    /// THIS node's own id, so a stream served over it gates same-node (access\n    /// short-circuit Allow). The conn carries NO iroh handshake: its identity is\n    /// broker-minted BY CONSTRUCTION and can never arise from an inbound dial.\n    /// This is what lets a LOCAL `spt rc` ride the byte-identical `serve_attach`\n    /// pump — QUIC self-dial is refused by iroh, so local attach uses this.\n    /// Idempotent: every call returns the same singleton conn (one is all a\n    /// local attach needs); no journaling needed (reuse is the dedup).\n    // [impl->REQ-RC-1]\n    pub fn dial_loopback(&self) -> (u64, String) {\n        let remote_id_hex = self.local_id.to_hex();\n        let mut slot = self.loopback_conn.lock().unwrap();\n        if let Some(id) = *slot {\n            return (id, remote_id_hex);\n        }\n        let id = self.shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n        self.shared.conns.lock().unwrap().insert(\n            id,\n            ConnEntry {\n                kind: ConnKind::Loopback,\n                remote_id_hex: remote_id_hex.clone(),\n…\n            },\n        );\n…\n    }\n\n[Showing lines 1539-1628 of 2865. Use :1629 to continue]"}],"details":{"truncation":{"content":"            local_id: self.local_id,\r\n            membership: self.membership.clone(),\r\n            roster_rx: self.roster.lock().unwrap().clone(),\r\n            self_addr: serde_json::to_value(self.endpoint.addr())\r\n                .unwrap_or(serde_json::Value::Null),\r\n        }\r\n    }\r\n\r\n    /// Record that journaled dial `(minter, op_id)` opened `conn_id` (called inside\r\n    /// the `apply_once` closure, so a concurrent deduped replay always finds it).\r\n    /// The `minter` matches the journal key the broker built (ADR-0034 namespacing).\r\n    pub fn record_dial_op(&self, minter: Minter, op_id: u64, conn_id: u64) {\r\n        self.dial_ops.lock().unwrap().insert((minter, op_id), conn_id);\r\n    }\r\n\r\n    /// The connection a journaled dial `(minter, op_id)` opened, if this broker\r\n    /// process still holds it (a broker restart drops connections; the journal\r\n    /// alone survives — see `Broker::dispatch_net_dial`).\r\n    pub fn dial_op_conn(&self, minter: Minter, op_id: u64) -> Option<u64> {\r\n        self.dial_ops.lock().unwrap().get(&(minter, op_id)).copied()\r\n    }\r\n\r\n    /// The remote node id (hex) of a held connection.\r\n    pub fn conn_remote_hex(&self, conn_id: u64) -> Option<String> {\r\n        self.shared\r\n            .conns\r\n            .lock()\r\n            .unwrap()\r\n            .get(&conn_id)\r\n            .map(|e| e.remote_id_hex.clone())\r\n    }\r\n\r\n    /// The subnets the peer of `conn_id` proved current-epoch seed-knowledge of\r\n    /// at connect (Mesh-D2, REQ-MESH-1). Empty when seed-proof was disabled or\r\n    /// the conn is gone. The seam the D5 gate swap reads in place of\r\n    /// `is_trusted(subnet, origin)`; unused by any gate at D2.\r\n    // [impl->REQ-MESH-1]\r\n    pub fn conn_proven_subnets(&self, conn_id: u64) -> HashSet<String> {\r\n        self.shared\r\n            .conns\r\n            .lock()\r\n            .unwrap()\r\n            .get(&conn_id)\r\n            .map(|e| e.proven_subnets.clone())\r\n            .unwrap_or_default()\r\n    }\r\n\r\n    /// The OBSERVED peer address of a held connection (remote id + the\r\n    /// connection's current path addresses) as opaque JSON — the address-\r\n    /// seeding write-back the `net-dialed` reply carries (M8 decision 20,\r\n    /// REQ-CONV-1). `null` when the conn is gone, path-less, or fails to\r\n    /// serialize: the seed is an optimization, never an error path.\r\n    // [impl->REQ-CONV-1]\r\n    pub fn conn_remote_addr_json(&self, conn_id: u64) -> serde_json::Value {\r\n        let conn = {\r\n            let conns = self.shared.conns.lock().unwrap();\r\n            match conns.get(&conn_id) {\r\n                // A loopback conn has no path to observe — nothing to seed.\r\n                Some(e) => match &e.kind {\r\n                    ConnKind::Quic(c) => c.clone(),\r\n                    ConnKind::Loopback => return serde_json::Value::Null,\r\n                },\r\n                None => return serde_json::Value::Null,\r\n            }\r\n        };\r\n        observed_addr_json(&conn)\r\n    }\r\n\r\n    /// Mint (or reuse) the broker's single in-process loopback connection\r\n    /// (M12 W1.5, B1) and return `(conn_id, remote_id_hex)` — `remote_id_hex` is\r\n    /// THIS node's own id, so a stream served over it gates same-node (access\r\n    /// short-circuit Allow). The conn carries NO iroh handshake: its identity is\r\n    /// broker-minted BY CONSTRUCTION and can never arise from an inbound dial.\r\n    /// This is what lets a LOCAL `spt rc` ride the byte-identical `serve_attach`\r\n    /// pump — QUIC self-dial is refused by iroh, so local attach uses this.\r\n    /// Idempotent: every call returns the same singleton conn (one is all a\r\n    /// local attach needs); no journaling needed (reuse is the dedup).\r\n    // [impl->REQ-RC-1]\r\n    pub fn dial_loopback(&self) -> (u64, String) {\r\n        let remote_id_hex = self.local_id.to_hex();\r\n        let mut slot = self.loopback_conn.lock().unwrap();\r\n        if let Some(id) = *slot {\r\n            return (id, remote_id_hex);\r\n        }\r\n        let id = self.shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\r\n        self.shared.conns.lock().unwrap().insert(\r\n            id,\r\n            ConnEntry {\r\n                kind: ConnKind::Loopback,\r\n                remote_id_hex: remote_id_hex.clone(),\r","truncated":true,"truncatedBy":"lines","totalLines":1327,"totalBytes":4248,"outputLines":90,"outputBytes":4248,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn dial_plan(&self) -> DialPlan {\n        DialPlan {\n…\n            local_id: self.local_id,\r\n            membership: self.membership.clone(),\r\n            roster_rx: self.roster.lock().unwrap().clone(),\r\n            self_addr: serde_json::to_value(self.endpoint.addr())\r\n                .unwrap_or(serde_json::Value::Null),\r\n        }\r\n    }\r\n\r\n    /// Record that journaled dial `(minter, op_id)` opened `conn_id` (called inside\r\n    /// the `apply_once` closure, so a concurrent deduped replay always finds it).\r\n    /// The `minter` matches the journal key the broker built (ADR-0034 namespacing).\r\n    pub fn record_dial_op(&self, minter: Minter, op_id: u64, conn_id: u64) {\r\n        self.dial_ops.lock().unwrap().insert((minter, op_id), conn_id);\r\n    }\r\n\r\n    /// The connection a journaled dial `(minter, op_id)` opened, if this broker\r\n    /// process still holds it (a broker restart drops connections; the journal\r\n    /// alone survives — see `Broker::dispatch_net_dial`).\r\n    pub fn dial_op_conn(&self, minter: Minter, op_id: u64) -> Option<u64> {\r\n        self.dial_ops.lock().unwrap().get(&(minter, op_id)).copied()\r\n    }\r\n\r\n    /// The remote node id (hex) of a held connection.\r\n    pub fn conn_remote_hex(&self, conn_id: u64) -> Option<String> {\r\n        self.shared\r\n            .conns\r\n            .lock()\r\n            .unwrap()\r\n            .get(&conn_id)\r\n            .map(|e| e.remote_id_hex.clone())\r\n    }\r\n\r\n    /// The subnets the peer of `conn_id` proved current-epoch seed-knowledge of\r\n    /// at connect (Mesh-D2, REQ-MESH-1). Empty when seed-proof was disabled or\r\n    /// the conn is gone. The seam the D5 gate swap reads in place of\r\n    /// `is_trusted(subnet, origin)`; unused by any gate at D2.\r\n    // [impl->REQ-MESH-1]\r\n    pub fn conn_proven_subnets(&self, conn_id: u64) -> HashSet<String> {\r\n        self.shared\r\n            .conns\r\n            .lock()\r\n            .unwrap()\r\n            .get(&conn_id)\r\n            .map(|e| e.proven_subnets.clone())\r\n            .unwrap_or_default()\r\n    }\r\n\r\n    /// The OBSERVED peer address of a held connection (remote id + the\r\n    /// connection's current path addresses) as opaque JSON — the address-\r\n    /// seeding write-back the `net-dialed` reply carries (M8 decision 20,\r\n    /// REQ-CONV-1). `null` when the conn is gone, path-less, or fails to\r\n    /// serialize: the seed is an optimization, never an error path.\r\n    // [impl->REQ-CONV-1]\r\n    pub fn conn_remote_addr_json(&self, conn_id: u64) -> serde_json::Value {\r\n        let conn = {\r\n            let conns = self.shared.conns.lock().unwrap();\r\n            match conns.get(&conn_id) {\r\n                // A loopback conn has no path to observe — nothing to seed.\r\n                Some(e) => match &e.kind {\r\n                    ConnKind::Quic(c) => c.clone(),\r\n                    ConnKind::Loopback => return serde_json::Value::Null,\r\n                },\r\n                None => return serde_json::Value::Null,\r\n            }\r\n        };\r\n        observed_addr_json(&conn)\r\n    }\r\n\r\n    /// Mint (or reuse) the broker's single in-process loopback connection\r\n    /// (M12 W1.5, B1) and return `(conn_id, remote_id_hex)` — `remote_id_hex` is\r\n    /// THIS node's own id, so a stream served over it gates same-node (access\r\n    /// short-circuit Allow). The conn carries NO iroh handshake: its identity is\r\n    /// broker-minted BY CONSTRUCTION and can never arise from an inbound dial.\r\n    /// This is what lets a LOCAL `spt rc` ride the byte-identical `serve_attach`\r\n    /// pump — QUIC self-dial is refused by iroh, so local attach uses this.\r\n    /// Idempotent: every call returns the same singleton conn (one is all a\r\n    /// local attach needs); no journaling needed (reuse is the dedup).\r\n    // [impl->REQ-RC-1]\r\n    pub fn dial_loopback(&self) -> (u64, String) {\r\n        let remote_id_hex = self.local_id.to_hex();\r\n        let mut slot = self.loopback_conn.lock().unwrap();\r\n        if let Some(id) = *slot {\r\n            return (id, remote_id_hex);\r\n        }\r\n        let id = self.shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\r\n        self.shared.conns.lock().unwrap().insert(\r\n            id,\r\n            ConnEntry {\r\n                kind: ConnKind::Loopback,\r\n                remote_id_hex: remote_id_hex.clone(),\r\n…\n            },\n        );\n…\n    }","startLine":1535,"lineNumbers":[1535,1536,null,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,null,1630,1631,null,1634]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":4248,"outputLines":90,"outputBytes":4248,"shownRange":{"start":1539,"end":1628},"nextOffset":1629}}},"isError":false,"timestamp":1784271863480}}
{"type":"message","id":"c4e865e9","parentId":"3d8120d3","timestamp":"2026-07-17T07:04:23.520Z","message":{"role":"toolResult","toolCallId":"call_Q81ykFBYn1obfakYOZJmkkRD|fc_01546d621137b6cd016a59d3f6bcf88195a0a23d24a05d3c1a","toolName":"read","content":[{"type":"text","text":"pub struct Broker {\n…\n    /// REQ-SPAWN-COLLISION-GUARD-LIVE-DUP). The broker is the sole spawner post-W3 (the\n    /// rc WMI auto-launch retired by REQ-RC-RECONNECT-TRUTH), so its own spawn choke\n    /// ([`dispatch_spawn`](Self::dispatch_spawn)) is the single-flight authority — no\n    /// perch-record claim (that would add another info.json writer, the exact\n    /// write-stomp this REQ kills). An id is claimed for the duration of one spawn;\n    /// a duplicate wake for a claimed-or-already-live endpoint stands down (no-op ack).\n    wake_inflight: Mutex<HashSet<String>>,\n    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n    /// UPDATE-WEDGE round-4 int gate's observability seam\n    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n    /// physical conn must drive its handler to EOF cleanup within the write\n    /// bound WITHOUT the client dropping — the test watches this count fall.\n    conn_handlers: AtomicUsize,\n}\n\n/// One held meet result for the two-phase join (see [`Broker::pair_holds`]).\nstruct PairHold {\n    subnet: String,\n    addr: EndpointAddr,\n    /// When the wait-for-code window expires (meet + 5 minutes).\n    deadline: Instant,\n}\n\n/// The two-phase join's wait-for-code window: the daemon holds the met address\n/// this long before a code must arrive (ADR-0030 / REQ-JOIN-TWO-PHASE).\nconst PAIR_CODE_WAIT: Duration = Duration::from_secs(5 * 60);\n\n/// Whether a held meet is still within its wait-for-code window\n/// (REQ-JOIN-TWO-PHASE). Pure (the testable core of the timeout).\nfn hold_is_live(deadline: Instant, now: Instant) -> bool {\n    deadline > now\n}\n\n/// Whether a failed code submission should KEEP the held meet for another try\n/// (REQ-JOIN-TWO-PHASE): only a wrong code — the operator re-enters it and the\n/// ceremony re-runs against the same held address (no re-search). Every other\n/// verdict (rate-limited, clock-skew, unknown-subnet, plumbing) is terminal.\nfn fail_keeps_hold(fail: &crate::pairhost::JoinFail) -> bool {\n    matches!(fail, crate::pairhost::JoinFail::WrongCode)\n}\n\n/// Render a [`JoinFail`](crate::pairhost::JoinFail) as the classified\n/// `pair-joined` failure frame the CLI's guided UX reads — single-sourced so the\n/// one-shot join, the meet phase, and the code phase all classify identically.\nfn pair_fail_frame(fail: &crate::pairhost::JoinFail) -> Envelope {\n    use crate::pairhost::JoinFail;\n    let (class, retry) = match fail {\n        JoinFail::NoSeedHolder(_) => (\"no-seed-holder\", None),\n        JoinFail::WrongCode => (\"wrong-code\", None),\n        JoinFail::RateLimited(retry) => (\"rate-limited\", *retry),\n        JoinFail::UnknownSubnet(_) => (\"unknown-subnet\", None),\n        JoinFail::ClockSkew => (\"clock-skew\", None),\n        JoinFail::Other(_) => (\"other\", None),\n    };\n    Envelope::new(\n        KIND_PAIR_JOINED,\n        serde_json::to_value(PairJoinReply {\n            ok: false,\n            class: Some(class.to_string()),\n            retry_after_secs: retry,\n            detail: fail.to_string(),\n        })\n        .expect(\"PairJoinReply serializes\"),\n    )\n}\n\n/// The distinct `(endpoint, install_dir)` pairs whose session runs `adapter`,\n/// from per-session `(endpoint, adapter, install_dir)` rows. Sessions with no\n/// recorded install dir are dropped (a live update needs a target). Pure (no\n/// lock, no I/O) so the live-update endpoint-selection is unit-testable without\n/// constructing real `HostedSession`s. (W3a, ADR-0025)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\nfn select_endpoints_running_adapter(\n    rows: impl IntoIterator<Item = (String, String, Option<std::path::PathBuf>)>,\n    adapter: &str,\n) -> Vec<(String, std::path::PathBuf)> {\n    let mut out: Vec<(String, std::path::PathBuf)> = rows\n        .into_iter()\n        .filter_map(|(endpoint, adp, dir)| {\n            // Parent-match: a profile-composite `<adapter>:<profile>` session belongs\n            // to the update's parent record name (F015B — no exact-match skew).\n            spt_runtime::profile::adapter_parent_matches(&adp, adapter)\n                .then_some(dir.map(|d| (endpoint, d)))?\n        })\n        .collect();\n    out.sort();\n    out.dedup();\n    out\n}\n\nimpl Broker {\n    /// Bind the broker's listening endpoint at the namespaced `name`, with the\n    /// effect journal at the default path (`<spt_home>/daemon-effects.log`).\n    pub fn bind(name: &str) -> io::Result<Arc<Self>> {\n        Self::bind_in(name, default_journal_path())\n    }\n\n    /// Bind with an explicit effect-journal path — the testable seam (tests point\n    /// the broker-owned anchor at a temp file rather than the shared home).\n    pub fn bind_in(name: &str, journal_path: std::path::PathBuf) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net(name, journal_path, None)\n    }\n\n    /// Bind with an already-started broker-owned network host (D4a). Taking the\n    /// host (not its config) keeps a net bind failure separable from a listener\n    /// bind failure — the daemon degrades to a net-less broker rather than\n    /// losing PTY hosting (see `Daemon::run`).\n    // [impl->REQ-DAEMON-2]\n    pub fn bind_in_with_net(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n    ) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net_cap(name, journal_path, net, DEFAULT_LOG_CHUNKS)\n    }\n\n    /// [`bind_in_with_net`](Self::bind_in_with_net) with an explicit output-log\n    /// ring cap (the per-session eviction bound). Production binds at\n    /// [`DEFAULT_LOG_CHUNKS`]; the restoration D4-2 resume harness binds a **tiny**\n    /// cap to force ring eviction and exercise the post-eviction floor clamp (a\n    /// `resume_seq` below the surviving ring floor must resume forward, losing only\n    /// the evicted tail — never gap-erroring the live stream).\n    pub fn bind_in_with_net_cap(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n        log_cap_chunks: usize,\n    ) -> io::Result<Arc<Self>> {\n        let net_cell = std::sync::OnceLock::new();\n        if let Some(host) = net {\n            let _ = net_cell.set(host);\n        }\n        Ok(Arc::new(Broker {\n            listener: LocalSocketTransport::bind(name)?,\n            name: name.to_string(),\n            stop: AtomicBool::new(false),\n            sessions: Arc::new(Mutex::new(HashMap::new())),\n            next_id: AtomicU64::new(1),\n            log_cap_chunks,\n            journal: Arc::new(EffectJournal::open(journal_path)?),\n            net: net_cell,\n            brain_restart: std::sync::OnceLock::new(),\n            pair_holds: Mutex::new(HashMap::new()),\n            wake_inflight: Mutex::new(HashSet::new()),\n            conn_handlers: AtomicUsize::new(0),\n        }))\n    }\n\n    /// Wire the brain supervisor's planned-restart signal (ADR-0018 D3-3) so the\n    /// `KIND_BRAIN_RESTART` verb can drive a real brain-process restart. Called\n    /// once by `Daemon::run` after spawning the supervisor; idempotent by\n    /// `OnceLock` (the first wins). A broker without it accepts the verb but\n    /// reports `honored: false` (no supervisor to respawn the child).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    pub fn set_brain_restart(&self, signal: Arc<crate::brainproc::BrainRestart>) -> bool {\n        self.brain_restart.set(signal).is_ok()\n    }\n\n    /// Attach a network host to a broker that was bound net-less (REQ-DAEMON-9):\n    /// the boot-race self-heal path calls this once the background retry brings\n    /// the net endpoint up, after which the dispatcher + peer pump are spawned.\n    /// Idempotent by `OnceLock` — a second attach is a no-op returning `false`\n    /// (the first host wins); the hot read path (`net()`) never blocks on it.\n    // [impl->REQ-DAEMON-9]\n    pub fn attach_net(&self, host: NetHost) -> bool {\n        self.net.set(host).is_ok()\n    }\n\n    /// The broker-owned exactly-once anchor (for introspection / tests).\n    pub fn journal(&self) -> &Arc<EffectJournal> {\n        &self.journal\n    }\n\n    /// The broker-owned network host, if one is attached (at bind or via the\n    /// REQ-DAEMON-9 self-heal `attach_net`). Lock-free.\n    pub fn net(&self) -> Option<&NetHost> {\n        self.net.get()\n    }\n\n    /// Accept connections forever, handling each on its own thread. The broker\n    /// is the long-lived process; this is its main loop. Per-connection\n    /// threading is half the KH 7.4 isolation stance: one brain's blocking\n    /// dispatch can never stall another's (the per-session half is\n    /// [`Broker::dispatch_spawn`]'s drain/waiter threads).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n        loop {\n            let conn = self.listener.accept()?;\n            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n            // throwaway connection that woke this accept — observe it and return so the\n            // serve thread (and, with it, the listener) tears down instead of leaking.\n            if self.stop.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            let me = Arc::clone(self);\n            thread::spawn(move || {\n                let _ = me.handle_conn(conn);\n            });\n        }\n    }\n\n    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n    /// load that starved the seed stop barrier — REQ-HAZARD-DAEMON-STOP-BARRIER).\n    // [impl->REQ-HAZARD-DAEMON-STOP-BARRIER]\n    pub fn stop(&self) {\n        self.stop.store(true, Ordering::Release);\n        let _ = LocalSocketTransport::connect(&self.name);\n    }\n\n    /// Accept exactly one connection and handle it on a spawned thread, returning\n…\n}\n\n[Showing lines 2599-2813 of 6298. Use :2814 to continue]"}],"details":{"truncation":{"content":"    /// REQ-SPAWN-COLLISION-GUARD-LIVE-DUP). The broker is the sole spawner post-W3 (the\n    /// rc WMI auto-launch retired by REQ-RC-RECONNECT-TRUTH), so its own spawn choke\n    /// ([`dispatch_spawn`](Self::dispatch_spawn)) is the single-flight authority — no\n    /// perch-record claim (that would add another info.json writer, the exact\n    /// write-stomp this REQ kills). An id is claimed for the duration of one spawn;\n    /// a duplicate wake for a claimed-or-already-live endpoint stands down (no-op ack).\n    wake_inflight: Mutex<HashSet<String>>,\n    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n    /// UPDATE-WEDGE round-4 int gate's observability seam\n    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n    /// physical conn must drive its handler to EOF cleanup within the write\n    /// bound WITHOUT the client dropping — the test watches this count fall.\n    conn_handlers: AtomicUsize,\n}\n\n/// One held meet result for the two-phase join (see [`Broker::pair_holds`]).\nstruct PairHold {\n    subnet: String,\n    addr: EndpointAddr,\n    /// When the wait-for-code window expires (meet + 5 minutes).\n    deadline: Instant,\n}\n\n/// The two-phase join's wait-for-code window: the daemon holds the met address\n/// this long before a code must arrive (ADR-0030 / REQ-JOIN-TWO-PHASE).\nconst PAIR_CODE_WAIT: Duration = Duration::from_secs(5 * 60);\n\n/// Whether a held meet is still within its wait-for-code window\n/// (REQ-JOIN-TWO-PHASE). Pure (the testable core of the timeout).\nfn hold_is_live(deadline: Instant, now: Instant) -> bool {\n    deadline > now\n}\n\n/// Whether a failed code submission should KEEP the held meet for another try\n/// (REQ-JOIN-TWO-PHASE): only a wrong code — the operator re-enters it and the\n/// ceremony re-runs against the same held address (no re-search). Every other\n/// verdict (rate-limited, clock-skew, unknown-subnet, plumbing) is terminal.\nfn fail_keeps_hold(fail: &crate::pairhost::JoinFail) -> bool {\n    matches!(fail, crate::pairhost::JoinFail::WrongCode)\n}\n\n/// Render a [`JoinFail`](crate::pairhost::JoinFail) as the classified\n/// `pair-joined` failure frame the CLI's guided UX reads — single-sourced so the\n/// one-shot join, the meet phase, and the code phase all classify identically.\nfn pair_fail_frame(fail: &crate::pairhost::JoinFail) -> Envelope {\n    use crate::pairhost::JoinFail;\n    let (class, retry) = match fail {\n        JoinFail::NoSeedHolder(_) => (\"no-seed-holder\", None),\n        JoinFail::WrongCode => (\"wrong-code\", None),\n        JoinFail::RateLimited(retry) => (\"rate-limited\", *retry),\n        JoinFail::UnknownSubnet(_) => (\"unknown-subnet\", None),\n        JoinFail::ClockSkew => (\"clock-skew\", None),\n        JoinFail::Other(_) => (\"other\", None),\n    };\n    Envelope::new(\n        KIND_PAIR_JOINED,\n        serde_json::to_value(PairJoinReply {\n            ok: false,\n            class: Some(class.to_string()),\n            retry_after_secs: retry,\n            detail: fail.to_string(),\n        })\n        .expect(\"PairJoinReply serializes\"),\n    )\n}\n\n/// The distinct `(endpoint, install_dir)` pairs whose session runs `adapter`,\n/// from per-session `(endpoint, adapter, install_dir)` rows. Sessions with no\n/// recorded install dir are dropped (a live update needs a target). Pure (no\n/// lock, no I/O) so the live-update endpoint-selection is unit-testable without\n/// constructing real `HostedSession`s. (W3a, ADR-0025)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\nfn select_endpoints_running_adapter(\n    rows: impl IntoIterator<Item = (String, String, Option<std::path::PathBuf>)>,\n    adapter: &str,\n) -> Vec<(String, std::path::PathBuf)> {\n    let mut out: Vec<(String, std::path::PathBuf)> = rows\n        .into_iter()\n        .filter_map(|(endpoint, adp, dir)| {\n            // Parent-match: a profile-composite `<adapter>:<profile>` session belongs\n            // to the update's parent record name (F015B — no exact-match skew).\n            spt_runtime::profile::adapter_parent_matches(&adp, adapter)\n                .then_some(dir.map(|d| (endpoint, d)))?\n        })\n        .collect();\n    out.sort();\n    out.dedup();\n    out\n}\n\nimpl Broker {\n    /// Bind the broker's listening endpoint at the namespaced `name`, with the\n    /// effect journal at the default path (`<spt_home>/daemon-effects.log`).\n    pub fn bind(name: &str) -> io::Result<Arc<Self>> {\n        Self::bind_in(name, default_journal_path())\n    }\n\n    /// Bind with an explicit effect-journal path — the testable seam (tests point\n    /// the broker-owned anchor at a temp file rather than the shared home).\n    pub fn bind_in(name: &str, journal_path: std::path::PathBuf) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net(name, journal_path, None)\n    }\n\n    /// Bind with an already-started broker-owned network host (D4a). Taking the\n    /// host (not its config) keeps a net bind failure separable from a listener\n    /// bind failure — the daemon degrades to a net-less broker rather than\n    /// losing PTY hosting (see `Daemon::run`).\n    // [impl->REQ-DAEMON-2]\n    pub fn bind_in_with_net(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n    ) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net_cap(name, journal_path, net, DEFAULT_LOG_CHUNKS)\n    }\n\n    /// [`bind_in_with_net`](Self::bind_in_with_net) with an explicit output-log\n    /// ring cap (the per-session eviction bound). Production binds at\n    /// [`DEFAULT_LOG_CHUNKS`]; the restoration D4-2 resume harness binds a **tiny**\n    /// cap to force ring eviction and exercise the post-eviction floor clamp (a\n    /// `resume_seq` below the surviving ring floor must resume forward, losing only\n    /// the evicted tail — never gap-erroring the live stream).\n    pub fn bind_in_with_net_cap(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n        log_cap_chunks: usize,\n    ) -> io::Result<Arc<Self>> {\n        let net_cell = std::sync::OnceLock::new();\n        if let Some(host) = net {\n            let _ = net_cell.set(host);\n        }\n        Ok(Arc::new(Broker {\n            listener: LocalSocketTransport::bind(name)?,\n            name: name.to_string(),\n            stop: AtomicBool::new(false),\n            sessions: Arc::new(Mutex::new(HashMap::new())),\n            next_id: AtomicU64::new(1),\n            log_cap_chunks,\n            journal: Arc::new(EffectJournal::open(journal_path)?),\n            net: net_cell,\n            brain_restart: std::sync::OnceLock::new(),\n            pair_holds: Mutex::new(HashMap::new()),\n            wake_inflight: Mutex::new(HashSet::new()),\n            conn_handlers: AtomicUsize::new(0),\n        }))\n    }\n\n    /// Wire the brain supervisor's planned-restart signal (ADR-0018 D3-3) so the\n    /// `KIND_BRAIN_RESTART` verb can drive a real brain-process restart. Called\n    /// once by `Daemon::run` after spawning the supervisor; idempotent by\n    /// `OnceLock` (the first wins). A broker without it accepts the verb but\n    /// reports `honored: false` (no supervisor to respawn the child).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    pub fn set_brain_restart(&self, signal: Arc<crate::brainproc::BrainRestart>) -> bool {\n        self.brain_restart.set(signal).is_ok()\n    }\n\n    /// Attach a network host to a broker that was bound net-less (REQ-DAEMON-9):\n    /// the boot-race self-heal path calls this once the background retry brings\n    /// the net endpoint up, after which the dispatcher + peer pump are spawned.\n    /// Idempotent by `OnceLock` — a second attach is a no-op returning `false`\n    /// (the first host wins); the hot read path (`net()`) never blocks on it.\n    // [impl->REQ-DAEMON-9]\n    pub fn attach_net(&self, host: NetHost) -> bool {\n        self.net.set(host).is_ok()\n    }\n\n    /// The broker-owned exactly-once anchor (for introspection / tests).\n    pub fn journal(&self) -> &Arc<EffectJournal> {\n        &self.journal\n    }\n\n    /// The broker-owned network host, if one is attached (at bind or via the\n    /// REQ-DAEMON-9 self-heal `attach_net`). Lock-free.\n    pub fn net(&self) -> Option<&NetHost> {\n        self.net.get()\n    }\n\n    /// Accept connections forever, handling each on its own thread. The broker\n    /// is the long-lived process; this is its main loop. Per-connection\n    /// threading is half the KH 7.4 isolation stance: one brain's blocking\n    /// dispatch can never stall another's (the per-session half is\n    /// [`Broker::dispatch_spawn`]'s drain/waiter threads).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n        loop {\n            let conn = self.listener.accept()?;\n            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n            // throwaway connection that woke this accept — observe it and return so the\n            // serve thread (and, with it, the listener) tears down instead of leaking.\n            if self.stop.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            let me = Arc::clone(self);\n            thread::spawn(move || {\n                let _ = me.handle_conn(conn);\n            });\n        }\n    }\n\n    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n    /// load that starved the seed stop barrier — REQ-HAZARD-DAEMON-STOP-BARRIER).\n    // [impl->REQ-HAZARD-DAEMON-STOP-BARRIER]\n    pub fn stop(&self) {\n        self.stop.store(true, Ordering::Release);\n        let _ = LocalSocketTransport::connect(&self.name);\n    }\n\n    /// Accept exactly one connection and handle it on a spawned thread, returning","truncated":true,"truncatedBy":"lines","totalLines":3700,"totalBytes":10127,"outputLines":215,"outputBytes":10127,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub struct Broker {\n…\n    /// REQ-SPAWN-COLLISION-GUARD-LIVE-DUP). The broker is the sole spawner post-W3 (the\n    /// rc WMI auto-launch retired by REQ-RC-RECONNECT-TRUTH), so its own spawn choke\n    /// ([`dispatch_spawn`](Self::dispatch_spawn)) is the single-flight authority — no\n    /// perch-record claim (that would add another info.json writer, the exact\n    /// write-stomp this REQ kills). An id is claimed for the duration of one spawn;\n    /// a duplicate wake for a claimed-or-already-live endpoint stands down (no-op ack).\n    wake_inflight: Mutex<HashSet<String>>,\n    /// Live per-connection handler-thread tally: incremented on [`handle_conn`]\n    /// entry, decremented (RAII, panic-safe) when the handler returns. The\n    /// UPDATE-WEDGE round-4 int gate's observability seam\n    /// (REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK): a poisoned/aborted\n    /// physical conn must drive its handler to EOF cleanup within the write\n    /// bound WITHOUT the client dropping — the test watches this count fall.\n    conn_handlers: AtomicUsize,\n}\n\n/// One held meet result for the two-phase join (see [`Broker::pair_holds`]).\nstruct PairHold {\n    subnet: String,\n    addr: EndpointAddr,\n    /// When the wait-for-code window expires (meet + 5 minutes).\n    deadline: Instant,\n}\n\n/// The two-phase join's wait-for-code window: the daemon holds the met address\n/// this long before a code must arrive (ADR-0030 / REQ-JOIN-TWO-PHASE).\nconst PAIR_CODE_WAIT: Duration = Duration::from_secs(5 * 60);\n\n/// Whether a held meet is still within its wait-for-code window\n/// (REQ-JOIN-TWO-PHASE). Pure (the testable core of the timeout).\nfn hold_is_live(deadline: Instant, now: Instant) -> bool {\n    deadline > now\n}\n\n/// Whether a failed code submission should KEEP the held meet for another try\n/// (REQ-JOIN-TWO-PHASE): only a wrong code — the operator re-enters it and the\n/// ceremony re-runs against the same held address (no re-search). Every other\n/// verdict (rate-limited, clock-skew, unknown-subnet, plumbing) is terminal.\nfn fail_keeps_hold(fail: &crate::pairhost::JoinFail) -> bool {\n    matches!(fail, crate::pairhost::JoinFail::WrongCode)\n}\n\n/// Render a [`JoinFail`](crate::pairhost::JoinFail) as the classified\n/// `pair-joined` failure frame the CLI's guided UX reads — single-sourced so the\n/// one-shot join, the meet phase, and the code phase all classify identically.\nfn pair_fail_frame(fail: &crate::pairhost::JoinFail) -> Envelope {\n    use crate::pairhost::JoinFail;\n    let (class, retry) = match fail {\n        JoinFail::NoSeedHolder(_) => (\"no-seed-holder\", None),\n        JoinFail::WrongCode => (\"wrong-code\", None),\n        JoinFail::RateLimited(retry) => (\"rate-limited\", *retry),\n        JoinFail::UnknownSubnet(_) => (\"unknown-subnet\", None),\n        JoinFail::ClockSkew => (\"clock-skew\", None),\n        JoinFail::Other(_) => (\"other\", None),\n    };\n    Envelope::new(\n        KIND_PAIR_JOINED,\n        serde_json::to_value(PairJoinReply {\n            ok: false,\n            class: Some(class.to_string()),\n            retry_after_secs: retry,\n            detail: fail.to_string(),\n        })\n        .expect(\"PairJoinReply serializes\"),\n    )\n}\n\n/// The distinct `(endpoint, install_dir)` pairs whose session runs `adapter`,\n/// from per-session `(endpoint, adapter, install_dir)` rows. Sessions with no\n/// recorded install dir are dropped (a live update needs a target). Pure (no\n/// lock, no I/O) so the live-update endpoint-selection is unit-testable without\n/// constructing real `HostedSession`s. (W3a, ADR-0025)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\nfn select_endpoints_running_adapter(\n    rows: impl IntoIterator<Item = (String, String, Option<std::path::PathBuf>)>,\n    adapter: &str,\n) -> Vec<(String, std::path::PathBuf)> {\n    let mut out: Vec<(String, std::path::PathBuf)> = rows\n        .into_iter()\n        .filter_map(|(endpoint, adp, dir)| {\n            // Parent-match: a profile-composite `<adapter>:<profile>` session belongs\n            // to the update's parent record name (F015B — no exact-match skew).\n            spt_runtime::profile::adapter_parent_matches(&adp, adapter)\n                .then_some(dir.map(|d| (endpoint, d)))?\n        })\n        .collect();\n    out.sort();\n    out.dedup();\n    out\n}\n\nimpl Broker {\n    /// Bind the broker's listening endpoint at the namespaced `name`, with the\n    /// effect journal at the default path (`<spt_home>/daemon-effects.log`).\n    pub fn bind(name: &str) -> io::Result<Arc<Self>> {\n        Self::bind_in(name, default_journal_path())\n    }\n\n    /// Bind with an explicit effect-journal path — the testable seam (tests point\n    /// the broker-owned anchor at a temp file rather than the shared home).\n    pub fn bind_in(name: &str, journal_path: std::path::PathBuf) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net(name, journal_path, None)\n    }\n\n    /// Bind with an already-started broker-owned network host (D4a). Taking the\n    /// host (not its config) keeps a net bind failure separable from a listener\n    /// bind failure — the daemon degrades to a net-less broker rather than\n    /// losing PTY hosting (see `Daemon::run`).\n    // [impl->REQ-DAEMON-2]\n    pub fn bind_in_with_net(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n    ) -> io::Result<Arc<Self>> {\n        Self::bind_in_with_net_cap(name, journal_path, net, DEFAULT_LOG_CHUNKS)\n    }\n\n    /// [`bind_in_with_net`](Self::bind_in_with_net) with an explicit output-log\n    /// ring cap (the per-session eviction bound). Production binds at\n    /// [`DEFAULT_LOG_CHUNKS`]; the restoration D4-2 resume harness binds a **tiny**\n    /// cap to force ring eviction and exercise the post-eviction floor clamp (a\n    /// `resume_seq` below the surviving ring floor must resume forward, losing only\n    /// the evicted tail — never gap-erroring the live stream).\n    pub fn bind_in_with_net_cap(\n        name: &str,\n        journal_path: std::path::PathBuf,\n        net: Option<NetHost>,\n        log_cap_chunks: usize,\n    ) -> io::Result<Arc<Self>> {\n        let net_cell = std::sync::OnceLock::new();\n        if let Some(host) = net {\n            let _ = net_cell.set(host);\n        }\n        Ok(Arc::new(Broker {\n            listener: LocalSocketTransport::bind(name)?,\n            name: name.to_string(),\n            stop: AtomicBool::new(false),\n            sessions: Arc::new(Mutex::new(HashMap::new())),\n            next_id: AtomicU64::new(1),\n            log_cap_chunks,\n            journal: Arc::new(EffectJournal::open(journal_path)?),\n            net: net_cell,\n            brain_restart: std::sync::OnceLock::new(),\n            pair_holds: Mutex::new(HashMap::new()),\n            wake_inflight: Mutex::new(HashSet::new()),\n            conn_handlers: AtomicUsize::new(0),\n        }))\n    }\n\n    /// Wire the brain supervisor's planned-restart signal (ADR-0018 D3-3) so the\n    /// `KIND_BRAIN_RESTART` verb can drive a real brain-process restart. Called\n    /// once by `Daemon::run` after spawning the supervisor; idempotent by\n    /// `OnceLock` (the first wins). A broker without it accepts the verb but\n    /// reports `honored: false` (no supervisor to respawn the child).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    pub fn set_brain_restart(&self, signal: Arc<crate::brainproc::BrainRestart>) -> bool {\n        self.brain_restart.set(signal).is_ok()\n    }\n\n    /// Attach a network host to a broker that was bound net-less (REQ-DAEMON-9):\n    /// the boot-race self-heal path calls this once the background retry brings\n    /// the net endpoint up, after which the dispatcher + peer pump are spawned.\n    /// Idempotent by `OnceLock` — a second attach is a no-op returning `false`\n    /// (the first host wins); the hot read path (`net()`) never blocks on it.\n    // [impl->REQ-DAEMON-9]\n    pub fn attach_net(&self, host: NetHost) -> bool {\n        self.net.set(host).is_ok()\n    }\n\n    /// The broker-owned exactly-once anchor (for introspection / tests).\n    pub fn journal(&self) -> &Arc<EffectJournal> {\n        &self.journal\n    }\n\n    /// The broker-owned network host, if one is attached (at bind or via the\n    /// REQ-DAEMON-9 self-heal `attach_net`). Lock-free.\n    pub fn net(&self) -> Option<&NetHost> {\n        self.net.get()\n    }\n\n    /// Accept connections forever, handling each on its own thread. The broker\n    /// is the long-lived process; this is its main loop. Per-connection\n    /// threading is half the KH 7.4 isolation stance: one brain's blocking\n    /// dispatch can never stall another's (the per-session half is\n    /// [`Broker::dispatch_spawn`]'s drain/waiter threads).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    pub fn serve(self: &Arc<Self>) -> io::Result<()> {\n        loop {\n            let conn = self.listener.accept()?;\n            // C1 graceful stop: [`Broker::stop`] raises the latch then dials the\n            // throwaway connection that woke this accept — observe it and return so the\n            // serve thread (and, with it, the listener) tears down instead of leaking.\n            if self.stop.load(Ordering::Acquire) {\n                return Ok(());\n            }\n            let me = Arc::clone(self);\n            thread::spawn(move || {\n                let _ = me.handle_conn(conn);\n            });\n        }\n    }\n\n    /// Gracefully stop the [`serve`](Self::serve) accept loop: raise the latch, then\n    /// dial one throwaway connection so the blocked `accept()` wakes, observes the\n    /// latch, and returns (dropping the listener). Best-effort + idempotent — a second\n    /// call, or a call after the loop already exited, just fails the dial harmlessly.\n    /// Fills the honest gap that `serve` is otherwise unstoppable (the leaked-serve\n    /// load that starved the seed stop barrier — REQ-HAZARD-DAEMON-STOP-BARRIER).\n    // [impl->REQ-HAZARD-DAEMON-STOP-BARRIER]\n    pub fn stop(&self) {\n        self.stop.store(true, Ordering::Release);\n        let _ = LocalSocketTransport::connect(&self.name);\n    }\n\n    /// Accept exactly one connection and handle it on a spawned thread, returning\n…\n}","startLine":2561,"lineNumbers":[2561,null,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,null,4579]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":10127,"outputLines":215,"outputBytes":10127,"shownRange":{"start":2599,"end":2813},"nextOffset":2814}}},"isError":false,"timestamp":1784271863520}}
{"type":"message","id":"02e7cbd3","parentId":"c4e865e9","timestamp":"2026-07-17T07:04:28.127Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Registering lines 900-1070**"},{"type":"toolCall","id":"call_fXVBj2xpPwFo6M3I8Jou2H01|fc_01546d621137b6cd016a59d3faf9a481958f329f0c4b93eeaf","name":"read","arguments":{"i":"Reading transport teardown ownership","path":"crates/spt-daemon/src/nethost.rs","selector":"900-1075"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":10770,"output":56,"cacheRead":61952,"cacheWrite":0,"totalTokens":72778,"reasoningTokens":13,"cost":{"input":0.05385,"output":0.00168,"cacheRead":0.030976,"cacheWrite":0,"total":0.086506}},"stopReason":"toolUse","timestamp":1784271863548,"responseId":"resp_01546d621137b6cd016a59d3f7de208195afee180c460d23aa","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_01546d621137b6cd016a59d3fa42788195a3b88248ef11f8d7","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdP6dlvrOIPVIaSplt2lIZRKnXA62LBCFGvI_A4okP9P9G9vN1vKct6oFQJx68a8T7KeCBsfwl2MtwpkTWp44TKze2kx_KOYXzxkh8YgZKWkR26_y0C3IFN30RAOwU2NqZsPxjo_ZC8vN_iMBVFc1fBxDJ93lwmE2RcmPOM3at8pVLSU8RRkORe2z2GiZeCHkbMyypa5XA2dWUsDtvY7x9usWsTOyMKTmJL50BZ1K8c773lWMdK1dVCIU0ZFI0FGvIY0vO6TLM9XR-U4lK59NjAH4DAudDyW7GCHk2wvOQH6er5fblCWdYFE0FMIm37IbuGObmYijIiHAAbxZg7AIcR8odjYZcC_2MKuxFJcA_Stg4xWVUBIEPFmziw3TyIssMlu539ZZYtKhjWTHCEPK_qyvrYP9Y3hV0nS3UJwXrE75kADOxC8nwZyqs387IdA8TNB986OPLgMxeZAgDOtIWgXP_S4QWCDbYMF9GYsaZsBgnyFxriI-nfXopo23PHyAYHvqlNgdrH39vrxE2Owz0fqUwYZd2bG2F_jOm8JVidnUk_waudHFqGumt5PzGAmDiZqMaMVB7DOlHHn9fh9ri_qLCV20Kx9ci0XV3xsbzaoZ4ep2SjMxFEmqXkuA4uDEYxniJy16DYL3jPBJoN4Bn9jTYQeC9JgD6HnRI9JEcxpQYgDY2L74sh8yb5DlUgDAVOdC-0TUOwXy3c6Yq5AX4c5L5Gtm_tW4u4rFM_2dsa3DfGKOboz4ua2pNDx-MKrBnDDoGgYLCBXTAD66KMFGg5tCUHkKSs1Iyvi07bQET14AwrvmDMF58Nw9ylrmXZy7j8Tfp_ecJOnAuxcUS0vIrpeoe85KcgeQTnVlvDTfDURCRVB3NAl6M979CLjPF6xGfIhP6kauU3BgBv3PoZiCQfnkvmZmy4_tEqp52Noi7ERkapnP2eRsXAjc7OShiy_KHBJSVgwNgS_PUCbUlGS0fIz3A==","summary":[{"type":"summary_text","text":"**Registering lines 900-1070**"}]},{"id":"fc_01546d621137b6cd016a59d3faf9a481958f329f0c4b93eeaf","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading transport teardown ownership\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"900-1075\"}","call_id":"call_fXVBj2xpPwFo6M3I8Jou2H01","name":"read"}]},"duration":4578.7568000108,"ttft":2725.362500011921,"contextSnapshot":{"promptTokens":72722,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fXVBj2xpPwFo6M3I8Jou2H01|fc_01546d621137b6cd016a59d3faf9a481958f329f0c4b93eeaf","toolName":"read","startedAt":"2026-07-17T07:04:28.128Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading transport teardown ownership"},"id":"6e591666","parentId":"02e7cbd3","timestamp":"2026-07-17T07:04:28.128Z"}
{"type":"message","id":"5609e93a","parentId":"6e591666","timestamp":"2026-07-17T07:04:28.156Z","message":{"role":"toolResult","toolCallId":"call_fXVBj2xpPwFo6M3I8Jou2H01|fc_01546d621137b6cd016a59d3faf9a481958f329f0c4b93eeaf","toolName":"read","content":[{"type":"text","text":"impl DialPlan {\n…\n    async fn connect(self, addr: EndpointAddr, stage: &DialStage) -> io::Result<(u64, String)> {\n…\n        let proven = match self.membership {\n            Some(src) => {\n…\n                prove_membership(\n…\n                )\n                .await\n                .ok_or_else(|| {\n                    io::Error::other(\"seed-proof failed: peer is not a subnet member\")\n                })?\n            }\n            None => HashSet::new(),\n        };\n        Ok(register_conn(&self.shared, conn, proven))\n    }\n}\n\n// Local alias so the stage constants read namespaced at the use sites without\n// a wildcard import.\nuse crate::seedproofx as spt_seedproof_stage;\nuse crate::seedproofx::DialStage;\n\nfn register_conn(\n    shared: &Arc<NetShared>,\n    conn: Connection,\n    proven_subnets: HashSet<String>,\n) -> (u64, String) {\n    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())\n        .expect(\"an iroh EndpointId is always a valid ed25519 public key\")\n        .to_hex();\n    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\n    shared.conns.lock().unwrap().insert(\n        id,\n        ConnEntry {\n            kind: ConnKind::Quic(conn.clone()),\n            remote_id_hex: remote_id_hex.clone(),\n            proven_subnets,\n        },\n    );\n\n    // Presence: announce the row BEFORE spawning the closed-watcher, so the\n    // `connected` event can never race behind its own `disconnected` (D4c). The\n    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\n    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\n    // did (id-only / path-less → `null`, nothing worth seeding).\n    let observed = observed_addr_json(&conn);\n    shared\n        .presence\n        .lock()\n        .unwrap()\n        .append_connected(id, &remote_id_hex, observed);\n\n    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\n    // close, conn loss, or our own endpoint close), removes the row — conns\n    // finally LEAVE the table — then appends the `disconnected` event (row\n    // gone before the event is observable).\n    // [impl->REQ-EP-4]\n    let sh_close = Arc::clone(shared);\n    let conn_close = conn.clone();\n    let hex_close = remote_id_hex.clone();\n    tokio::spawn(async move {\n        conn_close.closed().await;\n        sh_close.conns.lock().unwrap().remove(&id);\n        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\n        // — nothing can ever be served on them again, and leaving them makes\n        // every future dispatcher generation replay them (the control-steal\n        // hazard, KNOWN-HAZARDS 7.41). Presence keeps its own log; nothing\n        // else reads a dead conn's stream ring.\n        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n        sh_close\n            .streams\n            .lock()\n            .unwrap()\n            .retain(|_, e| e.conn_id != id);\n        sh_close\n            .presence\n            .lock()\n            .unwrap()\n            .append(PRESENCE_DISCONNECTED, id, &hex_close);\n    });\n\n    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\n    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes\n    // visible here on its first write.) Ends when the connection does.\n    let sh = Arc::clone(shared);\n    let hex = remote_id_hex.clone();\n    tokio::spawn(async move {\n        while let Ok((send, recv)) = conn.accept_bi().await {\n            register_stream(\n                &sh,\n                id,\n                &hex,\n                SendHalf::Quic(send),\n                RecvHalf::Quic(recv),\n                false,\n            );\n        }\n    });\n\n    (id, remote_id_hex)\n}\n\n/// Register a live stream (opened or accepted): mint its broker id and spawn\n/// its read pump. Must run inside the host's runtime context.\n/// `initiated_locally` records the direction fact for the table (true only\n/// from [`NetHost::open_stream`]).\nfn register_stream(\n    shared: &Arc<NetShared>,\n    conn_id: u64,\n    remote_id_hex: &str,\n    send: SendHalf,\n    recv: RecvHalf,\n    initiated_locally: bool,\n) -> u64 {\n    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n    let room = Arc::new(tokio::sync::Notify::new());\n    shared.streams.lock().unwrap().insert(\n        id,\n        Arc::new(StreamEntry {\n            conn_id,\n            remote_id_hex: remote_id_hex.to_string(),\n            initiated_locally,\n            send: tokio::sync::Mutex::new(send),\n            log: Arc::clone(&log),\n            room: Arc::clone(&room),\n            retired: std::sync::atomic::AtomicBool::new(false),\n        }),\n    );\n\n    // The read pump — the stream's single producer. A clean end (peer finish)\n    // and a torn end (conn lost) both finish the log; the buffered chunks stay\n    // replayable either way. The transport differs only at this read leaf\n    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\n    // same `StreamLog`, so nothing above the pump can tell them apart.\n    //\n    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\n    // so before pulling the next chunk the pump WAITS while the undrained ring is\n    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\n    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\n    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\n    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n    tokio::spawn(async move {\n        match recv {\n            RecvHalf::Quic(mut recv) => loop {\n                await_ring_room(&log, &room).await;\n                match recv.read_chunk(STREAM_READ_CHUNK).await {\n                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\n                    Ok(None) | Err(_) => {\n                        log.lock().unwrap().finish();\n                        break;\n                    }\n                }\n            },\n            RecvHalf::Loopback(mut recv) => {\n                let mut buf = vec![0u8; STREAM_READ_CHUNK];\n                loop {\n                    await_ring_room(&log, &room).await;\n                    match recv.read(&mut buf).await {\n                        Ok(0) | Err(_) => {\n                            log.lock().unwrap().finish();\n                            break;\n                        }\n                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\n                    }\n                }\n            }\n        }\n    });\n\n    id\n}\n\n/// Backpressure gate for a retentive (tunnel) read pump: park until the undrained\n/// ring has room below `retentive_cap`, woken by a `drain` on `room`. A no-op for\n/// an ordinary stream (`retentive_cap == 0`). Holds no lock across the await.\n// [impl->REQ-SHELL-4]\nasync fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {\n    loop {\n        {\n            let l = log.lock().unwrap();\n            if l.retentive_cap == 0 || l.ring.len() < l.retentive_cap {\n                return;\n            }\n        }\n…\n    }\n}\n\n[Showing lines 899-1078 of 2865. Use :1079 to continue]"}],"details":{"truncation":{"content":"                )\r\n                .await\r\n                .ok_or_else(|| {\r\n                    io::Error::other(\"seed-proof failed: peer is not a subnet member\")\r\n                })?\r\n            }\r\n            None => HashSet::new(),\r\n        };\r\n        Ok(register_conn(&self.shared, conn, proven))\r\n    }\r\n}\r\n\r\n// Local alias so the stage constants read namespaced at the use sites without\r\n// a wildcard import.\r\nuse crate::seedproofx as spt_seedproof_stage;\r\nuse crate::seedproofx::DialStage;\r\n\r\nfn register_conn(\r\n    shared: &Arc<NetShared>,\r\n    conn: Connection,\r\n    proven_subnets: HashSet<String>,\r\n) -> (u64, String) {\r\n    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())\r\n        .expect(\"an iroh EndpointId is always a valid ed25519 public key\")\r\n        .to_hex();\r\n    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\r\n    shared.conns.lock().unwrap().insert(\r\n        id,\r\n        ConnEntry {\r\n            kind: ConnKind::Quic(conn.clone()),\r\n            remote_id_hex: remote_id_hex.clone(),\r\n            proven_subnets,\r\n        },\r\n    );\r\n\r\n    // Presence: announce the row BEFORE spawning the closed-watcher, so the\r\n    // `connected` event can never race behind its own `disconnected` (D4c). The\r\n    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\r\n    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\r\n    // did (id-only / path-less → `null`, nothing worth seeding).\r\n    let observed = observed_addr_json(&conn);\r\n    shared\r\n        .presence\r\n        .lock()\r\n        .unwrap()\r\n        .append_connected(id, &remote_id_hex, observed);\r\n\r\n    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\r\n    // close, conn loss, or our own endpoint close), removes the row — conns\r\n    // finally LEAVE the table — then appends the `disconnected` event (row\r\n    // gone before the event is observable).\r\n    // [impl->REQ-EP-4]\r\n    let sh_close = Arc::clone(shared);\r\n    let conn_close = conn.clone();\r\n    let hex_close = remote_id_hex.clone();\r\n    tokio::spawn(async move {\r\n        conn_close.closed().await;\r\n        sh_close.conns.lock().unwrap().remove(&id);\r\n        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\r\n        // — nothing can ever be served on them again, and leaving them makes\r\n        // every future dispatcher generation replay them (the control-steal\r\n        // hazard, KNOWN-HAZARDS 7.41). Presence keeps its own log; nothing\r\n        // else reads a dead conn's stream ring.\r\n        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n        sh_close\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .retain(|_, e| e.conn_id != id);\r\n        sh_close\r\n            .presence\r\n            .lock()\r\n            .unwrap()\r\n            .append(PRESENCE_DISCONNECTED, id, &hex_close);\r\n    });\r\n\r\n    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\r\n    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes\r\n    // visible here on its first write.) Ends when the connection does.\r\n    let sh = Arc::clone(shared);\r\n    let hex = remote_id_hex.clone();\r\n    tokio::spawn(async move {\r\n        while let Ok((send, recv)) = conn.accept_bi().await {\r\n            register_stream(\r\n                &sh,\r\n                id,\r\n                &hex,\r\n                SendHalf::Quic(send),\r\n                RecvHalf::Quic(recv),\r\n                false,\r\n            );\r\n        }\r\n    });\r\n\r\n    (id, remote_id_hex)\r\n}\r\n\r\n/// Register a live stream (opened or accepted): mint its broker id and spawn\r\n/// its read pump. Must run inside the host's runtime context.\r\n/// `initiated_locally` records the direction fact for the table (true only\r\n/// from [`NetHost::open_stream`]).\r\nfn register_stream(\r\n    shared: &Arc<NetShared>,\r\n    conn_id: u64,\r\n    remote_id_hex: &str,\r\n    send: SendHalf,\r\n    recv: RecvHalf,\r\n    initiated_locally: bool,\r\n) -> u64 {\r\n    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\r\n    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\r\n    let room = Arc::new(tokio::sync::Notify::new());\r\n    shared.streams.lock().unwrap().insert(\r\n        id,\r\n        Arc::new(StreamEntry {\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            initiated_locally,\r\n            send: tokio::sync::Mutex::new(send),\r\n            log: Arc::clone(&log),\r\n            room: Arc::clone(&room),\r\n            retired: std::sync::atomic::AtomicBool::new(false),\r\n        }),\r\n    );\r\n\r\n    // The read pump — the stream's single producer. A clean end (peer finish)\r\n    // and a torn end (conn lost) both finish the log; the buffered chunks stay\r\n    // replayable either way. The transport differs only at this read leaf\r\n    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\r\n    // same `StreamLog`, so nothing above the pump can tell them apart.\r\n    //\r\n    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\r\n    // so before pulling the next chunk the pump WAITS while the undrained ring is\r\n    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\r\n    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\r\n    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\r\n    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\r\n    tokio::spawn(async move {\r\n        match recv {\r\n            RecvHalf::Quic(mut recv) => loop {\r\n                await_ring_room(&log, &room).await;\r\n                match recv.read_chunk(STREAM_READ_CHUNK).await {\r\n                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\r\n                    Ok(None) | Err(_) => {\r\n                        log.lock().unwrap().finish();\r\n                        break;\r\n                    }\r\n                }\r\n            },\r\n            RecvHalf::Loopback(mut recv) => {\r\n                let mut buf = vec![0u8; STREAM_READ_CHUNK];\r\n                loop {\r\n                    await_ring_room(&log, &room).await;\r\n                    match recv.read(&mut buf).await {\r\n                        Ok(0) | Err(_) => {\r\n                            log.lock().unwrap().finish();\r\n                            break;\r\n                        }\r\n                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n    id\r\n}\r\n\r\n/// Backpressure gate for a retentive (tunnel) read pump: park until the undrained\r\n/// ring has room below `retentive_cap`, woken by a `drain` on `room`. A no-op for\r\n/// an ordinary stream (`retentive_cap == 0`). Holds no lock across the await.\r\n// [impl->REQ-SHELL-4]\r\nasync fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {\r\n    loop {\r\n        {\r\n            let l = log.lock().unwrap();\r\n            if l.retentive_cap == 0 || l.ring.len() < l.retentive_cap {\r\n                return;\r\n            }\r\n        }\r","truncated":true,"truncatedBy":"lines","totalLines":1967,"totalBytes":7143,"outputLines":180,"outputBytes":7143,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl DialPlan {\n…\n    async fn connect(self, addr: EndpointAddr, stage: &DialStage) -> io::Result<(u64, String)> {\n…\n        let proven = match self.membership {\n            Some(src) => {\n…\n                prove_membership(\n…\n                )\r\n                .await\r\n                .ok_or_else(|| {\r\n                    io::Error::other(\"seed-proof failed: peer is not a subnet member\")\r\n                })?\r\n            }\r\n            None => HashSet::new(),\r\n        };\r\n        Ok(register_conn(&self.shared, conn, proven))\r\n    }\r\n}\r\n\r\n// Local alias so the stage constants read namespaced at the use sites without\r\n// a wildcard import.\r\nuse crate::seedproofx as spt_seedproof_stage;\r\nuse crate::seedproofx::DialStage;\r\n\r\nfn register_conn(\r\n    shared: &Arc<NetShared>,\r\n    conn: Connection,\r\n    proven_subnets: HashSet<String>,\r\n) -> (u64, String) {\r\n    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())\r\n        .expect(\"an iroh EndpointId is always a valid ed25519 public key\")\r\n        .to_hex();\r\n    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);\r\n    shared.conns.lock().unwrap().insert(\r\n        id,\r\n        ConnEntry {\r\n            kind: ConnKind::Quic(conn.clone()),\r\n            remote_id_hex: remote_id_hex.clone(),\r\n            proven_subnets,\r\n        },\r\n    );\r\n\r\n    // Presence: announce the row BEFORE spawning the closed-watcher, so the\r\n    // `connected` event can never race behind its own `disconnected` (D4c). The\r\n    // event carries the OBSERVED remote addr (REQ-CONV-1 seed write-back) so a\r\n    // non-blocking submit-dial seeds `peer-addrs.json` like the blocking reply\r\n    // did (id-only / path-less → `null`, nothing worth seeding).\r\n    let observed = observed_addr_json(&conn);\r\n    shared\r\n        .presence\r\n        .lock()\r\n        .unwrap()\r\n        .append_connected(id, &remote_id_hex, observed);\r\n\r\n    // Closed-watcher: the row's exit path. Awaits the connection's end (clean\r\n    // close, conn loss, or our own endpoint close), removes the row — conns\r\n    // finally LEAVE the table — then appends the `disconnected` event (row\r\n    // gone before the event is observable).\r\n    // [impl->REQ-EP-4]\r\n    let sh_close = Arc::clone(shared);\r\n    let conn_close = conn.clone();\r\n    let hex_close = remote_id_hex.clone();\r\n    tokio::spawn(async move {\r\n        conn_close.closed().await;\r\n        sh_close.conns.lock().unwrap().remove(&id);\r\n        // Retirement (ADR-0038 A): a dead conn's stream rows are transport-dead\r\n        // — nothing can ever be served on them again, and leaving them makes\r\n        // every future dispatcher generation replay them (the control-steal\r\n        // hazard, KNOWN-HAZARDS 7.41). Presence keeps its own log; nothing\r\n        // else reads a dead conn's stream ring.\r\n        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n        sh_close\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .retain(|_, e| e.conn_id != id);\r\n        sh_close\r\n            .presence\r\n            .lock()\r\n            .unwrap()\r\n            .append(PRESENCE_DISCONNECTED, id, &hex_close);\r\n    });\r\n\r\n    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\r\n    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes\r\n    // visible here on its first write.) Ends when the connection does.\r\n    let sh = Arc::clone(shared);\r\n    let hex = remote_id_hex.clone();\r\n    tokio::spawn(async move {\r\n        while let Ok((send, recv)) = conn.accept_bi().await {\r\n            register_stream(\r\n                &sh,\r\n                id,\r\n                &hex,\r\n                SendHalf::Quic(send),\r\n                RecvHalf::Quic(recv),\r\n                false,\r\n            );\r\n        }\r\n    });\r\n\r\n    (id, remote_id_hex)\r\n}\r\n\r\n/// Register a live stream (opened or accepted): mint its broker id and spawn\r\n/// its read pump. Must run inside the host's runtime context.\r\n/// `initiated_locally` records the direction fact for the table (true only\r\n/// from [`NetHost::open_stream`]).\r\nfn register_stream(\r\n    shared: &Arc<NetShared>,\r\n    conn_id: u64,\r\n    remote_id_hex: &str,\r\n    send: SendHalf,\r\n    recv: RecvHalf,\r\n    initiated_locally: bool,\r\n) -> u64 {\r\n    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\r\n    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\r\n    let room = Arc::new(tokio::sync::Notify::new());\r\n    shared.streams.lock().unwrap().insert(\r\n        id,\r\n        Arc::new(StreamEntry {\r\n            conn_id,\r\n            remote_id_hex: remote_id_hex.to_string(),\r\n            initiated_locally,\r\n            send: tokio::sync::Mutex::new(send),\r\n            log: Arc::clone(&log),\r\n            room: Arc::clone(&room),\r\n            retired: std::sync::atomic::AtomicBool::new(false),\r\n        }),\r\n    );\r\n\r\n    // The read pump — the stream's single producer. A clean end (peer finish)\r\n    // and a torn end (conn lost) both finish the log; the buffered chunks stay\r\n    // replayable either way. The transport differs only at this read leaf\r\n    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\r\n    // same `StreamLog`, so nothing above the pump can tell them apart.\r\n    //\r\n    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\r\n    // so before pulling the next chunk the pump WAITS while the undrained ring is\r\n    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\r\n    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\r\n    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\r\n    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\r\n    tokio::spawn(async move {\r\n        match recv {\r\n            RecvHalf::Quic(mut recv) => loop {\r\n                await_ring_room(&log, &room).await;\r\n                match recv.read_chunk(STREAM_READ_CHUNK).await {\r\n                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\r\n                    Ok(None) | Err(_) => {\r\n                        log.lock().unwrap().finish();\r\n                        break;\r\n                    }\r\n                }\r\n            },\r\n            RecvHalf::Loopback(mut recv) => {\r\n                let mut buf = vec![0u8; STREAM_READ_CHUNK];\r\n                loop {\r\n                    await_ring_room(&log, &room).await;\r\n                    match recv.read(&mut buf).await {\r\n                        Ok(0) | Err(_) => {\r\n                            log.lock().unwrap().finish();\r\n                            break;\r\n                        }\r\n                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n    id\r\n}\r\n\r\n/// Backpressure gate for a retentive (tunnel) read pump: park until the undrained\r\n/// ring has room below `retentive_cap`, woken by a `drain` on `room`. A no-op for\r\n/// an ordinary stream (`retentive_cap == 0`). Holds no lock across the await.\r\n// [impl->REQ-SHELL-4]\r\nasync fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {\r\n    loop {\r\n        {\r\n            let l = log.lock().unwrap();\r\n            if l.retentive_cap == 0 || l.ring.len() < l.retentive_cap {\r\n                return;\r\n            }\r\n        }\r\n…\n    }\n}","startLine":869,"lineNumbers":[869,null,879,null,888,889,null,891,null,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,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,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,null,1080,1081]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":7143,"outputLines":180,"outputBytes":7143,"shownRange":{"start":899,"end":1078},"nextOffset":1079}}},"isError":false,"timestamp":1784271868156}}
{"type":"message","id":"776bbc43","parentId":"5609e93a","timestamp":"2026-07-17T07:04:42.665Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing thread cleanup issues**\n\n**Identifying blocked reader threads causing hangs**"},{"type":"toolCall","id":"call_a6rMKT8WAzZzylcxqY41a6WN|fc_01546d621137b6cd016a59d407b27c8195a017a1f4fcb64b34","name":"read","arguments":{"i":"Reading brain connection ownership","path":"crates/spt-daemon/src/brain.rs","selector":"230-325,1430-1510,1710-1785"}},{"type":"toolCall","id":"call_oNcj6vfitEhfUDnPlRD3FcjI|fc_01546d621137b6cd016a59d407b29881959c5adb8bc5b2226c","name":"read","arguments":{"i":"Reading dispatch worker construction","path":"crates/spt-daemon/src/dispatch.rs","selector":"560-1045"}},{"type":"toolCall","id":"call_piUATHK2g1Pxh9FloeACTpnp|fc_01546d621137b6cd016a59d407b2a08195be36b51c3521763d","name":"read","arguments":{"i":"Reading attach serve loops","path":"crates/spt-daemon/src/attach.rs","selector":"270-570"}},{"type":"toolCall","id":"call_UvzIDK3ZmZV9E7PbYPLfRJVY|fc_01546d621137b6cd016a59d407b2a481959f1922e2a79b6c0f","name":"grep","arguments":{"i":"Finding split brain use","pattern":"split\\(|BrainConn::Split|net_stream_subscribe|read_event_until","path":"crates/spt-daemon/src/attach.rs; crates/spt-daemon/src/dispatch.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":6451,"output":416,"cacheRead":68096,"cacheWrite":0,"totalTokens":74963,"reasoningTokens":200,"cost":{"input":0.032255000000000006,"output":0.01248,"cacheRead":0.034048,"cacheWrite":0,"total":0.078783}},"stopReason":"toolUse","timestamp":1784271868205,"responseId":"resp_01546d621137b6cd016a59d3fc902c8195bec5df83e6b096a6","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_01546d621137b6cd016a59d3ff27b88195b216dc3dec57cb3b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdQHQpfGHtL5GnIM18bAw9n6GJQnsoy-P9XgOy0St4rDHoDHA_ENTZBCvbK12FFx5w1_vyu2jpStKYsZVXgSYEuf2_jUgd_EzHbwrIOTBtHamCbtbkCST826rJg9xK0QzCZVvGqA7vT2GzHdYIfSaA53g9p1Pd6LhWfxunQJU6zIuU7eA-BWhUjqxyLorntf-eWa9BDXVEQ_tCXRd9bnd9fwhwKu_7yHIq_tD0634RP-auW0IZGvAjetkuR6sAHPkRQ0mf_vOMV0MU_Kr1JYLrjRrnuiK3DJ2pilt2OYD74ElSXqzlHwhnHP1eRVTaXWTmc92y8PP-qwoYSTzy9s4y5Xjx-tWoWm8BkPI20PSCjameMvBER42n8VATMvamtAkaxJypRybpFPw-vIFZxEwjU66-s1vYqft54PUK0FD4lq7yCGYuJUGVMGxbpb7Nf2-MnTAepcr_EiSXsV6JsVgVPjQZRehkB-OYUHZ-1idW3buthcgrYDyZAg1263xXVBK17e8BLo4obF8Q6d-akrRRlvrCU0xxVJqT3nM4-mjMJlfx45eqZryQeFpAPb6r6XLzEIL4fb-d_VFQ1FYUujktVe1Ipkwiu_5rMX1iScHEJAfFK3t6sIkURoPwTN9g1rqCqCiAtkLVnZQRvGMQzK6Gp4vd8hyIQXTV5C1Ix4EO-tUYcqIB26kWrRQRIMF9lngngDyYWQPbTNjCc6BavHlXuVgcLRgLBb2lI4EIcd7RHyMfZe52AyuDvuGZkETIuJwrxyNzPkOOywcwATSej__HchNltPMPFjLPvW0WFMrg12OoJ8njI0-WHobk1MX6-nAnuDcspC2akFJG_KVune6TPXZDJF2j7f-SGIMoidO2uruIICUDgmZcHNDFaQanaWOspcUbZsSnYWu45zqDdQ4g2betv144BnS2vsRfg-B_WcEHo7sRP_LETVux9BgG1pMNIMem9_ODtDl3OYcFw8QWefbGSXOk50WvXHlmypLRToBHKR6gTzD-RQ5qfKjgyZq_q3DBHvYWlONZMZuwLJUsP5h5RWXZMzdYRolmyefqLTIjhEGdAIP0kUgE1bGLlfYU7tufX4bQNJdlh1bEnt87PLCiGAYFo4Z8QXyvKYO2LpSp0X0aXzEFB1V-JOBXheRrlF20xSjneUoSaqVeGaZ8kwRiSh-Zd1imRY4XNl_ovzNg8paMqcdfQQVCIfzrKb1DdCNyS7eY4a_1Fykq0SFxmGjkX54Dv38e44B3pPmXcEKkhjHiB1sRckF41-qAUx1Kbtdweth8DkIOOWrG61A0j03i-C3_0hNjfIr-gRhGpDcIE2nA3zX95do3ywxn_AR9PQWBlqxx1cDjmz44RjPmGSlOpKa9IrqholAqRuISgxSSZ3gDN1hXmjf8YTdIw0nqloHnGfYQNjAm71o5rE-Uoj66jG1-5J9K6DNT5KX5CMtiJH60LZONBuC8Kp8qeY8VUtfDRjjAvHh9lsv09VdytITd1FEfUZCoQ4HmsItsH63GlL4eQKWVWSF9u4slT7lJfBrNTdx1_C6GOt9p_eLJucaucBvVClvew6Yvy23cxfV0n9crB6UN6tMvI5Crw834LWVJLw0itJt2MPUYZ8Zw6be4cYiSjEhqn8tUQA5D0dWfZ5T1ZMwAHe2lEsszcFOZbO342jEx2mcJ3S-Xtd1RhTF4zj7ajszGZq331qEoivjjxr1N1UJffXEf7HDpIMNgzp4zWUtqiuYck_aArBIdBVO8Nx5jkaqLOj2jN_t7Xtagt9kgQ5Fo8jqyNm6OC8ZdbPQyBPe-e-KNrFNRnyKnTqFBftZhzz5zLI8IZJYKWZHHycx35I7YrQtOFgkhQvXWoSr26mUEiAlElS4TxD0Mzz9-daR9NEHm3_E4yLXY3zZi5usWkjgUBfcjmiRfbn-VV6hPeVXlmahSvQmb_fXEOSGn7rHNn-XDcfFUBrm6S-FUf3zeHs5aGadaICQQ9dabZONd6wwb_kGD_1SOGdRi_A5r9ta9aj7QK0FP_0gUahlyFN7yosk1HnXv486b25nzS4U-c2A5T3CE2I_lngTHIXdK9ZpPTftb1eO2qLdu0BxBX45iHp56HBtQg3DJkOIfpQm42WR4ceujjleQLMkWWHFWyX8cFOzzBIPbZbGj5RKHogUnAXsahkHOr2ZJe5dd4uqw5J0mZDo3g7M_9VbMFhV8WmFlPzb2xQVuYGaqyioBTL7LPaipQBLoOr-TtGeBVXfFzJ561kvxgF5SCFNbYQH0md6d7Jbj9HKck0x8gQfUw=","summary":[{"type":"summary_text","text":"**Analyzing thread cleanup issues**"},{"type":"summary_text","text":"**Identifying blocked reader threads causing hangs**"}]},{"id":"fc_01546d621137b6cd016a59d407b27c8195a017a1f4fcb64b34","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain connection ownership\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"230-325,1430-1510,1710-1785\"}","call_id":"call_a6rMKT8WAzZzylcxqY41a6WN","name":"read"},{"id":"fc_01546d621137b6cd016a59d407b29881959c5adb8bc5b2226c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatch worker construction\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"560-1045\"}","call_id":"call_oNcj6vfitEhfUDnPlRD3FcjI","name":"read"},{"id":"fc_01546d621137b6cd016a59d407b2a08195be36b51c3521763d","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading attach serve loops\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":\"270-570\"}","call_id":"call_piUATHK2g1Pxh9FloeACTpnp","name":"read"},{"id":"fc_01546d621137b6cd016a59d407b2a481959f1922e2a79b6c0f","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding split brain use\",\"pattern\":\"split\\\\(|BrainConn::Split|net_stream_subscribe|read_event_until\",\"path\":\"crates/spt-daemon/src/attach.rs; crates/spt-daemon/src/dispatch.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_UvzIDK3ZmZV9E7PbYPLfRJVY","name":"grep"}]},"duration":14458.82270000875,"ttft":2982.1134000122547,"contextSnapshot":{"promptTokens":74547,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_a6rMKT8WAzZzylcxqY41a6WN|fc_01546d621137b6cd016a59d407b27c8195a017a1f4fcb64b34","toolName":"read","startedAt":"2026-07-17T07:04:42.665Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading brain connection ownership"},"id":"11592cae","parentId":"776bbc43","timestamp":"2026-07-17T07:04:42.665Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_oNcj6vfitEhfUDnPlRD3FcjI|fc_01546d621137b6cd016a59d407b29881959c5adb8bc5b2226c","toolName":"read","startedAt":"2026-07-17T07:04:42.665Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatch worker construction"},"id":"576dec22","parentId":"11592cae","timestamp":"2026-07-17T07:04:42.665Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_piUATHK2g1Pxh9FloeACTpnp|fc_01546d621137b6cd016a59d407b2a08195be36b51c3521763d","toolName":"read","startedAt":"2026-07-17T07:04:42.665Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Reading attach serve loops"},"id":"01f18716","parentId":"576dec22","timestamp":"2026-07-17T07:04:42.665Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UvzIDK3ZmZV9E7PbYPLfRJVY|fc_01546d621137b6cd016a59d407b2a481959f1922e2a79b6c0f","toolName":"grep","startedAt":"2026-07-17T07:04:42.665Z","args":{"path":"crates/spt-daemon/src/attach.rs; crates/spt-daemon/src/dispatch.rs"},"intent":"Finding split brain use"},"id":"1d164484","parentId":"01f18716","timestamp":"2026-07-17T07:04:42.665Z"}
{"type":"message","id":"e1337671","parentId":"1d164484","timestamp":"2026-07-17T07:04:42.680Z","message":{"role":"toolResult","toolCallId":"call_a6rMKT8WAzZzylcxqY41a6WN|fc_01546d621137b6cd016a59d407b27c8195a017a1f4fcb64b34","toolName":"read","content":[{"type":"text","text":"impl BrainConn {\n…\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn split_with_reader(stream: Stream) -> BrainConn {\n        let (mut recv, send) = stream.split();\n        let (tx, frames) = mpsc::channel();\n        let reader = thread::Builder::new()\n            .name(\"pump-ipc-reader\".to_string())\n            .spawn(move || {\n                eprintln!(\"PUMP_IPC_READER: spawned\");\n                loop {\n                    let framed = read_frame(&mut recv);\n                    let was_err = framed.is_err();\n                    // Receiver gone (Brain dropped) → stop reading.\n                    if tx.send(framed).is_err() {\n                        break;\n                    }\n                    // A transport error (conn closed / hung up) is terminal —\n                    // forward it once (above), then exit so the thread never\n                    // spins on a dead half.\n                    if was_err {\n                        break;\n                    }\n                }\n                eprintln!(\"PUMP_IPC_READER: exited\");\n            })\n            .expect(\"spawn pump-ipc-reader thread\");\n        BrainConn::Split {\n            send,\n            frames,\n            _reader: reader,\n        }\n    }\n}\n\n/// A live brain attached to a broker.\npub struct Brain {\n    conn: BrainConn,\n    generation: u64,\n    gen_start_ms: u64,\n    session_id: Option<u64>,\n    /// The next output sequence still needed (resume cursor / dedup floor).\n    next_seq: u64,\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// Durability across a handoff is the logic layer's job (a re-attaching\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n    net_cursors: std::collections::HashMap<u64, u64>,\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// `None` until subscribed (or the first event arrives).\n    presence_cursor: Option<u64>,\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n    /// continuity state a cold-started brain rebuilds from the broker via\n    /// [`Brain::resume_sessions`], replacing the retired single-session\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\n    /// never a real gap). A session **absent** here uses the strict single-session\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\n    session_cursors: std::collections::HashMap<u64, u64>,\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n    /// into the reader-thread form so the deadline can be honored over a channel\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\n    io_timeout: Option<Duration>,\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\n    /// replay + strict reject-gap, entirely unchanged.\n    baseline_next_output: bool,\n}\n\nimpl Brain {\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::Whole(conn),\n            generation: 0,\n…\n        })\n    }\n…\n    pub fn net_stream_send(\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n…\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n    /// The dispatcher calls this when a serve loop runs to completion, so no\n    /// future dispatcher generation rediscovers and replays the stream\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n    /// broker to cycle onto the new binary).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n        self.send(\n            KIND_NET_STREAM_RETIRE,\n            serde_json::to_value(NetStreamRetireReq { stream_id })\n                .expect(\"NetStreamRetireReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(reply.removed);\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, read instead of peeking ring seq 0 (which a\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\n    /// stream but no opener is pinned (no newline yet / capture gave up).\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n    /// the dispatcher falls back to the ring peek).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n        self.send(\n            KIND_NET_STREAM_OPENER,\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\n                .expect(\"NetStreamOpenerReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return match reply.opener_b64 {\n                        Some(b64) => decode_bytes(&b64)\n                            .map(Some)\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\n                        None => Ok(None),\n                    };\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\n    /// freshly-swapped binary, with `start-reason=update`. `spt update apply`\n    /// calls this after swapping the binary on disk — the seam that makes a\n    /// routine update actually run new code without a manual bounce, every\n    /// broker-held endpoint untouched at the process level.\n    ///\n…\n    fn subscribe_with(\n…\n    ) -> io::Result<()> {\n…\n        // BEFORE it is `attached` (the frame is dropped by the `if attached` gate\n        // but the snap-above cursor still advanced past K); `attach_as(sid, 0)`\n        // then re-subscribes from 0, and without this reset the broker's re-send of\n        // seq K arrives below the advanced cursor and is deduped → seq K never\n        // reaches the operator viewport → a forward gap. Resetting to `from_seq`\n        // makes the re-subscribe re-deliver everything from its floor. A cold-start\n        // brain (empty map — e.g. the production dispatch serve brain) keeps the\n        // legacy `next_seq` path; this only arms in resume mode.\n        // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n        if !self.session_cursors.is_empty() {\n            self.session_cursors.insert(session_id, from_seq);\n        }\n        self.send(\n            KIND_SUBSCRIBE,\n            serde_json::to_value(SubscribeReq {\n                session_id,\n                from_seq,\n                intent,\n                by,\n                gen,\n            })\n            .expect(\"SubscribeReq serializes\"),\n        )\n    }\n\n    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n        let env = Envelope::new(kind, payload);\n        match &mut self.conn {\n            BrainConn::Whole(stream) => write_frame(stream, &env),\n            BrainConn::Split { send, .. } => write_frame(send, &env),\n        }\n    }\n\n    /// Read one framed [`Envelope`], bounded by an optional wall-clock\n    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):\n    ///\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\n    ///   `io_timeout`, so callers always pass `None` here).\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n        match &mut self.conn {\n            BrainConn::Whole(stream) => read_frame(stream),\n            BrainConn::Split { frames, .. } => match deadline {\n                Some(dl) => {\n                    // Total-wait bound: each recv is the REMAINING budget to the\n                    // call's entry deadline, so unrelated frames dripping in\n                    // cannot reset it (the per-read-timeout wedge the ruling\n                    // forbids). A non-positive remaining → recv_timeout(ZERO),\n                    // which still drains an already-queued frame before timing out.\n                    let remaining = dl.saturating_duration_since(Instant::now());\n                    match frames.recv_timeout(remaining) {\n                        Ok(framed) => framed,\n                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\n                            io::ErrorKind::TimedOut,\n                            \"brain IPC read deadline elapsed\",\n                        )),\n                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(\n                            io::ErrorKind::UnexpectedEof,\n                            \"brain IPC reader thread ended (conn closed)\",\n                        )),\n                    }\n                }\n                None => frames.recv().map_err(|_| {\n                    io::Error::new(\n                        io::ErrorKind::UnexpectedEof,\n                        \"brain IPC reader thread ended (conn closed)\",\n                    )\n                })?,\n            },\n        }\n    }\n…\n}"}],"details":{"displayContent":{"text":"impl BrainConn {\n…\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn split_with_reader(stream: Stream) -> BrainConn {\n        let (mut recv, send) = stream.split();\n        let (tx, frames) = mpsc::channel();\n        let reader = thread::Builder::new()\n            .name(\"pump-ipc-reader\".to_string())\n            .spawn(move || {\n                eprintln!(\"PUMP_IPC_READER: spawned\");\n                loop {\n                    let framed = read_frame(&mut recv);\n                    let was_err = framed.is_err();\n                    // Receiver gone (Brain dropped) → stop reading.\n                    if tx.send(framed).is_err() {\n                        break;\n                    }\n                    // A transport error (conn closed / hung up) is terminal —\n                    // forward it once (above), then exit so the thread never\n                    // spins on a dead half.\n                    if was_err {\n                        break;\n                    }\n                }\n                eprintln!(\"PUMP_IPC_READER: exited\");\n            })\n            .expect(\"spawn pump-ipc-reader thread\");\n        BrainConn::Split {\n            send,\n            frames,\n            _reader: reader,\n        }\n    }\n}\n\n/// A live brain attached to a broker.\npub struct Brain {\n    conn: BrainConn,\n    generation: u64,\n    gen_start_ms: u64,\n    session_id: Option<u64>,\n    /// The next output sequence still needed (resume cursor / dedup floor).\n    next_seq: u64,\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// Durability across a handoff is the logic layer's job (a re-attaching\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n    net_cursors: std::collections::HashMap<u64, u64>,\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// `None` until subscribed (or the first event arrives).\n    presence_cursor: Option<u64>,\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n    /// continuity state a cold-started brain rebuilds from the broker via\n    /// [`Brain::resume_sessions`], replacing the retired single-session\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\n    /// never a real gap). A session **absent** here uses the strict single-session\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\n    session_cursors: std::collections::HashMap<u64, u64>,\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n    /// into the reader-thread form so the deadline can be honored over a channel\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\n    io_timeout: Option<Duration>,\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\n    /// replay + strict reject-gap, entirely unchanged.\n    baseline_next_output: bool,\n}\n\nimpl Brain {\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::Whole(conn),\n            generation: 0,\n…\n        })\n    }\n…\n    pub fn net_stream_send(\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n…\n            }\n        }\n    }\n\n    /// List the streams the broker holds (D4b) — how a re-attaching brain\n    /// finds streams (incl. peer-initiated ones) that arrived while it was down.\n    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n        self.send(KIND_NET_STREAMS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n    /// The dispatcher calls this when a serve loop runs to completion, so no\n    /// future dispatcher generation rediscovers and replays the stream\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n    /// broker to cycle onto the new binary).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n        self.send(\n            KIND_NET_STREAM_RETIRE,\n            serde_json::to_value(NetStreamRetireReq { stream_id })\n                .expect(\"NetStreamRetireReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(reply.removed);\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, read instead of peeking ring seq 0 (which a\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\n    /// stream but no opener is pinned (no newline yet / capture gave up).\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n    /// the dispatcher falls back to the ring peek).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n        self.send(\n            KIND_NET_STREAM_OPENER,\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\n                .expect(\"NetStreamOpenerReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return match reply.opener_b64 {\n                        Some(b64) => decode_bytes(&b64)\n                            .map(Some)\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\n                        None => Ok(None),\n                    };\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\n    /// freshly-swapped binary, with `start-reason=update`. `spt update apply`\n    /// calls this after swapping the binary on disk — the seam that makes a\n    /// routine update actually run new code without a manual bounce, every\n    /// broker-held endpoint untouched at the process level.\n    ///\n…\n    fn subscribe_with(\n…\n    ) -> io::Result<()> {\n…\n        // BEFORE it is `attached` (the frame is dropped by the `if attached` gate\n        // but the snap-above cursor still advanced past K); `attach_as(sid, 0)`\n        // then re-subscribes from 0, and without this reset the broker's re-send of\n        // seq K arrives below the advanced cursor and is deduped → seq K never\n        // reaches the operator viewport → a forward gap. Resetting to `from_seq`\n        // makes the re-subscribe re-deliver everything from its floor. A cold-start\n        // brain (empty map — e.g. the production dispatch serve brain) keeps the\n        // legacy `next_seq` path; this only arms in resume mode.\n        // [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]\n        if !self.session_cursors.is_empty() {\n            self.session_cursors.insert(session_id, from_seq);\n        }\n        self.send(\n            KIND_SUBSCRIBE,\n            serde_json::to_value(SubscribeReq {\n                session_id,\n                from_seq,\n                intent,\n                by,\n                gen,\n            })\n            .expect(\"SubscribeReq serializes\"),\n        )\n    }\n\n    fn send(&mut self, kind: &str, payload: serde_json::Value) -> io::Result<()> {\n        let env = Envelope::new(kind, payload);\n        match &mut self.conn {\n            BrainConn::Whole(stream) => write_frame(stream, &env),\n            BrainConn::Split { send, .. } => write_frame(send, &env),\n        }\n    }\n\n    /// Read one framed [`Envelope`], bounded by an optional wall-clock\n    /// `deadline`. The carrier dispatch (REQ-HAZARD-PUMP-IPC-DEADLINE):\n    ///\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\n    ///   `io_timeout`, so callers always pass `None` here).\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n        match &mut self.conn {\n            BrainConn::Whole(stream) => read_frame(stream),\n            BrainConn::Split { frames, .. } => match deadline {\n                Some(dl) => {\n                    // Total-wait bound: each recv is the REMAINING budget to the\n                    // call's entry deadline, so unrelated frames dripping in\n                    // cannot reset it (the per-read-timeout wedge the ruling\n                    // forbids). A non-positive remaining → recv_timeout(ZERO),\n                    // which still drains an already-queued frame before timing out.\n                    let remaining = dl.saturating_duration_since(Instant::now());\n                    match frames.recv_timeout(remaining) {\n                        Ok(framed) => framed,\n                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\n                            io::ErrorKind::TimedOut,\n                            \"brain IPC read deadline elapsed\",\n                        )),\n                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(\n                            io::ErrorKind::UnexpectedEof,\n                            \"brain IPC reader thread ended (conn closed)\",\n                        )),\n                    }\n                }\n                None => frames.recv().map_err(|_| {\n                    io::Error::new(\n                        io::ErrorKind::UnexpectedEof,\n                        \"brain IPC reader thread ended (conn closed)\",\n                    )\n                })?,\n            },\n        }\n    }\n…\n}","startLine":225,"lineNumbers":[225,null,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,null,334,335,null,1402,null,1408,null,1425,1426,null,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,1696,null,1703,null,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,null,1793]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"}}},"isError":false,"timestamp":1784271882680}}
{"type":"message","id":"28f65cf5","parentId":"e1337671","timestamp":"2026-07-17T07:04:42.685Z","message":{"role":"toolResult","toolCallId":"call_UvzIDK3ZmZV9E7PbYPLfRJVY|fc_01546d621137b6cd016a59d407b2a481959f1922e2a79b6c0f","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## attach.rs\n 313|) -> io::Result<AttachServeOutcome> {\n*314|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 315|    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n 316|    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 317|    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n## dispatch.rs\n 663|    let mut brain = connect(broker_name)?;\n*664|    brain.net_stream_subscribe(stream_id, floor_seq)?;\n 665|    let mut buf: Vec<u8> = Vec::new();\n 666|    loop {\n 667|        match brain.read_event()? {\n...\n 990|    let policy = RegistryGatePolicy::load();\n*991|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 992|        return DispatchOutcome::Failed(e.to_string());\n 993|    }\n 994|    let mut decoder = RegistryDecoder::new();\n...\n 1075|        .unwrap_or_default();\n*1076|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 1077|        return DispatchOutcome::Failed(e.to_string());\n 1078|    }\n 1079|    let mut decoder = NotifDecoder::new();\n...\n 1148|) -> DispatchOutcome {\n*1149|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 1150|        return DispatchOutcome::Failed(e.to_string());\n 1151|    }\n 1152|    let mut decoder = WanDecoder::new();"}],"details":{"scopePath":"crates/spt-daemon/src/attach.rs, crates/spt-daemon/src/dispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":5,"fileCount":2,"files":["crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/attach.rs","count":1},{"path":"crates/spt-daemon/src/dispatch.rs","count":4}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## attach.rs\n 313│) -> io::Result<AttachServeOutcome> {\n*314│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 315│    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n 316│    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 317│    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n## dispatch.rs\n  663│    let mut brain = connect(broker_name)?;\n *664│    brain.net_stream_subscribe(stream_id, floor_seq)?;\n  665│    let mut buf: Vec<u8> = Vec::new();\n  666│    loop {\n  667│        match brain.read_event()? {\n     │...\n  990│    let policy = RegistryGatePolicy::load();\n *991│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n  992│        return DispatchOutcome::Failed(e.to_string());\n  993│    }\n  994│    let mut decoder = RegistryDecoder::new();\n     │...\n 1075│        .unwrap_or_default();\n*1076│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 1077│        return DispatchOutcome::Failed(e.to_string());\n 1078│    }\n 1079│    let mut decoder = NotifDecoder::new();\n     │...\n 1148│) -> DispatchOutcome {\n*1149│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 1150│        return DispatchOutcome::Failed(e.to_string());\n 1151│    }\n 1152│    let mut decoder = WanDecoder::new();"},"isError":false,"timestamp":1784271882685}}
{"type":"message","id":"0ed8eb8d","parentId":"28f65cf5","timestamp":"2026-07-17T07:04:42.690Z","message":{"role":"toolResult","toolCallId":"call_piUATHK2g1Pxh9FloeACTpnp|fc_01546d621137b6cd016a59d407b2a08195be36b51c3521763d","toolName":"read","content":[{"type":"text","text":"fn handle_attach_request(\n…\n) -> io::Result<Option<AttachServeOutcome>> {\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/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // This worker's attach GENERATION (ADR-0038 Amendment fix 6) — set when\n    // its Request lands; every terminal release carries it so a STALE worker\n    // (an older generation) can never clear a replacement controller.\n    let mut serve_gen: u64 = 0;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n        gen: req_gen,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            req_gen,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n            &mut serve_gen,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, serve_gen, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n            // ── Wire → session: requests, input, resize. ───────────────────\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Request {\n                            session_id,\n                            from_seq,\n                            intent: req_intent,\n                            // The dispatcher already resolved endpoint_id→session\n                            // (passed as remote_session) BEFORE this loop; ignore\n                            // the wire copy here (a mid-loop re-resolve would swallow\n                            // interleaved attach records — see remote_session docs).\n                            endpoint_id: _,\n                            gen: req_gen,\n                        } => {\n                            if let Some(out) = handle_attach_request(\n                                brain,\n                                &mut wire,\n                                stream_id,\n                                endpoint,\n                                origin_node,\n                                remote_session,\n                                session_id,\n                                from_seq,\n                                req_intent,\n                                req_gen,\n                                &mut intent,\n                                &mut attached_session,\n                                &mut attached,\n                                &mut serve_gen,\n                            )? {\n                                return Ok(out);\n                            }\n                        }\n                        AttachRecord::Input { data_b64, op_id }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                            match role {\n                                // Confirmed controller — exactly-once at the PTY\n                                // (the operator-minted id rides the B5 journal). NO-ACK\n                                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE): this path is\n                                // fire-and-forward (the ack is discarded here) and can\n                                // flood N input frames on this one conn; an acked write\n                                // back would fill the return direction and deadlock the\n                                // broker's per-conn handler. Dedup still holds at the\n                                // broker (op_id applied-set).\n                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n                                Some(ServeRole::Controller) => {\n                                    // The op_id is operator-minted (rc attach OpMinter),\n                                    // so it carries the `rc` journal tag (ADR-0034).\n                                    brain.send_effect_no_ack(\n                                        MintedOp::new(Minter::Rc, op_id),\n                                        &bytes,\n                                    )?\n                                }\n                                // Outcome not yet known — buffer; flushed on a\n                                // controller confirmation, discarded on busy/viewer.\n                                None => pending_input.push((op_id, bytes)),\n                                // A viewer never reaches here (intent gate).\n                                Some(ServeRole::Viewer) => {}\n                            }\n                        }\n                        AttachRecord::Resize { rows, cols }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            // Controller → PTY resize (controller-EXCLUSIVE; the\n                            // broker rejects it on a viewer's stream). A viewer's\n                            // Resize is dropped here by the intent gate too.\n                            let _ = brain.resize(rows, cols);\n                        }\n                        // Input/Resize before Request, a viewer's input/resize,\n                        // or target-direction records echoed back: noise — ignore.\n                        _ => {}\n                    }\n                }\n            }\n\n            // ── Broker → loop: the resolved controller/viewer role. ─────────\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…\n            }\n…\n        }\n    }\n}\n\n[Showing lines 269-573 of 718. Use :574 to continue]"}],"details":{"truncation":{"content":"}\n\n/// Serve one attach stream against this brain's broker, blocking until the\n/// session exits, the operator detaches, or the gate refuses.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // This worker's attach GENERATION (ADR-0038 Amendment fix 6) — set when\n    // its Request lands; every terminal release carries it so a STALE worker\n    // (an older generation) can never clear a replacement controller.\n    let mut serve_gen: u64 = 0;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n        gen: req_gen,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            req_gen,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n            &mut serve_gen,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, serve_gen, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n            // ── Wire → session: requests, input, resize. ───────────────────\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Request {\n                            session_id,\n                            from_seq,\n                            intent: req_intent,\n                            // The dispatcher already resolved endpoint_id→session\n                            // (passed as remote_session) BEFORE this loop; ignore\n                            // the wire copy here (a mid-loop re-resolve would swallow\n                            // interleaved attach records — see remote_session docs).\n                            endpoint_id: _,\n                            gen: req_gen,\n                        } => {\n                            if let Some(out) = handle_attach_request(\n                                brain,\n                                &mut wire,\n                                stream_id,\n                                endpoint,\n                                origin_node,\n                                remote_session,\n                                session_id,\n                                from_seq,\n                                req_intent,\n                                req_gen,\n                                &mut intent,\n                                &mut attached_session,\n                                &mut attached,\n                                &mut serve_gen,\n                            )? {\n                                return Ok(out);\n                            }\n                        }\n                        AttachRecord::Input { data_b64, op_id }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                            match role {\n                                // Confirmed controller — exactly-once at the PTY\n                                // (the operator-minted id rides the B5 journal). NO-ACK\n                                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE): this path is\n                                // fire-and-forward (the ack is discarded here) and can\n                                // flood N input frames on this one conn; an acked write\n                                // back would fill the return direction and deadlock the\n                                // broker's per-conn handler. Dedup still holds at the\n                                // broker (op_id applied-set).\n                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n                                Some(ServeRole::Controller) => {\n                                    // The op_id is operator-minted (rc attach OpMinter),\n                                    // so it carries the `rc` journal tag (ADR-0034).\n                                    brain.send_effect_no_ack(\n                                        MintedOp::new(Minter::Rc, op_id),\n                                        &bytes,\n                                    )?\n                                }\n                                // Outcome not yet known — buffer; flushed on a\n                                // controller confirmation, discarded on busy/viewer.\n                                None => pending_input.push((op_id, bytes)),\n                                // A viewer never reaches here (intent gate).\n                                Some(ServeRole::Viewer) => {}\n                            }\n                        }\n                        AttachRecord::Resize { rows, cols }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            // Controller → PTY resize (controller-EXCLUSIVE; the\n                            // broker rejects it on a viewer's stream). A viewer's\n                            // Resize is dropped here by the intent gate too.\n                            let _ = brain.resize(rows, cols);\n                        }\n                        // Input/Resize before Request, a viewer's input/resize,\n                        // or target-direction records echoed back: noise — ignore.\n                        _ => {}\n                    }\n                }\n            }\n\n            // ── Broker → loop: the resolved controller/viewer role. ─────────\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);","truncated":true,"truncatedBy":"lines","totalLines":450,"totalBytes":16807,"outputLines":305,"outputBytes":16807,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn handle_attach_request(\n…\n) -> io::Result<Option<AttachServeOutcome>> {\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/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // This worker's attach GENERATION (ADR-0038 Amendment fix 6) — set when\n    // its Request lands; every terminal release carries it so a STALE worker\n    // (an older generation) can never clear a replacement controller.\n    let mut serve_gen: u64 = 0;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n        gen: req_gen,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            req_gen,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n            &mut serve_gen,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, serve_gen, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n            // ── Wire → session: requests, input, resize. ───────────────────\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Request {\n                            session_id,\n                            from_seq,\n                            intent: req_intent,\n                            // The dispatcher already resolved endpoint_id→session\n                            // (passed as remote_session) BEFORE this loop; ignore\n                            // the wire copy here (a mid-loop re-resolve would swallow\n                            // interleaved attach records — see remote_session docs).\n                            endpoint_id: _,\n                            gen: req_gen,\n                        } => {\n                            if let Some(out) = handle_attach_request(\n                                brain,\n                                &mut wire,\n                                stream_id,\n                                endpoint,\n                                origin_node,\n                                remote_session,\n                                session_id,\n                                from_seq,\n                                req_intent,\n                                req_gen,\n                                &mut intent,\n                                &mut attached_session,\n                                &mut attached,\n                                &mut serve_gen,\n                            )? {\n                                return Ok(out);\n                            }\n                        }\n                        AttachRecord::Input { data_b64, op_id }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            let bytes = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                            match role {\n                                // Confirmed controller — exactly-once at the PTY\n                                // (the operator-minted id rides the B5 journal). NO-ACK\n                                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE): this path is\n                                // fire-and-forward (the ack is discarded here) and can\n                                // flood N input frames on this one conn; an acked write\n                                // back would fill the return direction and deadlock the\n                                // broker's per-conn handler. Dedup still holds at the\n                                // broker (op_id applied-set).\n                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n                                Some(ServeRole::Controller) => {\n                                    // The op_id is operator-minted (rc attach OpMinter),\n                                    // so it carries the `rc` journal tag (ADR-0034).\n                                    brain.send_effect_no_ack(\n                                        MintedOp::new(Minter::Rc, op_id),\n                                        &bytes,\n                                    )?\n                                }\n                                // Outcome not yet known — buffer; flushed on a\n                                // controller confirmation, discarded on busy/viewer.\n                                None => pending_input.push((op_id, bytes)),\n                                // A viewer never reaches here (intent gate).\n                                Some(ServeRole::Viewer) => {}\n                            }\n                        }\n                        AttachRecord::Resize { rows, cols }\n                            if attached && intent != AttachIntent::Viewer =>\n                        {\n                            // Controller → PTY resize (controller-EXCLUSIVE; the\n                            // broker rejects it on a viewer's stream). A viewer's\n                            // Resize is dropped here by the intent gate too.\n                            let _ = brain.resize(rows, cols);\n                        }\n                        // Input/Resize before Request, a viewer's input/resize,\n                        // or target-direction records echoed back: noise — ignore.\n                        _ => {}\n                    }\n                }\n            }\n\n            // ── Broker → loop: the resolved controller/viewer role. ─────────\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…\n            }\n…\n        }\n    }\n}","startLine":238,"lineNumbers":[238,null,253,null,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,null,578,null,626,627,628]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":718,"totalBytes":16807,"outputLines":305,"outputBytes":16807,"shownRange":{"start":269,"end":573},"nextOffset":574}}},"isError":false,"timestamp":1784271882690}}
{"type":"message","id":"2906aad7","parentId":"0ed8eb8d","timestamp":"2026-07-17T07:04:42.699Z","message":{"role":"toolResult","toolCallId":"call_oNcj6vfitEhfUDnPlRD3FcjI|fc_01546d621137b6cd016a59d407b29881959c5adb8bc5b2226c","toolName":"read","content":[{"type":"text","text":"pub fn run_dispatch_loop(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        for s in reply.streams {\n…\n            thread::spawn(move || {\n…\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n…\n                    {\n…\n                        eprintln!(\n…\n                            dispatch_event(\n…\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}\n\n[Showing lines 559-1048 of 1558. Use :1049 to continue]"}],"details":{"truncation":{"content":"                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }","truncated":true,"truncatedBy":"lines","totalLines":1000,"totalBytes":21670,"outputLines":490,"outputBytes":21670,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn run_dispatch_loop(\n…\n) -> io::Result<()> {\n…\n    while !stop.load(Ordering::Relaxed) {\n…\n        for s in reply.streams {\n…\n            thread::spawn(move || {\n…\n                let breaker_window = match &outcome {\n                    DispatchOutcome::Failed(msg)\n…\n                    {\n…\n                        eprintln!(\n…\n                            dispatch_event(\n…\n                                None,\n                                attempts,\n                                conn_id,\n                                \"breaker-trip\",\n                                &format!(\"window_ms={}\", window.as_millis()),\n                            )\n                        );\n                        window\n                    }\n                    _ => Duration::ZERO,\n                };\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now(), breaker_window),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Bounded redispatch worker pool (ADR-0038 Amendment fix 7): a cold stream\n/// table claims at most this many rows per poll tick.\nconst MAX_DISPATCH_WORKERS: usize = 8;\n\n/// PURE pool gate (Amendment fix 7): may the loop claim another row right\n/// now? The cold-enumeration break rides this — a refresh with a large\n/// backlog claims at most [`MAX_DISPATCH_WORKERS`] rows per poll, the rest\n/// wait for the next tick (batched, never one thread per row).\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn pool_has_room(active: usize) -> bool {\n    active < MAX_DISPATCH_WORKERS\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // ONLY the old broker's explicit unknown-verb answer falls through\n        // to the ring peek (ADR-0038 Amendment fix 1) — the pre-Amendment\n        // broad Err(_) arm caught transport timeout/EOF/poison too, and each\n        // retry then manufactured a throwaway replay subscriber against a\n        // possibly-wedged conn (the v0.34 stall engine's first gear).\n        // [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\n        Err(e) if is_unsupported_verb(&e) => {}\n        // Everything else is a transient: Failed -> classified bounded\n        // requeue, never a second replay subscriber.\n        Err(e) => return Err(e),\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n/// The endpoint identity a stream's opener names, where the family carries\n/// one (Amendment fix 9 — hertz's live catch on stream 7058 could not bind\n/// the stalled stream to an endpoint; this key is what makes the next catch\n/// a bound stream→endpoint proof). Families without an endpoint concept\n/// answer `None` honestly.\n// [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\nfn line_endpoint(family: StreamFamily, line: &serde_json::Value) -> Option<String> {\n    let key = match family {\n        StreamFamily::Attach => \"endpoint_id\",\n        StreamFamily::Rest | StreamFamily::Xfer => \"endpoint\",\n        StreamFamily::ShellLink => \"owner\",\n        StreamFamily::WanMsg => \"target\",\n        _ => return None,\n    };\n    line.get(key)\n        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(str::to_string)\n}\n\n/// A worker's report: the outcome plus the telemetry keys it resolved\n/// (family + endpoint, once the opener classified) — Amendment fix 9.\nstruct WorkerReport {\n    outcome: DispatchOutcome,\n    family: Option<StreamFamily>,\n    endpoint: Option<String>,\n}\n\nimpl WorkerReport {\n    fn bare(outcome: DispatchOutcome) -> WorkerReport {\n        WorkerReport {\n            outcome,\n            family: None,\n            endpoint: None,\n        }\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> WorkerReport {\n    if take_injected_transient() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected transient worker-start failure (test valve)\".into(),\n        ));\n    }\n    if take_injected_seatblocked() {\n        return WorkerReport::bare(DispatchOutcome::Failed(\n            \"injected subscriber busy failure (test valve)\".into(),\n        ));\n    }\n    let line = match first_line(broker_name, stream_id, floor_seq) {\n        Ok(Some(v)) => v,\n        Ok(None) => return WorkerReport::bare(DispatchOutcome::Unknown),\n        // Pre-serve terminal exclusion (Amendment fix 6 rider): a row that\n        // vanished between enumeration and this query (retired by a sibling\n        // worker's Served report, or conn-swept) is TERMINAL — report Served\n        // so the claim closes now, instead of burning three transient\n        // retries against a stream that no longer exists.\n        // [impl->REQ-SERVE-OWNERSHIP-GENERATION]\n        Err(e) if e.to_string().contains(\"no such stream\") => {\n            return WorkerReport::bare(DispatchOutcome::Served(\n                \"terminal pre-serve: row already gone\".into(),\n            ))\n        }\n        Err(e) => return WorkerReport::bare(DispatchOutcome::Failed(e.to_string())),\n    };\n    let family = classify_first_line(&line);\n    let endpoint = line_endpoint(family, &line);\n\n    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n    // row's exchange is over — the operator detached — and replaying it would\n    // take the live controller (pre-existing backlog + brain-down-detach\n    // orderings, which the Served-report path can never see). Report Served\n    // WITHOUT serving: the loop's completion path retires the row, so the\n    // backlog also drains from the table. Finished request/reply rows fall\n    // through — their FIN is protocol, not completion (see\n    // `finished_row_is_terminal`).\n    if finished_row_is_terminal(family, finished) {\n        return WorkerReport {\n            outcome: DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into()),\n            family: Some(family),\n            endpoint,\n        };\n    }\n\n    let mut brain = match connect(broker_name) {\n        Ok(b) => b,\n        Err(e) => {\n            return WorkerReport {\n                outcome: DispatchOutcome::Failed(e.to_string()),\n                family: Some(family),\n                endpoint,\n            }\n        }\n    };\n\n    let outcome = match family {\n        StreamFamily::Sync => {\n            let cs = match ContextStore::open_or_init() {\n                Ok(cs) => cs,\n                Err(e) => {\n                    return WorkerReport {\n                        outcome: DispatchOutcome::Failed(e.to_string()),\n                        family: Some(family),\n                        endpoint,\n                    }\n                }\n            };\n            let policy = SyncPolicy::load();\n            match serve_sync(\n                &mut brain,\n                stream_id,\n                origin,\n                0,\n                &cs,\n                &policy,\n                &paths.scratch,\n            ) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Update => {\n            let cache = ReleaseCache::open(&paths.releases);\n            let roster = RosterStore::load();\n            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),\n        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),\n        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n        StreamFamily::Attach => {\n            // The gate subject is the session's OWNING endpoint, resolved\n            // from the broker's table (the spawn's label) — never from wire\n            // bytes. An unlabeled or unknown session refuses, fail-closed.\n            //\n            // Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): a REMOTE operator\n            // cannot know our local session id, so it names the ENDPOINT. When the\n            // Request carries `endpoint_id`, THAT is the gate subject — but only if\n            // we actually host a live session for it (D5 session-presence gate;\n            // D6: a stale registry row that named a gone endpoint resolves to no\n            // session → refuse cleanly, never a false attach). The endpoint id is\n            // WHICH endpoint the operator asks for (client-supplied, like Xfer's\n            // `endpoint`); the origin subject stays the handshake identity. The\n            // LOCAL path sends no endpoint_id and its session_id → endpoint mapping\n            // is unchanged.\n            // `endpoint` is the gate subject; `remote_session` carries the\n            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n            // before serve_attach subscribes — never mid-serve, which would swallow\n            // interleaved attach records) and `None` on the local path.\n            let (endpoint, remote_session) = match line\n                .get(\"endpoint_id\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n            {\n                Some(ep) => match crate::attach::resolve_local_session(&mut brain, ep) {\n                    Some(sid) => (Some(ep.to_string()), Some(sid)),\n                    None => (None, None),\n                },\n                None => {\n                    let session_id = line.get(\"session_id\").and_then(|v| v.as_u64());\n                    let endpoint = session_id.and_then(|sid| {\n                        brain.sessions().ok().and_then(|reply| {\n                            reply\n                                .sessions\n                                .iter()\n                                .find(|s| s.session_id == sid && !s.endpoint.is_empty())\n                                .map(|s| s.endpoint.clone())\n                        })\n                    });\n                    (endpoint, None)\n                }\n            };\n            match endpoint {\n                // Seed the serve loop with the DURABLE opener Request\n                // (ADR-0038 B): after a ring roll the wire replay no longer\n                // carries the Request record, so the serve would idle forever\n                // waiting for one. The pinned opener IS that record — hand it\n                // over so reconstruction resumes the same operator stream.\n                // Subscribe from the ring FLOOR: a rolled ring's chunk 0 is\n                // gone, and a below-floor cursor trips the brain's gap check.\n                // [impl->REQ-STREAM-OPENER-DURABLE]\n                Some(endpoint) => match serve_attach(\n                    &mut brain,\n                    broker_name,\n                    stream_id,\n                    &endpoint,\n                    origin,\n                    floor_seq,\n                    remote_session,\n                    serde_json::from_value(line.clone()).ok(),\n                ) {\n                    Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                    Err(e) => DispatchOutcome::Failed(e.to_string()),\n                },\n                None => {\n                    // Refuse politely: finish our send side, claim stays.\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"attach refused: unlabeled/unknown session\".into())\n                }\n            }\n        }\n        StreamFamily::Xfer => {\n            // The wire names whose whitelist gates (the rule set); the origin\n            // it is checked against stays handshake-proven. Blank refuses.\n            let endpoint = line\n                .get(\"endpoint\")\n                .and_then(|v| v.as_str())\n                .filter(|e| !e.is_empty())\n                .map(str::to_string);\n            match endpoint {\n                Some(endpoint) => {\n                    let root = spt_store::xfer::transfers_dir();\n                    let progress = paths.scratch.join(\"xfer-progress\");\n                    match serve_xfer(\n                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n                    ) {\n                        Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                        Err(e) => DispatchOutcome::Failed(e.to_string()),\n                    }\n                }\n                None => {\n                    let _ = brain.net_stream_send(stream_id, &[], None, true);\n                    DispatchOutcome::Served(\"xfer refused: no endpoint named\".into())\n                }\n            }\n        }\n        StreamFamily::Rest => {\n            // The wire names whose whitelist gates (the xfer posture); the\n            // origin stays handshake-proven. The serve applies the event\n            // through the LOCAL transition host — echo gate, wake effects,\n            // and shell cascade run on this node, where the instance lives.\n            // [impl->REQ-INST-6]\n            let knob = crate::config::DaemonConfig::load().auto_suspend_after_ms;\n            match crate::resthost::serve_rest(&mut brain, stream_id, origin, knob) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ShellLink => {\n            // The wire names the owner whose whitelist gates AND whose shell\n            // tree resolves (owner exclusivity = resolution scope); the\n            // action runs against the LOCAL owlery — perch, manifest, spool,\n            // and binary all live here, the shell's node.\n            // [impl->REQ-SHELL-2]\n            match crate::linkhost::serve_shell_link(&mut brain, stream_id, origin, &paths.owlery) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::ServeProbe => {\n            // No gate (serveprobe module docs): the answer is public\n            // serve-state for a named subnet, read fresh from this node's\n            // own stores — the QUIC handshake is the only subject.\n            // [impl->REQ-SUBNET-5]\n            match crate::serveprobe::serve_subnet_probe(&mut brain, stream_id) {\n                Ok(out) => DispatchOutcome::Served(format!(\"{out:?}\")),\n                Err(e) => DispatchOutcome::Failed(e.to_string()),\n            }\n        }\n        StreamFamily::Unknown => DispatchOutcome::Unknown,\n    };\n    WorkerReport {\n        outcome,\n        family: Some(family),\n        endpoint,\n    }\n}\n\n/// Registry feed arm: decode `RegistryUpdate` lines, merge each batch into\n/// the daemon's [`RegistryHost`] under the fail-closed gate (member subnet ∧\n/// origin trusted in that subnet), until the peer finishes.\n///\n/// An admitted batch may carry **attention flips** (REQ-INST-3): a peer\n/// instance of an id observed transitioning to `Active` — its node's daemon\n/// just gave it a driver, so a *locally hosted* instance of the same id\n/// rests warm (CONTEXT: driving `ling@laptop` makes `ling@desktop`\n/// dormant). The rest edge's transition echo is gate-armed\n/// ([`crate::resting::arm_transition_echo`]) — the endpoint's own pulse\n/// loop runs the bounded summarizer call, never this worker (the KH 7.4\n/// non-blocking posture: N flips in one feed are N gate files, not N LLM\n/// calls serialized here).\n// [impl->REQ-INST-7]\nfn serve_registry_feed(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin: &str,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    let policy = RegistryGatePolicy::load();\n    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n        return DispatchOutcome::Failed(e.to_string());\n    }\n    let mut decoder = RegistryDecoder::new();\n    let mut merged = 0usize;\n    loop {\n        match brain.read_event() {\n            Ok(BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            }) if sid == stream_id => {\n                let records = decoder.push(&bytes);\n                if records.is_empty() {\n                    continue;\n                }\n                // Split the feed into instance rows and node-level labels\n                // (REQ-SUBNET-3) — each goes through its own gated apply path.\n                let mut updates: Vec<RegistryUpdate> = Vec::new();\n                let mut labels: Vec<NodeLabelUpdate> = Vec::new();\n                for rec in records {\n                    match rec {\n                        RegistryFeedRecord::Instance(u) => updates.push(u),\n                        RegistryFeedRecord::NodeLabel(n) => labels.push(n),\n                    }\n                }\n                if !labels.is_empty() {\n                    merged += registry.apply_node_labels(origin, &labels, &policy).len();\n                }\n                if updates.is_empty() {\n                    continue;\n                }\n                let (verdicts, flips) = registry.apply_feed_flips(origin, &updates, &policy);\n                merged += verdicts.len();\n                // Attention shift (D9-2): only ids this node actually hosts\n                // (a perch with a record); everything else is a no-op here.\n                // [impl->REQ-INST-3]\n                for id in flips {\n                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n                    if local.join(\"info.json\").exists() {\n                        if let Err(e) = crate::resting::daemon_rest_event(\n                            &id,\n                            crate::resting::RestEvent::AttentionShift,\n                            None,\n                        ) {\n                            eprintln!(\"REST_SHIFT_FAIL:{id}:{e}\");\n                        }\n                    }\n                }\n            }\n            Ok(BrokerEvent::NetStreamEof { stream_id: sid, .. }) if sid == stream_id => {\n                return DispatchOutcome::Served(format!(\"registry feed: {merged} record(s)\"));\n            }\n            Ok(BrokerEvent::Error { message }) => return DispatchOutcome::Failed(message),\n            Ok(_) => {}\n            Err(e) => return DispatchOutcome::Failed(e.to_string()),\n        }\n    }\n}","startLine":429,"lineNumbers":[429,null,435,null,455,null,464,null,498,null,548,549,null,551,null,554,null,556,null,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,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,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,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,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":21670,"outputLines":490,"outputBytes":21670,"shownRange":{"start":559,"end":1048},"nextOffset":1049}}},"isError":false,"timestamp":1784271882699}}
{"type":"message","id":"0c790f4e","parentId":"2906aad7","timestamp":"2026-07-17T07:05:50.601Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing thread count and connection churn**\n\n**Tracing BrokerConn lifecycle and thread cleanup**\n\n**Identifying non-brain thread sources**"},{"type":"thinking","thinking":"**Planning targeted stream count test**\n\n**Designing regression for stream retention**\n\n**Analyzing safe physical stream removal**"},{"type":"thinking","thinking":"**Analyzing stream retirement impact on connection retention**\n\n**Tracing performance leaks in replay loops and connection churn**\n\n**Estimating connection churn from backlog enumeration**"},{"type":"toolCall","id":"call_O4y36C6gfaHd9VfF3nQT6Auz|fc_09e6344731bf55e3016a59d44d73e08194b2f20afd5eb8fec1","name":"grep","arguments":{"i":"Finding dispatcher production poll","pattern":"run_dispatch_loop\\(","path":"crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_rXgtgJ9RBqJXmE3T2VwAf1Jw|fc_09e6344731bf55e3016a59d44d73f081948a49d0c47c36096e","name":"grep","arguments":{"i":"Finding dispatch cadence constants","pattern":"DISPATCH|from_millis\\(50\\)|poll","path":"crates/spt-daemon/src/daemon.rs; crates/spt-daemon/src/brainproc.rs; crates/spt-daemon/src/lib.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_NhXzFJZdfXBkNjI5Ze1Za7AX|fc_09e6344731bf55e3016a59d44d73fc81948cf2aba4e38871b8","name":"grep","arguments":{"i":"Finding broker write deadline","pattern":"BRAIN_WRITE_DEADLINE|brain_write_deadline|from_secs\\(15\\)|CONTROLLER_WRITE_DEADLINE","path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_6dhpbT1Lwl9UIVMgp5ovLhFT|fc_09e6344731bf55e3016a59d44d74048194a640398f2ba1baaf","name":"grep","arguments":{"i":"Finding pump cadence","pattern":"RegistryWorker|cadence\\(|REGISTRY|Duration::from_secs\\(30\\)|PumpCadence","path":"crates/spt-daemon/src/pump","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":86715,"output":1825,"cacheRead":0,"cacheWrite":0,"totalTokens":88540,"reasoningTokens":1515,"cost":{"input":0.43357500000000004,"output":0.05475,"cacheRead":0,"cacheWrite":0,"total":0.48832500000000006}},"stopReason":"toolUse","timestamp":1784271882746,"responseId":"resp_09e6344731bf55e3016a59d426d13c8194a9b4881ff7eb40c2","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_09e6344731bf55e3016a59d42c62d08194b6d6e626f3f5028a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdQ2BIcWiRgoQoxt4kTiBLlDxebeNde_hgEXdwZj8IIEpg3dCv1dqxRJ3K5B8Po6hdwT6sXWavU0movUl--joUD-v5laUc2logcUdq7IBRzzh8FoLlq-Xw7g1pL7shbF11JkVJj59vPNcuwxOS4gaF7n0A7iMhfnYfcRe_MMa1X5721v4xypaDqjW_I__hwz-jsxFV0B8Uj1y5WThnnJ1pqPXN7wrqdnRTXfg_4t34k9ubaTckF8Oxe_ahPL2NNU2-NqgxC_ANVoP-yGAuLASv6heDPWM1Ja11idQy4900RrHaVRYXaECQQSaNyBwWpdmDPnwpChOKWjXF2e4rvEjNimHSHR4RGetG4bKeMSyhMvvBQO4G61ghdHrWKtA6jwPRL9X-bhBo0uguAy4a_01-hd0u7JjSanhCd4w6vjx7l2xFfBaz4BJUhulOUQajU9TppCjuaeDVDxh_L6TlKntWZzINXGTiawh43PTTfIH9ENzLy7_mTX5JfhHpVMDrgei2MKheDWr3hIR6QdLdzYBCYdnfAAgWIauvngeZhRCzX7vOjFOtQB8q8dJPeTxXNluZBr-aDshH1g-EwiO7nbCe0tfjoKk-4lid2kQ5ZU1zLbe5ySURAbs8CMwrRLR9drPk1OKC46VvC_MGugOMucM6zX8BMSxo-fYuowoKPGFaCkwda-B0A7itYXy4Fm38C0n8MynsRYxQW56zwV63XgHMLUv9fAArOBCqNB0W03DGlcdn64L-pxJbISh89dmlIccoouNaGvH2c7_R3YWFDVDlRQZji0aRcOhbAUmis4ohVdCuaeVz_Jmn4X8UJM6De4C5rwXWW1bOXvHz1LgrThzGQjFzv8tKVv31xbml8UglhWS53gOAnsofPmPoLIcYkzXROF7LwcdzD3fkArCTmdb8S6VLFH04B-YSJ6yV8sWHaVHpGwcXGdVE2NabPeqFIJ-EHGoOrKBH4yeg4SQp3CmNV6jtex3gleon6Cddv9V6wKPoTZV7pHsFBRfcXQCPVlN1AZSSku6d-flxNmswRZ-rm3BBxKCpJDLlM1Moe58tEmcsV2uqDuONgvxfQdOnuAhy-okKHJUYIweqiLFK1MEX-4-Q3ORZ5cNxgD6-BprWxANUQXvn9HVKoVhEfhBYt1EVsHB6fg0loL35LLRIzNkhZ_3BnQ5mi433-JY8F1xurKZVfXQsAqiZ9VA8gpDPzLwiHy4WHeqKYO8FJ_EtPbx2a-VWALeGkFI-LSdpIfvs6bvRZI7lLFlCizjiYcqR5BYGv7zKbL8FwoGUQwv8gUu3hvpXTQZgQ5utoHs3C9_cppOLOVyUa_Sg1P_ABxzzn8fBiJBQIB8Mdcoa4OA3Q21bHSYN3yjHzp_4jbCNh68u2NQDZxtw7D7oVpHN9UB-KiNBD9A4e5Rc6Rvk1jXrmRYN99qGbSAQpN-e4JlJEUrXnGW1Zioi-cUsxcOURwSjoappEH1pLo35_xrjqJzwOUtzR4tVfzYxDfh6H3CwkO8cxHEYlezLWqEwt4G-alEsffZsBN6VITp5DsFSwf6L-66pRcA2DAsznuQeVnF9MwItLR3rauNicjckLzfn5aOFnwtlitx8J9oGJ8E72sGYGo0qaHcG1_lBM4hcXHyfGVDHjYArvVZzV_gwxWqKKkknTaZFefNMk5XFcHCmkU63NDRrKf_wQOejpiU1yZUSoJHLF-oEGFBwqwt7iUsJKyTRSK3BV1Q61fX8Ku_PFoR_eW57TieDojHHGagfd-E8OqOMjYiEADIiPryU9THkDmj79bmaczDE0YqwuADZENpRDsbJjpZAwMmggiGYK--qXgfXpzPne8jqBhCJHjHmBvm8RpCrTfFpbQl9LpuP3n6vz1650NfnmvH_Gtk39_eUwqC9VoqTDEIUlGecrYDrMfp725GznaUC07YDdjFo4X5xRmXD1F09aRqwbwpSZb7MEsbJCi2QnOr-YrpYUAV0Y4xcOuaSQ1sXa-Gmh1YgWm94z8NgfJZTORQrvyrT9yWe_m0c4Wggv5bwghQ4kujPAyUWt0qHmlvJiWWSIGMIjhxMqV6LHeQNWxX0R21gONPVvPLq-7ERAP-aFTQdMVqGwjbt-iJHSd8J2gAXXj_2J6V1fz0zfVKQhWpWhamxyDMHiUdZijkRjmyRGVREiJwp_K7RhRV4o0HPEByeX4nLV6fRxmANA6-1k_q_qVI0_EB7mTbL4bBV4ZyYG5nItqJGDv-yXl1VvfaSHd7ODas2414rZB_gAHHt4ZVzYrpY97mxKwfg-4YnkVs7SaJCbzakFEEmBlXqe0Mkawdu9iQdqS1oF9KCNrhd82UskGllAsK92QRrEVfU7jIOgKqlfGhQ9HKfFN6KbLuIDzBiL1Q6ZJG_ldKisjsQknzH_Rzs3R-JN464sMk-w-KEfIisgndmiOkk8996eg3IMPETAX3t4W6aK152WYsSsdDZxj2H0CwoEOrQl5ALos4DixNMknBuNR3Eu6dfPxeaUCHhpvWYD1BgdT-SImhzuT7jcEaDAFVnDJ95s25HinQW3g5TaICdKy5Duzu-Y6HSfAwoZc92o3ZwAxrqNukc00v5To9y04hRr4QEBQ3WvhW_dxyIo4W6PxSPDxL4n9AcBjRMBk6YxVo5NKsuWR1fU3cXfcPqR-wFdiNtTQWU72BahR2ac7M3Ln1yfdDADycTgZYnapHJ7nZEAU_WJfkWlpJvsnd9FnWYu0goea_C6xBUWQDF626aszWhvcX8e5hasDs6StLWTT41IUouFlmYm5t1vaJdgAAAEyy8Md-6vWhBxNQe3snL_wku6ZhueRx-0usBowLX_g_LRjvVZuy5ES5g4AQk3he6q0zGcuL2x24a720uCza9m-kA2qf4KwPpHL64nkQNDQrCNETosucTHuGLFOHVO5Wg7V11x5uqserWydrzqesGm3Dh4WUnvtStG3kMm9nVJszBDII1zoRT7faEMXVl5BsVedmU4YxK0NU3CIrCxMrn3HEgeYNydI6RQeU_j5EnOI6q2mq89LMs1myQmkqO0BTpCCAXhsUnW6oLfvBDioFI0BubeZzdlYRdTBHlN2ug6XpS-NaTxxXmUYsLAu6dOwfRMRCdodRmdDzUsF0JBSBH0wfTUeFcB9fEiXR_of4AYeybgpQTe7j9O5hbzi3HNaWJwlZ6m9YYYbn_6y-AkYRVZAhBVI2zcxIzarJNnngsFfUqNSlORWb2lHPbeoXnZZgdfVkKSLQSMunXI0WDAzzQ26OpmCr8rOkc6rwpHKY0Tb6bNeaKb_ndOafU8T_SBAd0lrpRP6Lv5Wyp0CFhiEVnyn0Fade4JP5lu_uQdFaMEK11h0pXENttL28QKXqPBral-36Bmu2Zb-GymhCl2dLL4hr9yh7uwc9r_pd8LNke48QdzE3o3XC8wDA1gCs7pt2Jj-VSQmXyw9HPtUo8L_xcTIWEltZ06h5mB4jee4ujcXFu39xi0CJfC7_MXar2hgDuG4zfMxJ6L_0dKeVimM16JLHBHN8cqWFZxoSFg8TL77Sbc2vRLhodlDdq_ykDGoSIT-uYrWXa_NuuO7qv_pBWrlbhKql8lsSznqQGFecJ6p5L7KUV-I5AhURUejMfABkmHmbpQ7gNDLIPhFLiei4jc1oXBW3KlEewlGKIuAEKUrjGQ9c8mOIP9-dpxiZDcoaLLWT0oZ2ClnyBXfHqQiwA_knUm9yCxEuiVCGiowC19Vy2t-Fl5gAtw3f1bKixjV6YBnEePaAbP8z9m_nqrnSRFSGNd1rt-q1WIQwRtQrrbAYXrZTJn0Fc4sqfXBy7aStWBVSElDN7-_q6lI7MNUUQxRoToJpppc6P26fLjWonwIkMJt7C8LeLnwkZgLl5-7VN5j32_8kTxbmMAr6U95qd0BCSwSy4VdAG0SfMp3BNGU8M3_5ikNFNZ4ZwWJCA6VPSQHR06RpEVDcc1EOJ6WYAVj0ab6Yy9coNAhNOU0c6hFT_On8X1lG72OIQyWuckjt1ZMAh1jHt02e81VXzLwTmdxcRU9UmTORxQbUK-Hm_0-QDj109m4ZLyQ5i8JyKZ-H-MfFIHFI49j92hzYBZ2DYFJfYpSvZeePzYyXk2qa4rsUmh8XRFAppk2g3DYHIO6vEquz7ZZKqE=","summary":[{"type":"summary_text","text":"**Assessing thread count and connection churn**"},{"type":"summary_text","text":"**Tracing BrokerConn lifecycle and thread cleanup**"},{"type":"summary_text","text":"**Identifying non-brain thread sources**"}]},{"id":"rs_09e6344731bf55e3016a59d43679388194adbc8bcd9ef52b2c","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdQ_2acz8NhOP_jV9wTkSkHp2SABjgdQBykODeKT4xjN-Z_N43oyMFW5bWSTKKV_KieOBMChMeHSMhMHHE-VpJ7VJ61qpxtB6sFWii1-Qd9lkR46sRXam53oyv2AK4RyTZK4BPZ_hR4o-fbmJe6HnRtwM5nSGUp8fWpD17rF5ors-HS1Q92UmQofcxGO9Iy_hzNZAwmaPYa8seojZBrTccxXMcu2if1qJ5eXUBFRJV9d81SwbClsAB8C_jtRbUmnOrgL5P-JuI4gRuQBgyLiUaWAEKymo5AuzEKPBc2iRIYuWFDQ-7ytSWHzVN_OgGpIDZ9FW5Po1zwlvhGeqdwXmmnOwSv-s8vtNOAh2jGJwbogUNOZmXRAfR2mH81nZXK-SvXq4pWILgFN49D9Q_IHEoBf4lWxJszTs2KUh-CEK1-r6VNI8WvZVsOHOiPoTm7z8b6NmHecYInDikTyMyGUOvTW4dykYcmdtJ-9ABU_7tVoynBHugf0nj1kx5I4g9so5W_Alu2SYNOht3PMzWq3dYvfho43xDbSbmb-XjLhXtBDo73GgQgHUq4XyZilMmhQd7fZv10HNhWFtQvnIM4wfthBLZIu5WWN9HL6RJUP55RVsZpiKa47YCI5bFsP7J5TBhNTltscik3sFdc33EIco-dbJZV5CyEQ16Is_G_FD45K6ExcU2SQq1QsJ_-CYfhS7IwOKIk4cink6JVzMDKUeI_neAAIi-eOkMTHCVavxRSmsdJlKkr-4mG7ZDTRroWc3TQZdclgJ_4rBBO2u4p9hZmcAglGeDbe1diGhHG-uL_f7AmJ-Ds2W9JGeuQqNnOyn-eoJjGRKpSYT39PhfFBFg4GFUhfLf2fNsyqfA_L_VsQXkLKHOmVkny_6ohHZ0WxdFoUBJbwu7lVMS0-3AtZOxVUxA5Ow-iHo3jkbYVIk4WM6ar6ba85fv9MBx87sK6fpvaOkaVbqQ-mOAGmZ8muJUkfVPtuwJrC2Ry2HVDtIGytEuGVKOLI76ZE0SINUeyB-dL86aztkBEUTbta2a2XVYGLZDz1XWxREhM-a6gRh3o_nsGMUrXxTrYCSjwEnFSYlbJw182Jii98vJ4BHsz4BMjseaRUC1Qm_B6R4zI-1s_NmyL5mjDWM4-a6JA4Ha1sN80Ao9K_1Wyw7BO-3UQ5rtsFVHbJpmYDMknnxRuygT9UQG08_OIas1R66Y_vEOV3vG0ZwstFfyVJrB3XfoG9HL4xQjSbYMEUmhqTwOqpa0CVoxGpcBBE6w5k4oGon69IyucgW0YK5OEKk5JuRkZlX9xIQo8RLDMSeTKrtxz36uVWAEgf-Bv9JD1l2_omx0n2pjPRgTOGz2YvwQufVEWnok7eE9Nnxy7QnZM57wOlKNiJdKoBWK2KhMJssghyOoO7J3o8vir0TnjP49y9o5aADzZ2HugKjCyQsUeOrijpgT7BwU-6glzseRlDSavzKAtg3RO-y7K63d7_y8YihQKhtQ-SvAnvZw5bdTWqMEcTBZMLWABZ5f6w9d3EIUDdEU117J1ZrJIsFBguiabXOUPHXiCS_oa_4teGkpXltP1vXa2klE9AthQcofO4J2Gu-MpWlkpE_pgh-IYeZU3WH21b_fJlLQ2UqGQZL7lYWAnfTdYYU09MZQDDBGoSPCVZQXO9gYDXvP8_iSy94ahq-LZ-eogh4CoK-c7wGoLR1tyCdjB1t5VNsZVV9TDN3AqkBBxO5nQIiLRHZIUxhw42zX4GYdM3r0ki510YxZkZiaGd7uAbZw23c5F8phVFxnX1R-sW1PCPNloXuYF7ksTsjdW42fB4wQd-2g1dHcQb0VAKl68NWj46vy_VJO9jPZdqaHNn3wn-KT_EoTzqOp176liFQpYwwkZWViIDuh2ix7R4JZK5VEKofV7yll14vnmFMTyoPHtCeVVqkt_1ERaGctnj-L4nEUXtoyGwi61MEbIheeH0c-AAtsjeSIuisr6Fzk6xm3qcUWurSjynN9_Wog8cmHrs2ZdDvETq0xzgQ5lJMuUfww9mYw_S87TNxakcQOBfREUH-Sm3MfjrUK3QWIsMYww4LST-k-BI7GMsozazIL6iYju2WGTQUbGQKlper35oO1fbxeQHbfFLrDVP8Ef-dX_sGA9Shc_Mc2QNURPckSHFqSkkkBfgqL-Zzzy7wEUFxsQs4y7c-ZEb75mvG68tDurCVxAwFvwe8rBctTzoTrCEFF1lQ-ELMFzrvZieSaLxwB6q_3x4eEYsVcfAJKm4mBj-WYdTgkvAZFirqLApopz1Stm8m7vWAJoXn3cqFOIwtZHdQJpaRKt4u-vI5_FIXCB1vRz2zMvJSgKX_kKAdfLnMqJHY4cSqdYCkgeprNRCPWXhAjnXkgkbLxmX_M-VIdOnKvnDU_EinsBDT8_K-nz63ypiM9ozOov1Wy2aeljwkTUW2ikZAVmcfRPaOj9oNE56q0j9QQrkcvymkfETZMXZS-8Thrk2i1YJIlMcAWPip_v9Y3z4R0eQxAOeq-SSLCb-uB_ADtGCHVvmPwUR2AiN8fI-EM7KWrflpoLdOBb5mAHNn7bBm7cJ58bHVZp985uO-6qPJQCucaSoqo4JdR208hTUtVS6O5xuz-iko4XQXQZuPURBGqFA-8w_eUDNMHSH9xlvamtS3JkmB8sHWqXfmaORZ1WFcE7CHr1r9HppuDyme7kof7WiT2Q5X3i4l7ej6uqIU78qUsp03M6DxFAxvk2HP1vhVa6p2NoSClBvAlz9fzC3AGBvZXSjxu3g_SYOsvq402u3-UIsmS8zSiKcfRE_L-4mk1nKp7d81oqtnGmvhZfpH8YEj3431323g3zCn073tN0i79izkbRYnYlqKi_LGqr_BmkUsBMkSvF3C5JluH99_NBS3GqI8bSHlhslNlvcBu3tQizSLQQlg-zcHkbe2IVLw7iUq2QbR_zXT0zxuqr1LAwLbj6-XOF1tXvszWshvXyC3XBXYTYdCCEIrzpuAXauZQiqaBfBurNwXN5QCOPp8AanUcXJ6ulcvNVbLbgcuC0ZTuvvBUNfJQPmbu8rR0w4w1GID7sH6T9snNK3BDVE406Rf1TG_jJekom-5e1ScG9QwclBG0yNeykFmADnRBw2qWrjJf0V6uPI95isLoHHc6MGj4cVYVBCXNljoFCNiap9Vp5GW7Zo03rNwLrdTY0m_gN4rRkS87wVUKVXrY2Zav2pug5stHEes0qWgmUPUOeXOnOUPxxQ5dOxGPID0k23idTWxONA8zP0c0wIjuRE0AFfzmG0j9i01O5b_3UqaGKIfHMjQAB56eQIK9NS9l1wamRjyUHy5oajz1ay8-a66mhyAH-gBcZnyI3GeHgMQv-X_1b8_iKpFJig9V1iCsVRL_Pt4nts33dH9_lCeGD8hHzC0Nr4rBYPZf7NI-A4Vo8eRXXd1odo8c4NnqI1lkWfrdbqScWsBdCFwlaVd71HGkWBc6asckspEE5VsJDnp0c1eJgnvmMq15zixgZDcegyK3QM4lJ7tbSZsJx_3Tl0zVcYbGb8jV-0IJHTYeBnvydVSuE1xWYhN_0F9i5mzY72aV9lwqZ3z_X3V1FPYFooOgVFDB82TMU4S7rZ7Z2hWx7MGxOCatft9sIB-UHjjW_0nBA-lyAKWmVW1VON01eAp6tqxU-PjWyfuGwhF4yihndtSY4S7jkLmradP2XcG74AgyzSuCQc2-pvYmmzSMHfzPS5UcM-WPJyHTZjY-441wbpoSUBQyH5aEDq_T3QCKu9I0DyQw3pek8TZbClNSv8MJVbi-lejoWjEVchiVwqTTMx84iGpfoZhk0paFM9KpfhZ2hVCeO-bYxDQ-qwAkk9O8uv1yNly73TnO1d1sXIGhErxHWfdbf0q-S0YfnsefDZQHTcLlvkEXMbA0niP9kGEr_btyQ3-mKf7LGuRntCGeR27CpGOsl4krYdGNOgsZIv4SYtDVvGQQrG7_vfqUOMZIWmO5qoKPqlTxJgDdR2aw96gRhO8I6i9yw5mes2jYGhlcJsxxMGDq8JYBNdaBMfSGxXwrfR4noFvQSpShMdU_JQdskX1HM3a94SBmQQbQo25H13AZZjdciQ4TXsPh5Bj54A30Lq7QKVPu_uEN6s0D1qHHCAiulM20XDhArtkUEumxd9SlIBlKK6G_Bd4OCL1FdFKZFWsmzA6dxLfTCj6TC0I3IO5o6J9wu6RpQWuAYqCJD5aE9zpNiLeZeeAxToFRCjY3B-ow2lExzqdwJGm8r_rPP_bDawttEzXVD1O4XRBp8f64K3M8ahyNFPQMqhRZrXYZxozlD1kzRdNyBpz1fNc0eywoAwtcZtJfA=","summary":[{"type":"summary_text","text":"**Planning targeted stream count test**"},{"type":"summary_text","text":"**Designing regression for stream retention**"},{"type":"summary_text","text":"**Analyzing safe physical stream removal**"}]},{"id":"rs_09e6344731bf55e3016a59d43f5ee88194a6c6f8f775863d1b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRNALI2J6bg7Y4ixBROyvN74V_l6r218A9MY_heXz0fP5rzhkWH3vPJFUpPuJeZv8SOV9sRLKzO3_H1Vz_4Qmxasl_p0kDT3_DFBFIHPJSP5k4lCRP66Z56lVgE5KD3LOn4MjedlxDxkifqM0V3_KaOYP4l5aNf6k96KJzdiGPEBEOlJ23SIgf5rIfmKl3oLzBd85JpzcrLOt58vLBgfGGSBK2vMKnyfYOt-stT5RXNjgChENAUSS4n9TnDX-JJqD9VbXxAIiZRovOwJkgYSR4ex9XXlcYao54zlkfhYmQUYZgkdIrUSxZwsNeOv21eyK6YSzsh-3QLjxX7eXdmDjT6gMehaCOqGkNVRPFSRDlRYtr21M-kn-FI9i1IjOYDAP19AlnIDyI4PEV0lsDgrgboH4eQRNFktYerCgKvxlXR3wtAjYearLLgec2nqRhnDdmvXcA3DlrERz-ByiqvaiBjwdnWIk6bGrOxV-BQZBGWHGUcYq403SGOsMCWhjCp9QQISbGa98CxModybr8KIUsrPMMTMa3u__fbCdkATu3Rv3p8Y7ECh3d6mL5-GguZBE1SvQO0VVUlght5iqQFsZlM7IALJZDgJ_PLCVl9acSmmxbHlB39xXRMSZjConOJIvLDqE5Qsr403qHurqlPDc3qBBW6kY-SNRBohpLdhprnYR00-eAwxH6MyJWCkn2kftz1bmugRlLciOgWjdR_fOIh6TQ24J4-1A7eHFTCuz1UoJBYePY3CrY3m_ZW4EdZBUlc_tR6EitVd8E26eS5cWtcMlndInKaWrUxJmBHcVfGuCFqopproE-5QPOd9sW_sAyvdDcjopFWVy266xZxPe6FBrFRbH91t1pfNMzs9Ar9c32qfeQB6ytMP_6hCwOCgEdjNCUsEO4OK3To88Ei5JZr6JQJpHhULa-PWKGscwvr9AlUH6YWI4lwY_xNZISbAi8f9pRz-NuIjvcy8gC1xem_XECEE9VUchewxN07eKPV-av5WJr_m9rmeUZAHaLsuUm-v-BET9ib-ChC2sH3QM0pndqZBKS4bqusAODJxDVQ1rmleanC1EIv9cV4fagpoF-9ufj8q5XUtbkDWJi73PjXXkXM-skT4_P6bbmmctZD9DUNReAnytFeGXxomBcG8f-329tBb-CieKxwi0boqSpJQxr3M5cbq7fjpVFDDGpgH3zK9kttn83MzvL6j0_rDX83xovYqdXi7Ia6fJG62yk4WTMyzhhSF0OKOavzu-tyfF7y03p8vNkaVj03MqxMj6gGLvxc_3rXrKcPgC3n4x5NEunEFn9naE-7y56n_Rj1YMhTP8xSiASxi0CMpphNHEaMTH0omtrN4mKPAlHAvQUL9e-bBCDGDVVobgfz2W-L2Jyu28bfy8weL35_5Mw-v0L0zvzcMfl2qFeUPbXSOaAxZnjmj3BRSV4w0RPyQPVEeJTsdEBMMjiEy3X2UNBK1hghZETRlt9K8PTE4ayT9jWrlatngnyiuQyhVtENgR51vDBsmxqcomlRVEIA_1GU7MVhAPjynJNIWaihIy_ltjGHvMOELBkdBwQ_0BRr_cGFkQMXekc66xEoR6sJj_3Dd3SZ0x6QXTQwRsito3R3zGHEth2AUloPQumbQS4Vd2vESkSz0nDnl7SLbi3xioYCYJ-taNt7JZxys8HUwIRI5sAxKtmFt9sKNHK9sZoY7SJ-aJorbqh5TYe3Ko78br7ZZlZLsKO9yoAeKzA3H2yC99JJ_CAX2akGEZf45va1SdtB19BMn65LnFG6ziPe6t5Pl6VP2QkuzaMCFP1hr02fD0_yyHMShJh4qLbYq4Vj7SehxnIO2nOzM1prv7xS-taeIXSraCQNbuKAgfH8a9Y2FsnTijRV9AZoob0ynO18arclarjzJPRA2YEzyiN8NHwYF2ee8Be6AtbdHCKswasQsNWeRN5YCbeLMXzkiQhODg191HlB-bRio4Owuaz3c4ckJtVHjUi8YaL9eP_XS7zcEXaUdrOl9jvqDCk0eH6d8E46JryQzEnrj7HL1Z0eXEjRHv6anS9N3YvC05Hy_PQaV5hDdkQ9uGcPUlCr4KtbDrM5kKMgU31tonVVLL1VPcFWvQ9zS-_E2uQh23CiN2LligAJEwTnZP-oCEKlwzv1_9vydqHoZ3_Qk1QDu9W5jINnQrd_vUaYEZ86lX0qEee2I2sXYp6vJJ1NZo0gRwCEw8FqXnJ6IU871uYKhPr_O9gI351572gUiN3IwOaNfmuK1b3nKt8tQLEqNdkVTwjs_dlCIvD87eKGfpZFsi6fVZtk9JxhwyQ42_qabLeONzinKE0soYf_spSNbwosUZLvR5wkBN0kJzZmM1ymb1F8b7dk1Ata4vRMUvZ3vEbUfuyeyyREDThA1ojH46f6pQff-xZGYbXOgsObtvSPZ_gMrj3VYUAaoYqXWKH9IIAX95m2qo6M1_x2QGXYvzXMMDbGCH_DnNL3Ty6dMji8EuVYin0EN4bDk8nF82Y3w8OrgeVF_hs-PDucLMRQCLl9o5bNhnqQuKk2g_ah9Q2k88td529qjfD_-2m4BGbAihOrZep4yph-O_0xmW9mDYO8QWq7fy1Cj8MGCsJtKxu2uDPYdogqYhqurrrD5VkpLUivy0gEPU9iMiT05Dc75-SzRuqncRhOGMAhXV3_DSsxdmh5MDtwhYmm2Bn1gBLRGq9lGG5oB8VzIsmCIpBcpDsPbCDi8old2KsVqdpI1u4nAJarrQFycBhe3hvKvKVaONroEJu1sKCE8GI8ZahbPAQmJp0sKVFG3w3Vy-U6wyEvvXJN1LSdq0_JI96q0bxo7z428b-wrg-MITC9a2dBG2FjJr5Kz0PEC8_2BLwgn72DYBP3oukIxj1B6L-2xxvJ22rr1HEpVT1d8JX7e0BjwsYVmFUlDBHkl1CUIcHENzAAXSWWecg02IafmP0fdeAYduKxksRPSXeveMBNkigAVVW7l0viZ9BIGMw92AacQ9AQ5JaI8zO6HaV5txPlkcaJAEXZPX_iMfab0-fQJPIyBOeo-5J4ldM4j2SzJJHIKc0sANMqgHycGM6IQt1cn3fA5kh-nznxrtJPwYeoyXAf81HvSq4YWFCY6Jn0aAchnT1OrJC3Icgk7RqcbmygiD-dHx3JJSVzuMrqRXEDnGjgz34UKOetBViu156B4tZl4dLSyY8RGwVz_VdqOHrFE7RIAltH8eluM_F3sFls-Cpcz_yj5vAAyb2JPhL_RdKAnFOEIgJxj8JmuhFwPfaxXqAOa4IK8WmzLoFG1Z044f2KZilqrthVYGB6kn885GpzUjkliBicuNAUxEcZtUZCK4dKPdTNy-7bqvp-GNzFf8ONlTxBkFpl0HAdcbaMbRIuz-mFFF4Cef6I8nfOQqcVxKnrQWDXV2jy7Zb8JX5kETl_0Tt_hn_XOn1nUcJGd8Bg5C9xv-fI4J_V2HOoldLSV3fuB37fi6i8x2W7MdtJtuc-jogK2k1PL4hJ1kK2OFZoL25JOCDB6-mOMNxqW6kIEGsux1017uErr9pdGD3e12Pn7SEjRRN77CM0zkgedStJsM8uuGtcnIZljREMIntA0CoKdHsM3Vd3d1QuRMqJsuIyf7EHKXF57sSS-RWfcPJJT61iftzrFL08FotCa6lBY6QyFeJ8fqAOhjzGd2x_DiMWkXEDXy1Nem6D60GM3FWnaMsKV58mj4Z5frTBzZC5rt3pVzLfWt0RTk9tjrSl8aCZF124UEzUgJ3Y0h-1a1kCS8qQTvZBHWVVBFnzOlz8DAcObJfullXuvETgyQqCck0-4sR6ICTMXcdffjq504vPZdzq7oWaAy2t9ZhaCNL7UP-e3B3F5FVVFamM9obsauIHxhLTlw50TJ2ltgDmCqQujCUI2oGiC9gVIm1gY7Tn7BTN-x1tDDNNVcbegpkmvCtvltW4jjYL1_YcH31bENDEVH66XUj9DWlduO7F8m13gHJ-7cQUt5T_MLHNuOgMpxQkxyI4-akRcDQ3ZNxDXgcclP-7fjbMnZmqElwHHmEmlYcaawfmzgRupvy2iQab2WrN4syy0JQmJOeDTbSNASuqlAb-OaeILS0Q-Ws4","summary":[{"type":"summary_text","text":"**Analyzing stream retirement impact on connection retention**"},{"type":"summary_text","text":"**Tracing performance leaks in replay loops and connection churn**"},{"type":"summary_text","text":"**Estimating connection churn from backlog enumeration**"}]},{"id":"fc_09e6344731bf55e3016a59d44d73e08194b2f20afd5eb8fec1","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding dispatcher production poll\",\"pattern\":\"run_dispatch_loop\\\\(\",\"path\":\"crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_O4y36C6gfaHd9VfF3nQT6Auz","name":"grep"},{"id":"fc_09e6344731bf55e3016a59d44d73f081948a49d0c47c36096e","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding dispatch cadence constants\",\"pattern\":\"DISPATCH|from_millis\\\\(50\\\\)|poll\",\"path\":\"crates/spt-daemon/src/daemon.rs; crates/spt-daemon/src/brainproc.rs; crates/spt-daemon/src/lib.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_rXgtgJ9RBqJXmE3T2VwAf1Jw","name":"grep"},{"id":"fc_09e6344731bf55e3016a59d44d73fc81948cf2aba4e38871b8","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding broker write deadline\",\"pattern\":\"BRAIN_WRITE_DEADLINE|brain_write_deadline|from_secs\\\\(15\\\\)|CONTROLLER_WRITE_DEADLINE\",\"path\":\"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_NhXzFJZdfXBkNjI5Ze1Za7AX","name":"grep"},{"id":"fc_09e6344731bf55e3016a59d44d74048194a640398f2ba1baaf","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding pump cadence\",\"pattern\":\"RegistryWorker|cadence\\\\(|REGISTRY|Duration::from_secs\\\\(30\\\\)|PumpCadence\",\"path\":\"crates/spt-daemon/src/pump\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_6dhpbT1Lwl9UIVMgp5ovLhFT","name":"grep"}]},"duration":67852.82189999521,"ttft":33657.92309999466,"contextSnapshot":{"promptTokens":86715,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_O4y36C6gfaHd9VfF3nQT6Auz|fc_09e6344731bf55e3016a59d44d73e08194b2f20afd5eb8fec1","toolName":"grep","startedAt":"2026-07-17T07:05:50.602Z","args":{"path":"crates/spt-daemon/src"},"intent":"Finding dispatcher production poll"},"id":"5f6fc125","parentId":"0c790f4e","timestamp":"2026-07-17T07:05:50.602Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_rXgtgJ9RBqJXmE3T2VwAf1Jw|fc_09e6344731bf55e3016a59d44d73f081948a49d0c47c36096e","toolName":"grep","startedAt":"2026-07-17T07:05:50.602Z","args":{"path":"crates/spt-daemon/src/daemon.rs; crates/spt-daemon/src/brainproc.rs; crates/spt-daemon/src/lib.rs"},"intent":"Finding dispatch cadence constants"},"id":"0ccf250c","parentId":"5f6fc125","timestamp":"2026-07-17T07:05:50.602Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_NhXzFJZdfXBkNjI5Ze1Za7AX|fc_09e6344731bf55e3016a59d44d73fc81948cf2aba4e38871b8","toolName":"grep","startedAt":"2026-07-17T07:05:50.602Z","args":{"path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs"},"intent":"Finding broker write deadline"},"id":"4aadfa3b","parentId":"0ccf250c","timestamp":"2026-07-17T07:05:50.602Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6dhpbT1Lwl9UIVMgp5ovLhFT|fc_09e6344731bf55e3016a59d44d74048194a640398f2ba1baaf","toolName":"grep","startedAt":"2026-07-17T07:05:50.602Z","args":{"path":"crates/spt-daemon/src/pump"},"intent":"Finding pump cadence"},"id":"88922a72","parentId":"4aadfa3b","timestamp":"2026-07-17T07:05:50.602Z"}
{"type":"message","id":"00401bd5","parentId":"88922a72","timestamp":"2026-07-17T07:05:50.612Z","message":{"role":"toolResult","toolCallId":"call_6dhpbT1Lwl9UIVMgp5ovLhFT|fc_09e6344731bf55e3016a59d44d74048194a640398f2ba1baaf","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/pump/\n## mod.rs\n 91|use notif::NotifWorker;\n*92|use registry::RegistryWorker;\n 93|use sync::SyncWorker;\n 94|use update::UpdateWorker;\n 95|\n...\n 108|/// escalated to a supervised restart, NEVER a per-peer retry.\n*109|const PUMP_PEER_IO_TIMEOUT: Duration = Duration::from_secs(30);\n 110|\n 111|/// Supervisor backoff floor: the first restart after a pump death.\n 112|const SUPERVISE_BACKOFF_BASE: Duration = Duration::from_secs(5);\n...\n 294|#[derive(Debug, Clone, Copy)]\n*295|pub struct PumpCadence {\n 296|    pub registry: Duration,\n 297|    pub notif: Duration,\n 298|    pub sync: Duration,\n...\n 304|\n*305|impl PumpCadence {\n 306|    /// From the daemon config knobs.\n*307|    pub fn from_config(cfg: &DaemonConfig) -> PumpCadence {\n*308|        PumpCadence {\n 309|            registry: cfg.registry_pump_period,\n 310|            notif: cfg.notif_pump_period,\n 311|            sync: cfg.sync_pull_period,\n...\n 320|pub struct PumpConfig {\n*321|    pub cadence: PumpCadence,\n 322|    /// `true` = the explicit opt-in to prompt-free updates; default `false`\n 323|    /// surfaces every staged release as a consent notif.\n 324|    pub full_auto_update: bool,\n...\n 330|        PumpConfig {\n*331|            cadence: PumpCadence::from_config(cfg),\n 332|            full_auto_update: cfg.full_auto_update,\n 333|        }\n 334|    }\n...\n 372|    /// This worker's scheduling period (config-driven in production).\n*373|    fn cadence(&self) -> Duration;\n 374|\n 375|    /// Take this worker's exactly-once wake marker — a wake forces one round\n 376|    /// NOW regardless of cadence. Called every tick so a marker is consumed\n...\n 576|    // hooks (ADR-0013).\n*577|    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\n 578|    let mut notif_worker = NotifWorker::new(paths, &cadence);\n 579|    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\n 580|    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\n...\n 747|        let wake = workers[i].poll_wake();\n*748|        flags[i] = due(last[i], wake, now, workers[i].cadence());\n 749|        any |= flags[i];\n 750|    }\n 751|    (flags, any)\n...\n 1298|    #[test]\n*1299|    fn wake_forces_a_round_regardless_of_cadence() {\n 1300|        let now = Instant::now();\n 1301|        let day = Duration::from_secs(86_400);\n 1302|        // Just ran, nowhere near due — wake forces it anyway.\n...\n 1371|    impl PumpWorker for RecordingWorker {\n*1372|        fn cadence(&self) -> Duration {\n 1373|            self.cadence\n 1374|        }\n 1375|        fn poll_wake(&mut self) -> bool {\n## notif.rs\n 15|\n*16|use super::{PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n 17|\n 18|/// The notif leg as a [`PumpWorker`]: the full semilattice spool per subnet\n 19|/// ([`emit_notif_feed`]); the receiver's join dedups.\n...\n 28|    /// Construction capture (§Frozen-4): the notif spool DB path + the cadence.\n*29|    pub(super) fn new(paths: &PumpPaths, cadence: &PumpCadence) -> NotifWorker {\n 30|        NotifWorker {\n 31|            notif_db: paths.notif_db.clone(),\n 32|            cadence: cadence.notif,\n...\n 37|impl PumpWorker for NotifWorker {\n*38|    fn cadence(&self) -> Duration {\n 39|        self.cadence\n 40|    }\n 41|\n## registry.rs\n 22|\n*23|use super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n 24|\n 25|/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,\n 26|/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the\n...\n 67|/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\n*68|pub(super) struct RegistryWorker {\n 69|    /// The shared registry host (sweep + advertise + the canonical epoch).\n 70|    registry: Arc<RegistryHost>,\n 71|    /// The perch tree scanned for advertisable rows.\n...\n 86|\n*87|impl RegistryWorker {\n 88|    /// Construction capture (§Frozen-4): the shared registry plus this worker's\n 89|    /// `PathBuf` slices and cadence knobs out of the pump's wiring.\n 90|    pub(super) fn new(\n...\n 92|        paths: &PumpPaths,\n*93|        cadence: &PumpCadence,\n*94|    ) -> RegistryWorker {\n*95|        RegistryWorker {\n 96|            registry,\n 97|            owlery: paths.owlery.clone(),\n 98|            subnets: paths.subnets.clone(),\n...\n 107|\n*108|impl PumpWorker for RegistryWorker {\n*109|    fn cadence(&self) -> Duration {\n 110|        self.cadence\n 111|    }\n 112|\n...\n 136|        // admitted feed and is never touched.\n*137|        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 138|        self.registry.evict_silent_peers(self.evict_after);\n 139|        // Trigger (b) — per-row Offline-TTL (#2-secondary): the SAME sweep also\n 140|        // evicts rows that have been non-routable (Offline) beyond the grace even\n...\n 145|        // stamps for free.\n*146|        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 147|        self.registry.evict_aged_offline_rows(self.evict_after);\n 148|        // Mesh-D7 (REQ-MESH-4): fire any seed rotation whose revoke window has\n 149|        // closed — after the eviction sweep, before re-advertising. The\n...\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...\n 240|            advertise_marker: marker.clone(),\n*241|            cadence: Duration::from_secs(30),\n 242|            evict_after: Duration::from_secs(600),\n 243|            adverts: Vec::new(),\n 244|        };\n...\n 289|\n*290|            let mut w = RegistryWorker {\n 291|                registry: Arc::new(RegistryHost::new_at(\n 292|                    \"aa11\",\n 293|                    EpochSource::load_from(&home.join(\"epoch.json\")),\n...\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 303|            };\n## sync.rs\n 23|\n*24|use super::{PeerIo, PumpCadence, PumpHooks, PumpPaths, PumpWorker, RoundCtx};\n 25|\n 26|/// The sync leg as a [`PumpWorker`]: anti-entropy pull for the minds.\n 27|pub(super) struct SyncWorker {\n...\n 50|        paths: &PumpPaths,\n*51|        cadence: &PumpCadence,\n 52|        hooks: PumpHooks,\n 53|    ) -> SyncWorker {\n 54|        SyncWorker {\n...\n 65|impl PumpWorker for SyncWorker {\n*66|    fn cadence(&self) -> Duration {\n 67|        self.cadence\n 68|    }\n 69|\n## update.rs\n 28|\n*29|use super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n 30|\n 31|/// The update leg as a [`PumpWorker`]: the peer-propagation pull (REQ-UPD-1),\n 32|/// consent-gated by default (REQ-UPD-4), with typed convergence outcomes\n...\n 62|        paths: &PumpPaths,\n*63|        cadence: &PumpCadence,\n 64|        full_auto_update: bool,\n 65|    ) -> UpdateWorker {\n 66|        UpdateWorker {\n...\n 80|impl PumpWorker for UpdateWorker {\n*81|    fn cadence(&self) -> Duration {\n 82|        self.cadence\n 83|    }\n 84|\n...\n 159|                // collide (the rig presence-rung UNIQUE failure).\n*160|                // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]\n 161|                let produced = self.registry.with_epoch(|ep| {\n 162|                    produce_consent_notif(\n 163|                        &decision,"}],"details":{"scopePath":"crates/spt-daemon/src/pump","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":37,"fileCount":5,"files":["crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/notif.rs","crates/spt-daemon/src/pump/registry.rs","crates/spt-daemon/src/pump/sync.rs","crates/spt-daemon/src/pump/update.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/pump/mod.rs","count":13},{"path":"crates/spt-daemon/src/pump/notif.rs","count":3},{"path":"crates/spt-daemon/src/pump/registry.rs","count":14},{"path":"crates/spt-daemon/src/pump/sync.rs","count":3},{"path":"crates/spt-daemon/src/pump/update.rs","count":4}],"truncated":false,"displayContent":"# crates/spt-daemon/src/pump/\n## mod.rs\n   91│use notif::NotifWorker;\n  *92│use registry::RegistryWorker;\n   93│use sync::SyncWorker;\n   94│use update::UpdateWorker;\n   95│\n     │...\n  108│/// escalated to a supervised restart, NEVER a per-peer retry.\n *109│const PUMP_PEER_IO_TIMEOUT: Duration = Duration::from_secs(30);\n  110│\n  111│/// Supervisor backoff floor: the first restart after a pump death.\n  112│const SUPERVISE_BACKOFF_BASE: Duration = Duration::from_secs(5);\n     │...\n  294│#[derive(Debug, Clone, Copy)]\n *295│pub struct PumpCadence {\n  296│    pub registry: Duration,\n  297│    pub notif: Duration,\n  298│    pub sync: Duration,\n     │...\n  304│\n *305│impl PumpCadence {\n  306│    /// From the daemon config knobs.\n *307│    pub fn from_config(cfg: &DaemonConfig) -> PumpCadence {\n *308│        PumpCadence {\n  309│            registry: cfg.registry_pump_period,\n  310│            notif: cfg.notif_pump_period,\n  311│            sync: cfg.sync_pull_period,\n     │...\n  320│pub struct PumpConfig {\n *321│    pub cadence: PumpCadence,\n  322│    /// `true` = the explicit opt-in to prompt-free updates; default `false`\n  323│    /// surfaces every staged release as a consent notif.\n  324│    pub full_auto_update: bool,\n     │...\n  330│        PumpConfig {\n *331│            cadence: PumpCadence::from_config(cfg),\n  332│            full_auto_update: cfg.full_auto_update,\n  333│        }\n  334│    }\n     │...\n  372│    /// This worker's scheduling period (config-driven in production).\n *373│    fn cadence(&self) -> Duration;\n  374│\n  375│    /// Take this worker's exactly-once wake marker — a wake forces one round\n  376│    /// NOW regardless of cadence. Called every tick so a marker is consumed\n     │...\n  576│    // hooks (ADR-0013).\n *577│    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\n  578│    let mut notif_worker = NotifWorker::new(paths, &cadence);\n  579│    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\n  580│    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\n     │...\n  747│        let wake = workers[i].poll_wake();\n *748│        flags[i] = due(last[i], wake, now, workers[i].cadence());\n  749│        any |= flags[i];\n  750│    }\n  751│    (flags, any)\n     │...\n 1298│    #[test]\n*1299│    fn wake_forces_a_round_regardless_of_cadence() {\n 1300│        let now = Instant::now();\n 1301│        let day = Duration::from_secs(86_400);\n 1302│        // Just ran, nowhere near due — wake forces it anyway.\n     │...\n 1371│    impl PumpWorker for RecordingWorker {\n*1372│        fn cadence(&self) -> Duration {\n 1373│            self.cadence\n 1374│        }\n 1375│        fn poll_wake(&mut self) -> bool {\n## notif.rs\n 15│\n*16│use super::{PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n 17│\n 18│/// The notif leg as a [`PumpWorker`]: the full semilattice spool per subnet\n 19│/// ([`emit_notif_feed`]); the receiver's join dedups.\n   │...\n 28│    /// Construction capture (§Frozen-4): the notif spool DB path + the cadence.\n*29│    pub(super) fn new(paths: &PumpPaths, cadence: &PumpCadence) -> NotifWorker {\n 30│        NotifWorker {\n 31│            notif_db: paths.notif_db.clone(),\n 32│            cadence: cadence.notif,\n   │...\n 37│impl PumpWorker for NotifWorker {\n*38│    fn cadence(&self) -> Duration {\n 39│        self.cadence\n 40│    }\n 41│\n## registry.rs\n  22│\n *23│use super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n  24│\n  25│/// Fire any seed rotations whose coalescing window has closed (Mesh-D7,\n  26│/// REQ-MESH-4). For each due subnet: rotate the seed (bump epoch, retain the\n    │...\n  67│/// an origin the receiver cannot gate honestly (KNOWN-HAZARDS 7.5/4.10).\n *68│pub(super) struct RegistryWorker {\n  69│    /// The shared registry host (sweep + advertise + the canonical epoch).\n  70│    registry: Arc<RegistryHost>,\n  71│    /// The perch tree scanned for advertisable rows.\n    │...\n  86│\n *87│impl RegistryWorker {\n  88│    /// Construction capture (§Frozen-4): the shared registry plus this worker's\n  89│    /// `PathBuf` slices and cadence knobs out of the pump's wiring.\n  90│    pub(super) fn new(\n    │...\n  92│        paths: &PumpPaths,\n *93│        cadence: &PumpCadence,\n *94│    ) -> RegistryWorker {\n *95│        RegistryWorker {\n  96│            registry,\n  97│            owlery: paths.owlery.clone(),\n  98│            subnets: paths.subnets.clone(),\n    │...\n 107│\n*108│impl PumpWorker for RegistryWorker {\n*109│    fn cadence(&self) -> Duration {\n 110│        self.cadence\n 111│    }\n 112│\n    │...\n 136│        // admitted feed and is never touched.\n*137│        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 138│        self.registry.evict_silent_peers(self.evict_after);\n 139│        // Trigger (b) — per-row Offline-TTL (#2-secondary): the SAME sweep also\n 140│        // evicts rows that have been non-routable (Offline) beyond the grace even\n    │...\n 145│        // stamps for free.\n*146│        // [impl->REQ-HAZARD-REGISTRY-GHOST-ROWS]\n 147│        self.registry.evict_aged_offline_rows(self.evict_after);\n 148│        // Mesh-D7 (REQ-MESH-4): fire any seed rotation whose revoke window has\n 149│        // closed — after the eviction sweep, before re-advertising. The\n    │...\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    │...\n 240│            advertise_marker: marker.clone(),\n*241│            cadence: Duration::from_secs(30),\n 242│            evict_after: Duration::from_secs(600),\n 243│            adverts: Vec::new(),\n 244│        };\n    │...\n 289│\n*290│            let mut w = RegistryWorker {\n 291│                registry: Arc::new(RegistryHost::new_at(\n 292│                    \"aa11\",\n 293│                    EpochSource::load_from(&home.join(\"epoch.json\")),\n    │...\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 303│            };\n## sync.rs\n 23│\n*24│use super::{PeerIo, PumpCadence, PumpHooks, PumpPaths, PumpWorker, RoundCtx};\n 25│\n 26│/// The sync leg as a [`PumpWorker`]: anti-entropy pull for the minds.\n 27│pub(super) struct SyncWorker {\n   │...\n 50│        paths: &PumpPaths,\n*51│        cadence: &PumpCadence,\n 52│        hooks: PumpHooks,\n 53│    ) -> SyncWorker {\n 54│        SyncWorker {\n   │...\n 65│impl PumpWorker for SyncWorker {\n*66│    fn cadence(&self) -> Duration {\n 67│        self.cadence\n 68│    }\n 69│\n## update.rs\n  28│\n *29│use super::{now_ms, PeerIo, PumpCadence, PumpPaths, PumpWorker, RoundCtx};\n  30│\n  31│/// The update leg as a [`PumpWorker`]: the peer-propagation pull (REQ-UPD-1),\n  32│/// consent-gated by default (REQ-UPD-4), with typed convergence outcomes\n    │...\n  62│        paths: &PumpPaths,\n *63│        cadence: &PumpCadence,\n  64│        full_auto_update: bool,\n  65│    ) -> UpdateWorker {\n  66│        UpdateWorker {\n    │...\n  80│impl PumpWorker for UpdateWorker {\n *81│    fn cadence(&self) -> Duration {\n  82│        self.cadence\n  83│    }\n  84│\n    │...\n 159│                // collide (the rig presence-rung UNIQUE failure).\n*160│                // [impl->REQ-HAZARD-REGISTRY-EPOCH-LEASE]\n 161│                let produced = self.registry.with_epoch(|ep| {\n 162│                    produce_consent_notif(\n 163│                        &decision,"},"isError":false,"timestamp":1784271950611}}
{"type":"message","id":"86fbe943","parentId":"00401bd5","timestamp":"2026-07-17T07:05:50.613Z","message":{"role":"toolResult","toolCallId":"call_O4y36C6gfaHd9VfF3nQT6Auz|fc_09e6344731bf55e3016a59d44d73e08194b2f20afd5eb8fec1","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## dispatch.rs\n 428|/// stream).\n*429|pub fn run_dispatch_loop(\n 430|    broker_name: &str,\n 431|    paths: &DispatchPaths,\n 432|    registry: Arc<RegistryHost>,\n...\n 1204|        if let Err(e) =\n*1205|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206|        {\n 1207|            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208|        }"}],"details":{"scopePath":"crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":2}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## dispatch.rs\n  428│/// stream).\n *429│pub fn run_dispatch_loop(\n  430│    broker_name: &str,\n  431│    paths: &DispatchPaths,\n  432│    registry: Arc<RegistryHost>,\n     │...\n 1204│        if let Err(e) =\n*1205│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206│        {\n 1207│            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208│        }"},"isError":false,"timestamp":1784271950613}}
{"type":"message","id":"49037121","parentId":"86fbe943","timestamp":"2026-07-17T07:05:50.614Z","message":{"role":"toolResult","toolCallId":"call_NhXzFJZdfXBkNjI5Ze1Za7AX|fc_09e6344731bf55e3016a59d44d73fc81948cf2aba4e38871b8","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## broker.rs\n 140|/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n*141|/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n 142|/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n 143|/// never trips the deadline.\n 144|const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n...\n 153|/// untouched.\n*154|const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n 155|\n 156|/// How long a controller's writer thread may sit BLOCKED inside a single socket\n 157|/// write — the brain subscriber suspended / black-holed — before a take/reattach\n...\n 159|/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n*160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n 161|/// protects the fan-out against ONE per-client conn falling behind and is\n 162|/// output-driven (the drain observes a Full channel); THIS bound is measured\n 163|/// full-with-**zero writer progress** and covers the case the output-driven path\n...\n 171|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*172|const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n 173|\n*174|/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n*175|/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n 176|/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n 177|/// the slot) is provable in ~1 s instead of 15 (mirrors the\n 178|/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n...\n 180|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*181|fn brain_write_deadline() -> Duration {\n*182|    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n 183|        Ok(ms) => ms\n 184|            .parse::<u64>()\n 185|            .map(Duration::from_millis)\n*186|            .unwrap_or(BRAIN_WRITE_DEADLINE),\n*187|        Err(_) => BRAIN_WRITE_DEADLINE,\n 188|    }\n 189|}\n 190|\n...\n 285|/// buggy translation binary can therefore stall operator input for at most this\n*286|/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n 287|const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 288|\n 289|/// The effective commit deadline. Production is the const above; the int gate\n...\n 492|/// advances `delivered_through` on each successful write, and it is never evicted\n*493|/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n 494|struct ControllerSink {\n 495|    /// The bounded live-frame queue to the controller's writer thread.\n 496|    tx: SyncSender<CtrlMsg>,\n...\n 511|    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n*512|    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n 513|    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n 514|    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n 515|    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n...\n 521|    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n*522|    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n 523|    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n 524|    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n 525|    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n...\n 729|    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n*730|    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n 731|    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n 732|    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n 733|    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n...\n 735|    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n*736|    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n 737|    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n 738|    /// (bounded wedge preserved). The writer still does the blocking socket write +\n 739|    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n...\n 843|                Err(TrySendError::Full(_)) => {\n*844|                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n 845|                        Some(epoch) // stalled past the deadline → bounded detach\n 846|                    } else {\n 847|                        None // drop this chunk; controller falls behind the ring\n...\n 1011|        // writer is BLOCKED (not exited) on a suspended brain conn past\n*1012|        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1013|        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1014|        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1015|        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n...\n 1058|\n*1059|    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n 1060|    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1061|    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1062|    /// record the evict for observability (broker log now; W3 persists the stderr,\n...\n 1071|    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n*1072|        let deadline = brain_write_deadline();\n 1073|        if !self.controller_write_stalled(now, deadline) {\n 1074|            return false;\n 1075|        }\n## conn.rs\n 424|    /// deadline (gate-wait + OS write completion) — the broker passes its\n*425|    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 426|    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 427|        let raw = raw_of(&half);\n 428|        let inner = Arc::new(Inner {"}],"details":{"scopePath":"crates/spt-daemon/src/broker.rs, crates/spt-daemon/src/conn.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":21,"fileCount":2,"files":["crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/src/conn.rs","count":1}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/src/\n## broker.rs\n  140│/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n *141│/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n  142│/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n  143│/// never trips the deadline.\n  144│const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n     │...\n  153│/// untouched.\n *154│const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n  155│\n  156│/// How long a controller's writer thread may sit BLOCKED inside a single socket\n  157│/// write — the brain subscriber suspended / black-holed — before a take/reattach\n     │...\n  159│/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n *160│/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n  161│/// protects the fan-out against ONE per-client conn falling behind and is\n  162│/// output-driven (the drain observes a Full channel); THIS bound is measured\n  163│/// full-with-**zero writer progress** and covers the case the output-driven path\n     │...\n  171│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n *172│const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n  173│\n *174│/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n *175│/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n  176│/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n  177│/// the slot) is provable in ~1 s instead of 15 (mirrors the\n  178│/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n     │...\n  180│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n *181│fn brain_write_deadline() -> Duration {\n *182│    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n  183│        Ok(ms) => ms\n  184│            .parse::<u64>()\n  185│            .map(Duration::from_millis)\n *186│            .unwrap_or(BRAIN_WRITE_DEADLINE),\n *187│        Err(_) => BRAIN_WRITE_DEADLINE,\n  188│    }\n  189│}\n  190│\n     │...\n  285│/// buggy translation binary can therefore stall operator input for at most this\n *286│/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n  287│const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n  288│\n  289│/// The effective commit deadline. Production is the const above; the int gate\n     │...\n  492│/// advances `delivered_through` on each successful write, and it is never evicted\n *493│/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n  494│struct ControllerSink {\n  495│    /// The bounded live-frame queue to the controller's writer thread.\n  496│    tx: SyncSender<CtrlMsg>,\n     │...\n  511│    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n *512│    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n  513│    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n  514│    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n  515│    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n     │...\n  521│    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n *522│    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n  523│    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n  524│    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n  525│    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n     │...\n  729│    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n *730│    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n  731│    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n  732│    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n  733│    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n     │...\n  735│    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n *736│    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n  737│    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n  738│    /// (bounded wedge preserved). The writer still does the blocking socket write +\n  739│    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n     │...\n  843│                Err(TrySendError::Full(_)) => {\n *844│                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n  845│                        Some(epoch) // stalled past the deadline → bounded detach\n  846│                    } else {\n  847│                        None // drop this chunk; controller falls behind the ring\n     │...\n 1011│        // writer is BLOCKED (not exited) on a suspended brain conn past\n*1012│        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1013│        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1014│        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1015│        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n     │...\n 1058│\n*1059│    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n 1060│    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1061│    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1062│    /// record the evict for observability (broker log now; W3 persists the stderr,\n     │...\n 1071│    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n*1072│        let deadline = brain_write_deadline();\n 1073│        if !self.controller_write_stalled(now, deadline) {\n 1074│            return false;\n 1075│        }\n## conn.rs\n 424│    /// deadline (gate-wait + OS write completion) — the broker passes its\n*425│    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 426│    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 427│        let raw = raw_of(&half);\n 428│        let inner = Arc::new(Inner {"},"isError":false,"timestamp":1784271950614}}
{"type":"message","id":"774b0cf5","parentId":"49037121","timestamp":"2026-07-17T07:05:50.615Z","message":{"role":"toolResult","toolCallId":"call_rXgtgJ9RBqJXmE3T2VwAf1Jw|fc_09e6344731bf55e3016a59d44d73f081948a49d0c47c36096e","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## daemon.rs\n 199|                // The broker keeps only the NetHost bring-up and the boot-race\n*200|                // self-heal that binds it; the brain polls `net-status` and\n 201|                // starts the consumers once net reports enabled.\n 202|                // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 203|                    if !net_up && node_hex.is_some() {\n...\n 272|        // nothing — the slot is ephemeral control, a restart correctly drops it).\n*273|        // `spt shell drive` writes it, `api drive-poll --link` takes-and-clears it.\n 274|        // [impl->REQ-SHELL-3]\n 275|        let drive_hub = Arc::new(crate::drivehub::DriveHub::new());\n 276|        {\n...\n 929|/// the real per-rung launchers and log which rung won. The caller still confirms\n*930|/// readiness via the existing `brain.ready` poll — the returned pid is for\n 931|/// logging/return, not readiness (WMI/schtasks pids are the launcher's view).\n 932|// [impl->REQ-HAZARD-VIEWER-CLOSE-DETACH]\n 933|#[cfg(windows)]\n...\n 1185|/// is recoverable from schtasks → returns a sentinel; readiness is the caller's\n*1186|/// `brain.ready` poll. This is the FALLBACK rung (WMI is primary); its `/tr`\n 1187|/// quoting is best-effort.\n 1188|// [impl->REQ-HAZARD-VIEWER-CLOSE-DETACH]\n 1189|#[cfg(windows)]\n...\n 1225|    }\n*1226|    // No pid from schtasks; sentinel — readiness is the brain.ready poll.\n 1227|    Ok(0)\n 1228|}\n 1229|\n...\n 1299|    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n*1300|    // 30s poll, never an ever-growing or zero-length spin.\n 1301|    #[test]\n 1302|    fn net_retry_backoff_doubles_then_caps() {\n 1303|        assert_eq!(net_retry_backoff(NET_RETRY_FIRST), Duration::from_secs(2));\n## brainproc.rs\n 279|        // DRIVE the broker's controller-liveness reap every heartbeat (UPDATE-WEDGE\n*280|        // fix, counter-54). A `KIND_SESSIONS` poll is what runs the broker's\n 281|        // `reap_dead_controller` sweep (broker.rs), which stall-evicts a black-holed\n 282|        // LOCAL controller conn a HARD-KILLED prior brain generation left behind (its\n 283|        // hosted PTYs keep writing to a dead pipe → the broker-side writer BLOCKS, never\n*284|        // EOFs). Nothing else polls `KIND_SESSIONS` during an isolated brain-trial\n 285|        // window, so without this the wedge never drains: the promotion DRAINED gate\n 286|        // (`old_gen_drained()` = `!any_local_controller_wedged()`, a PURE READ that never\n 287|        // drives the evict) stays false for the full trial → \"alive but never ready\"\n...\n 672|/// semantic) and promotion waits for `env.old_gen_drained()` too. While ready-seen\n*673|/// but not-yet-drained the watch keeps polling to `deadline`; if the drain never\n 674|/// comes the window elapses to [`TrialStep::WindowElapsedAlive`] — the caller KILLS\n 675|/// the candidate and rolls back (conservative: never a false-promote onto a wedge).\n 676|/// The normal case drains well inside the window — W2 bounds a real drain to\n...\n 1322|    /// re-spawned (the `already_started` latch) — so a single consumer set runs,\n*1323|    /// no matter how many heartbeats poll `net-status`.\n 1324|    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 1325|    #[test]\n 1326|    fn consumer_gate_spawns_once_when_net_enabled() {\n...\n 1461|        // before; `u32::MAX` = never drains within a test window; a small N = drains\n*1462|        // after N polls (the ready-seen latch holds across the undrained polls).\n 1463|        drain_countdown: AtomicU32,\n 1464|    }\n 1465|\n...\n 1493|        /// Arm the DRAINED gate: `old_gen_drained` returns `false` for the first\n*1494|        /// `polls` calls, then `true` (0 = always drained; `u32::MAX` = never within\n*1495|        /// the test window). Models the OLD-gen control plane draining after N polls.\n*1496|        fn drains_after(self, polls: u32) -> Self {\n*1497|            self.drain_countdown.store(polls, Ordering::Relaxed);\n 1498|            self\n 1499|        }\n 1500|\n...\n 1544|            // Countdown: false (still draining) while > 0, decrementing; then true.\n*1545|            // Single-threaded per env in these harnesses (the supervisor polls\n 1546|            // sequentially), so a plain load/store is race-free here.\n 1547|            let left = self.drain_countdown.load(Ordering::Relaxed);\n 1548|            if left == 0 {\n...\n 1673|    /// (DRAINED gate — latch releases) A ready candidate whose old generation drains\n*1674|    /// AFTER a couple of polls promotes: the ready-seen latch holds across the\n*1675|    /// undrained polls (a ready-then-exited candidate is not lost while waiting) and\n 1676|    /// promotion fires the moment `old_gen_drained()` flips true — never a premature\n 1677|    /// rollback, never a false-promote.\n 1678|    // [unit->REQ-UPDATE-PROMOTE-DRAINED]"}],"details":{"scopePath":"crates/spt-daemon/src/daemon.rs, crates/spt-daemon/src/brainproc.rs, crates/spt-daemon/src/lib.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":18,"fileCount":2,"files":["crates/spt-daemon/src/daemon.rs","crates/spt-daemon/src/brainproc.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/daemon.rs","count":6},{"path":"crates/spt-daemon/src/brainproc.rs","count":12}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## daemon.rs\n  199│                // The broker keeps only the NetHost bring-up and the boot-race\n *200│                // self-heal that binds it; the brain polls `net-status` and\n  201│                // starts the consumers once net reports enabled.\n  202│                // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n  203│                    if !net_up && node_hex.is_some() {\n     │...\n  272│        // nothing — the slot is ephemeral control, a restart correctly drops it).\n *273│        // `spt shell drive` writes it, `api drive-poll --link` takes-and-clears it.\n  274│        // [impl->REQ-SHELL-3]\n  275│        let drive_hub = Arc::new(crate::drivehub::DriveHub::new());\n  276│        {\n     │...\n  929│/// the real per-rung launchers and log which rung won. The caller still confirms\n *930│/// readiness via the existing `brain.ready` poll — the returned pid is for\n  931│/// logging/return, not readiness (WMI/schtasks pids are the launcher's view).\n  932│// [impl->REQ-HAZARD-VIEWER-CLOSE-DETACH]\n  933│#[cfg(windows)]\n     │...\n 1185│/// is recoverable from schtasks → returns a sentinel; readiness is the caller's\n*1186│/// `brain.ready` poll. This is the FALLBACK rung (WMI is primary); its `/tr`\n 1187│/// quoting is best-effort.\n 1188│// [impl->REQ-HAZARD-VIEWER-CLOSE-DETACH]\n 1189│#[cfg(windows)]\n     │...\n 1225│    }\n*1226│    // No pid from schtasks; sentinel — readiness is the brain.ready poll.\n 1227│    Ok(0)\n 1228│}\n 1229│\n     │...\n 1299│    // and caps at NET_RETRY_CAP — so a long boot-race wait settles to a steady\n*1300│    // 30s poll, never an ever-growing or zero-length spin.\n 1301│    #[test]\n 1302│    fn net_retry_backoff_doubles_then_caps() {\n 1303│        assert_eq!(net_retry_backoff(NET_RETRY_FIRST), Duration::from_secs(2));\n## brainproc.rs\n  279│        // DRIVE the broker's controller-liveness reap every heartbeat (UPDATE-WEDGE\n *280│        // fix, counter-54). A `KIND_SESSIONS` poll is what runs the broker's\n  281│        // `reap_dead_controller` sweep (broker.rs), which stall-evicts a black-holed\n  282│        // LOCAL controller conn a HARD-KILLED prior brain generation left behind (its\n  283│        // hosted PTYs keep writing to a dead pipe → the broker-side writer BLOCKS, never\n *284│        // EOFs). Nothing else polls `KIND_SESSIONS` during an isolated brain-trial\n  285│        // window, so without this the wedge never drains: the promotion DRAINED gate\n  286│        // (`old_gen_drained()` = `!any_local_controller_wedged()`, a PURE READ that never\n  287│        // drives the evict) stays false for the full trial → \"alive but never ready\"\n     │...\n  672│/// semantic) and promotion waits for `env.old_gen_drained()` too. While ready-seen\n *673│/// but not-yet-drained the watch keeps polling to `deadline`; if the drain never\n  674│/// comes the window elapses to [`TrialStep::WindowElapsedAlive`] — the caller KILLS\n  675│/// the candidate and rolls back (conservative: never a false-promote onto a wedge).\n  676│/// The normal case drains well inside the window — W2 bounds a real drain to\n     │...\n 1322│    /// re-spawned (the `already_started` latch) — so a single consumer set runs,\n*1323│    /// no matter how many heartbeats poll `net-status`.\n 1324│    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 1325│    #[test]\n 1326│    fn consumer_gate_spawns_once_when_net_enabled() {\n     │...\n 1461│        // before; `u32::MAX` = never drains within a test window; a small N = drains\n*1462│        // after N polls (the ready-seen latch holds across the undrained polls).\n 1463│        drain_countdown: AtomicU32,\n 1464│    }\n 1465│\n     │...\n 1493│        /// Arm the DRAINED gate: `old_gen_drained` returns `false` for the first\n*1494│        /// `polls` calls, then `true` (0 = always drained; `u32::MAX` = never within\n*1495│        /// the test window). Models the OLD-gen control plane draining after N polls.\n*1496│        fn drains_after(self, polls: u32) -> Self {\n*1497│            self.drain_countdown.store(polls, Ordering::Relaxed);\n 1498│            self\n 1499│        }\n 1500│\n     │...\n 1544│            // Countdown: false (still draining) while > 0, decrementing; then true.\n*1545│            // Single-threaded per env in these harnesses (the supervisor polls\n 1546│            // sequentially), so a plain load/store is race-free here.\n 1547│            let left = self.drain_countdown.load(Ordering::Relaxed);\n 1548│            if left == 0 {\n     │...\n 1673│    /// (DRAINED gate — latch releases) A ready candidate whose old generation drains\n*1674│    /// AFTER a couple of polls promotes: the ready-seen latch holds across the\n*1675│    /// undrained polls (a ready-then-exited candidate is not lost while waiting) and\n 1676│    /// promotion fires the moment `old_gen_drained()` flips true — never a premature\n 1677│    /// rollback, never a false-promote.\n 1678│    // [unit->REQ-UPDATE-PROMOTE-DRAINED]"},"isError":false,"timestamp":1784271950615}}
{"type":"message","id":"b81ebc62","parentId":"774b0cf5","timestamp":"2026-07-17T07:05:54.699Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Adding DEFAULT_DISPATCH_POLL line**"},{"type":"toolCall","id":"call_V9fpkgNxQv9UmTXrownG2qYg|fc_0cb3467658e49868016a59d450fa0c8197a1930381d4d45e65","name":"grep","arguments":{"i":"Finding default poll value","pattern":"DEFAULT_DISPATCH_POLL","path":"crates/spt-daemon/src/dispatch.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":11465,"output":73,"cacheRead":82432,"cacheWrite":0,"totalTokens":93970,"reasoningTokens":12,"cost":{"input":0.05732500000000001,"output":0.00219,"cacheRead":0.041215999999999996,"cacheWrite":0,"total":0.100731}},"stopReason":"toolUse","timestamp":1784271950668,"responseId":"resp_0cb3467658e49868016a59d44f18a88197bff1f702a5e37313","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0cb3467658e49868016a59d45048548197ad7249d3e753b0ca","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRQZi0VNgIDMZS2sCBm7-9gQ7F9o_P2fmbwxGnAe-Rk7CUwCdUyUvT5NEsSfro08N9kEnbq2EdVeovBZDVTBYIefBy4jq5LdExdxnws1b_r0qitPYqPlvnFpTUNqCeN4dKyIGE3YwZDu-XVRwnWFbND0YeEFvitRvKacceCPS_eda235OgD8ob7vdPB5GrfPVtI3YlNUWLKze4H_TfLa0ZZyGozrVltoM55PEEsN48bdA604bn1Y0Hsd2HLi05ovZPxQiQsk92vabvShsimPJ3zUrgRa2Eb3vbuAnH0EBcrxHNYMZPbjqd02dpSXhWnUYMrogafm6w8yOK0sU99yiv60i--I49Qu6O_Hrn3UceFQ83h4H2CWCqol49kBu3KIHk9YKR5gUUXcEEzVtzFhnZ1tiQNcWxnfvlgKrCF7nyUHnjJ8j9QnwXtqm76kJAiKwYVI0hsPO3uBnDHhSfjytwKfM98opFwJNawckhIbmw1q_VgpJeZXB8s999_YVTSVs0oYs5zLOgyPp2sW3Q3TLVez-ILoa6TCdq6jqY8YZ-u0ofjDarp2aofqFv8oT5dXryxybEPaj8zE34N2sX8c0_JfW7ql4y8At0kOx2RP9KG9me0bPzsdwQeyyhn92CeSd6g90pKZC9iQZQtVY2DJniuP8GMbUKM86lloWEeAGG4ruyWL1Yb5fWTjg-SfaKXWQoSxfFGAIwoesx7QFC9xcPW-xCh1hMEHRW7uCpJe3R6phpLdOEq0O80y5yoAijh3eurQ3csDrgluYzCXiTOpK54l5RqogMirYWLhDbt2oZtMgMJYp_wJgAcmYtTriARN37AjGjugGp-4MycBYl6wHukJmPnxBrYA7npQ1sbigrfdf1k9tv7jgg2slrWJI6gWbh513iWkRM5SW2-yk8-ojCIdr9MIcN5_0jYIo10NdSy3l6LGQ1ejk6VToNi4y61Rec4XOXYtXvQvyLkfpG49NVY8Q==","summary":[{"type":"summary_text","text":"**Adding DEFAULT_DISPATCH_POLL line**"}]},{"id":"fc_0cb3467658e49868016a59d450fa0c8197a1930381d4d45e65","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding default poll value\",\"pattern\":\"DEFAULT_DISPATCH_POLL\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_V9fpkgNxQv9UmTXrownG2qYg","name":"grep"}]},"duration":4030.6723000109196,"ttft":1654.0951000005007,"contextSnapshot":{"promptTokens":93897,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_V9fpkgNxQv9UmTXrownG2qYg|fc_0cb3467658e49868016a59d450fa0c8197a1930381d4d45e65","toolName":"grep","startedAt":"2026-07-17T07:05:54.700Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Finding default poll value"},"id":"f415f866","parentId":"b81ebc62","timestamp":"2026-07-17T07:05:54.700Z"}
{"type":"message","id":"94983e29","parentId":"f415f866","timestamp":"2026-07-17T07:05:54.704Z","message":{"role":"toolResult","toolCallId":"call_V9fpkgNxQv9UmTXrownG2qYg|fc_0cb3467658e49868016a59d450fa0c8197a1930381d4d45e65","toolName":"grep","content":[{"type":"text","text":" 86|/// workers block on their stream's event feed).\n*87|pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n 88|\n 89|/// What protocol a peer-initiated stream speaks, by its first record's shape.\n 90|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n...\n 1204|        if let Err(e) =\n*1205|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206|        {\n 1207|            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208|        }"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":2}],"truncated":false,"displayContent":"   86│/// workers block on their stream's event feed).\n  *87│pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n   88│\n   89│/// What protocol a peer-initiated stream speaks, by its first record's shape.\n   90│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n     │...\n 1204│        if let Err(e) =\n*1205│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 1206│        {\n 1207│            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 1208│        }"},"isError":false,"timestamp":1784271954704}}
{"type":"message","id":"fc8c5fb5","parentId":"94983e29","timestamp":"2026-07-17T07:06:01.539Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing production configuration cadence**"},{"type":"toolCall","id":"call_GQXpCt0lP5AMgU7s1Tg2PZ8w|fc_0e52f3011c523270016a59d457f7948193be159cb849fd37ea","name":"grep","arguments":{"i":"Finding registry cadence default","pattern":"registry_pump_period|DEFAULT.*REGISTRY|30","path":"crates/spt-daemon/src/config.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4521,"output":98,"cacheRead":89600,"cacheWrite":0,"totalTokens":94219,"reasoningTokens":32,"cost":{"input":0.022605000000000004,"output":0.00294,"cacheRead":0.0448,"cacheWrite":0,"total":0.070345}},"stopReason":"toolUse","timestamp":1784271954751,"responseId":"resp_0e52f3011c523270016a59d4535f908193bc20f27752a1b6af","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0e52f3011c523270016a59d456a92c8193be3916ea52fb27ae","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRXuCP2mWnf9kofkqtpirPVqRloMfXhCpxjnjQ3DlYS-TNGHQn3gHErr4423q_5t02xaxnSCIDix-29JjpAKqDS_iZTESCHHcVhUGbEe0Cs7_ewbIRxtreMpn7H5s4ZtRLCr4yrXYVhETq9v5EKFp-YpZj3MufGak-XZd0gAgKtBLgYOkhwrhpRwL2z00xYiedCuXUV3n5HJYFVA0kpWL_K9DnzEHAZ3UwThVusEGRUVocDvfaliBwCXy3FaHJII_EjUUtmOgDDeTFwlsseBpatDEmEP5F0Ujbq7cY4ats-zak2HBFBsaiCTT3ZGM-b4SK-gZr2SbyJUt8EpiUyrufM3gAiVONEbnzH5bajpSQzFdPgKD_ZzCBaGB6maE6MlNFCkKsgufH2xSaTMrZYMjoUQ2Bg5I2VowpCdsH8FSQLmWFlCfZ7TL4Ehl5IvgrjKsv7LJiAiTCW47N7NXOb5TGvPn9Ct-rT7iWAQ0uPjMWxLyNjAkWhbKwYO_qOIqxp0l_fJQuR2RF1F9AjnIW2geVCrM0imnungtQdC5FdfHccLB-GZEq9qK5Pvgs3JKCTWo4LE8w8Nmi8ZYrBeFJV6bzHEDqztAvUM3r-AltJKbWwTWMagJ7WQULF4uC01Jz_7H30ehlzDDqqV6v-aUSYhBQrqFfjb571Wqz1MtF2arwhDSh3TT8JqGbBKCWIPkw8g6W_Mx7L2djSahNaWUMqFbU4Fl5vcGteq2e5w5PPAHH2S6fuqdbZ9HXiIlLzxiFHC1kRQ9K7zU12_QkDM8eIonYNgC0NzffEegyc9_0cse76cpU0K0TsALixt3yMZ8pmzEcDUYi8BxqRxZCTm3wZJmR0jzYpGm7OTOvdRHVz93qqCdKSFylNiwawOVJ1AHqWj0wXKvYHKYISv8OkCTuSeg74F0wG3qPbFtCHredh4u3Xk7RTUzWNNMop9q1uPCMPRS0UneOcR3BBN-TDM5waRM4IoOsHw0ffCCk8xmcgihouc2wmXieV1RMm83amJzVBpg2lqXCo8riM5XnnWJwWH66EzwVEepKFIOg2wrbkwljW35BtfIpTrnwWxcaRcabyjIPM","summary":[{"type":"summary_text","text":"**Assessing production configuration cadence**"}]},{"id":"fc_0e52f3011c523270016a59d457f7948193be159cb849fd37ea","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding registry cadence default\",\"pattern\":\"registry_pump_period|DEFAULT.*REGISTRY|30\",\"path\":\"crates/spt-daemon/src/config.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_GQXpCt0lP5AMgU7s1Tg2PZ8w","name":"grep"}]},"duration":6788.348199993372,"ttft":3981.4311999976635,"contextSnapshot":{"promptTokens":94121,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GQXpCt0lP5AMgU7s1Tg2PZ8w|fc_0e52f3011c523270016a59d457f7948193be159cb849fd37ea","toolName":"grep","startedAt":"2026-07-17T07:06:01.540Z","args":{"path":"crates/spt-daemon/src/config.rs"},"intent":"Finding registry cadence default"},"id":"60f98b7b","parentId":"fc8c5fb5","timestamp":"2026-07-17T07:06:01.540Z"}
{"type":"message","id":"7f2388b9","parentId":"60f98b7b","timestamp":"2026-07-17T07:06:01.543Z","message":{"role":"toolResult","toolCallId":"call_GQXpCt0lP5AMgU7s1Tg2PZ8w|fc_0e52f3011c523270016a59d457f7948193be159cb849fd37ea","toolName":"grep","content":[{"type":"text","text":" 38|/// driver, so a missed tick costs only latency, never correctness.\n*39|pub const DEFAULT_REGISTRY_PUMP_PERIOD: Duration = Duration::from_secs(30);\n 40|pub const DEFAULT_NOTIF_PUMP_PERIOD: Duration = Duration::from_secs(15);\n 41|pub const DEFAULT_SYNC_PULL_PERIOD: Duration = Duration::from_secs(60);\n 42|pub const DEFAULT_UPDATE_CHECK_PERIOD: Duration = Duration::from_secs(3_600);\n...\n 49|/// stops poisoning bare-id resolution within minutes.\n*50|pub const DEFAULT_REGISTRY_EVICT_AFTER: Duration = Duration::from_secs(300);\n 51|\n 52|/// Which relay stance the broker's net endpoint runs (ADR-0002 / REQ-NET-2) —\n 53|/// the `\"relay\"` knob in `daemon.json`. Maps onto `spt-net`'s `RelayPolicy` in\n...\n 104|    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n*105|    registry_pump_period_ms: Option<u64>,\n 106|    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n 107|    registry_evict_after_ms: Option<u64>,\n 108|    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n...\n 153|    /// Registry feed push cadence (D9-1).\n*154|    pub registry_pump_period: Duration,\n 155|    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10): rows from a\n 156|    /// node not heard within this window are evicted on the next registry\n 157|    /// pump tick — a dead node identity must not poison bare-id resolution\n*158|    /// forever. Default [`DEFAULT_REGISTRY_EVICT_AFTER`].\n 159|    pub registry_evict_after: Duration,\n 160|    /// Notif feed push cadence (D9-1).\n 161|    pub notif_pump_period: Duration,\n...\n 210|            peer_pump: true,         // the subnet self-drives (D9-1)\n*211|            registry_pump_period: DEFAULT_REGISTRY_PUMP_PERIOD,\n*212|            registry_evict_after: DEFAULT_REGISTRY_EVICT_AFTER,\n 213|            notif_pump_period: DEFAULT_NOTIF_PUMP_PERIOD,\n 214|            sync_pull_period: DEFAULT_SYNC_PULL_PERIOD,\n 215|            update_check_period: DEFAULT_UPDATE_CHECK_PERIOD,\n...\n 270|            peer_pump: file.peer_pump.unwrap_or(d.peer_pump),\n*271|            registry_pump_period: file\n*272|                .registry_pump_period_ms\n 273|                .map(Duration::from_millis)\n*274|                .unwrap_or(d.registry_pump_period),\n 275|            registry_evict_after: file\n 276|                .registry_evict_after_ms\n 277|                .map(Duration::from_millis)\n...\n 314|            peer_pump: Some(self.peer_pump),\n*315|            registry_pump_period_ms: Some(self.registry_pump_period.as_millis() as u64),\n 316|            registry_evict_after_ms: Some(self.registry_evict_after.as_millis() as u64),\n 317|            notif_pump_period_ms: Some(self.notif_pump_period.as_millis() as u64),\n 318|            sync_pull_period_ms: Some(self.sync_pull_period.as_millis() as u64),\n...\n 400|            peer_pump: false,\n*401|            registry_pump_period: Duration::from_millis(11_000),\n 402|            registry_evict_after: Duration::from_millis(16_000),\n 403|            notif_pump_period: Duration::from_millis(12_000),\n 404|            sync_pull_period: Duration::from_millis(13_000),\n...\n 458|        let path = dir.path().join(\"daemon.json\");\n*459|        std::fs::write(&path, r#\"{\"auto_suspend_after_ms\": 300000}\"#).unwrap();\n 460|        assert_eq!(\n 461|            DaemonConfig::load_from(&path).auto_suspend_after_ms,\n*462|            Some(300_000)\n 463|        );\n 464|        // An absent file keeps the OFF default.\n 465|        assert_eq!("}],"details":{"scopePath":"crates/spt-daemon/src/config.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\config.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":14,"fileCount":1,"files":["crates/spt-daemon/src/config.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/config.rs","count":14}],"truncated":false,"displayContent":"  38│/// driver, so a missed tick costs only latency, never correctness.\n *39│pub const DEFAULT_REGISTRY_PUMP_PERIOD: Duration = Duration::from_secs(30);\n  40│pub const DEFAULT_NOTIF_PUMP_PERIOD: Duration = Duration::from_secs(15);\n  41│pub const DEFAULT_SYNC_PULL_PERIOD: Duration = Duration::from_secs(60);\n  42│pub const DEFAULT_UPDATE_CHECK_PERIOD: Duration = Duration::from_secs(3_600);\n    │...\n  49│/// stops poisoning bare-id resolution within minutes.\n *50│pub const DEFAULT_REGISTRY_EVICT_AFTER: Duration = Duration::from_secs(300);\n  51│\n  52│/// Which relay stance the broker's net endpoint runs (ADR-0002 / REQ-NET-2) —\n  53│/// the `\"relay\"` knob in `daemon.json`. Maps onto `spt-net`'s `RelayPolicy` in\n    │...\n 104│    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n*105│    registry_pump_period_ms: Option<u64>,\n 106│    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n 107│    registry_evict_after_ms: Option<u64>,\n 108│    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    │...\n 153│    /// Registry feed push cadence (D9-1).\n*154│    pub registry_pump_period: Duration,\n 155│    /// Silent-peer registry eviction window (KNOWN-HAZARDS 4.10): rows from a\n 156│    /// node not heard within this window are evicted on the next registry\n 157│    /// pump tick — a dead node identity must not poison bare-id resolution\n*158│    /// forever. Default [`DEFAULT_REGISTRY_EVICT_AFTER`].\n 159│    pub registry_evict_after: Duration,\n 160│    /// Notif feed push cadence (D9-1).\n 161│    pub notif_pump_period: Duration,\n    │...\n 210│            peer_pump: true,         // the subnet self-drives (D9-1)\n*211│            registry_pump_period: DEFAULT_REGISTRY_PUMP_PERIOD,\n*212│            registry_evict_after: DEFAULT_REGISTRY_EVICT_AFTER,\n 213│            notif_pump_period: DEFAULT_NOTIF_PUMP_PERIOD,\n 214│            sync_pull_period: DEFAULT_SYNC_PULL_PERIOD,\n 215│            update_check_period: DEFAULT_UPDATE_CHECK_PERIOD,\n    │...\n 270│            peer_pump: file.peer_pump.unwrap_or(d.peer_pump),\n*271│            registry_pump_period: file\n*272│                .registry_pump_period_ms\n 273│                .map(Duration::from_millis)\n*274│                .unwrap_or(d.registry_pump_period),\n 275│            registry_evict_after: file\n 276│                .registry_evict_after_ms\n 277│                .map(Duration::from_millis)\n    │...\n 314│            peer_pump: Some(self.peer_pump),\n*315│            registry_pump_period_ms: Some(self.registry_pump_period.as_millis() as u64),\n 316│            registry_evict_after_ms: Some(self.registry_evict_after.as_millis() as u64),\n 317│            notif_pump_period_ms: Some(self.notif_pump_period.as_millis() as u64),\n 318│            sync_pull_period_ms: Some(self.sync_pull_period.as_millis() as u64),\n    │...\n 400│            peer_pump: false,\n*401│            registry_pump_period: Duration::from_millis(11_000),\n 402│            registry_evict_after: Duration::from_millis(16_000),\n 403│            notif_pump_period: Duration::from_millis(12_000),\n 404│            sync_pull_period: Duration::from_millis(13_000),\n    │...\n 458│        let path = dir.path().join(\"daemon.json\");\n*459│        std::fs::write(&path, r#\"{\"auto_suspend_after_ms\": 300000}\"#).unwrap();\n 460│        assert_eq!(\n 461│            DaemonConfig::load_from(&path).auto_suspend_after_ms,\n*462│            Some(300_000)\n 463│        );\n 464│        // An absent file keeps the OFF default.\n 465│        assert_eq!("},"isError":false,"timestamp":1784271961543}}
{"type":"message","id":"32a80922","parentId":"7f2388b9","timestamp":"2026-07-17T07:06:16.056Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Estimating stream object memory usage**\n\n**Analyzing process I/O discrepancies**\n\n**Confirming absence of broker write compression**"},{"type":"thinking","thinking":"**Inspecting worker round scheduling**"},{"type":"toolCall","id":"call_GNM7MMpW4VueEKhAWKRCP7yZ|fc_047ad50e444c37b9016a59d466a7388195bc225ef1a77aa86a","name":"read","arguments":{"i":"Reading pump scheduler marking","path":"crates/spt-daemon/src/pump/mod.rs","selector":"570-770"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5558,"output":633,"cacheRead":89600,"cacheWrite":0,"totalTokens":95791,"reasoningTokens":591,"cost":{"input":0.027790000000000002,"output":0.01899,"cacheRead":0.0448,"cacheWrite":0,"total":0.09158}},"stopReason":"toolUse","timestamp":1784271961586,"responseId":"resp_047ad50e444c37b9016a59d459ee5881959afdec72a9de5b93","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_047ad50e444c37b9016a59d45b8390819599079208fb592a25","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRlmO9TvYPnbepk0yxME6Zamcblj1HOn4cPXPD7jQSGuCuyCWz1ImoJye4j6reQ6z78XUsQ7LbSIAbSbkvSZ2IA9OD85-kE9Pf_eVXCZI_HanLy0D7nguxRji4k7i0uWetDFP0r6dEVqshlu6ELI62W0uwI786idcVVl0mPikwX9SB6QQMkT2pOym0XLNUEbtwGqhWBAmJWKUN-CpHOYmgGOaT8vCSMAYMv8msirVMW9dlbsxv0PliE0f-QBPO1ghFLmZV9yblKdFPinIeVn1JcuBswt3-eR50SqO0JJU0OLwhUZ9lDTMo2LBirCviyfsFcIBXhbBXuEMV0Ozuq0TgzjpLCrmIrqHapgKlYVTnjpzV8iHN8gd0sO35lompsnrWtNjnvnFVulNR8LaVobNzlOPy5TU6xgrvnJJY3sxYzUgQS6uCas3H716KnytTHraiQWduYhW9A9-lmTP-LBeUndD89DvL6EGBlbL_di3V6DqtJDqb82SOdI2nkgur9dMTUArcDhtI-z-hl-zMTNGWpF_-w2CfJpfbj8awh7uv6ldbeAz4QXEViM047XwNsdZDsm4JS6GycxG2VYuveIGk_7lh4cxJtHrK25O2d5sNpu8jwI0CWaUxxDxcgnZ4Kpo682qqm1uTUznCur8IpZ3VL-3PxJ0WbcgWFzX2J2E6eDZb6mIuB6U-aXDh48e5et78eVhpsh2zIsZuENI6ZqYvAZnL0MvdnOdIVw6kEgyFGcjYlShjms_BsVa48rUB_E8nvYULxNGVmwwtplAGoB5juvfe694J2Io7uxJCmgDil0cPafC9MQsnRXISOEO_wDKv4Xet1_snvbl_WE7cESzjTnq5R-GdW72vzcRPfkV-sLox9R1DGt88PTW2rO0x4Y3tufpO-a6VjnsjRKJnObJhvwCKl-I4DXZw9BmPHQQiluwQcLxU8m3gZ5U1fQxkGPKcRKA_XXP1ahJStoF2KN74QNzvOQquFIv7ivXzDDRjM226ox-tgzhulPpR_pi8QZUXw3S_vDpT0n_qmlhbv0q_n5ofs776G8ahRz0ArP1--o6f3zkb15HFEMMMpq5emMfLCrZ6ftz54Sm6MyjmU11Vc_K4KvOucx3rgmAO6mMwArHAC9eMOgrmglSZEapuoZ17nn6ElB5wYBCoWpwWz2bnHkBqZ_f1QLudYCUXDeoQzirVnspqKVG-bONMamrpdu7SbMrgMGMsg8MRgHHIIe-JKmElm1pycSFTsfUdFHO9lO5eastaW7KeQ6kTkPyVXDGjVbmkYxqbf-P_4BBxIpw5hU5XTul6gbn18GAOj3f8qsatb4zzCr8xuGzJRFPYSnyafWQ9UoVzE2pGybkToi2F5oQgkvMEmvVL6JDr2t0Ori6Yg1wQNbpEL89-CxFj4rPnrpYDf7-Odyk_DLy4cdkhAyGvemjGD-DjEpAWlg0TUB0ga0BadlvubSOQAG86Ai08hb_p5nQTO0NwPAkjuNGnKBCUmnxCunK3j0LNb75qT0B0b1Sz-DABffLyh7ytgZHI8IJcC-HA_ueweG7O0JOPcYiBaBqfkWo_h-Mx1ln5-VKE8zaHoc4vKq-JOGGBWBSgyqKys-KzeHAgI8WI_x9RoteyC42fKQuT2878XBokSVICLi6N5LkoAwhYBh5bGz-LpN4iAwtEwOIjLpJIvhqIVbY59fqu_LnV2HEtWkCb3R4P7wlTMD1DwmoCqhEuI6HF0Bafz7HQAuAO5L6_SQCwPFvxIGZaSshCt752O1uYsGVXTNThfwAq3fvBGz0_jhMbkmD7EeE3nKi7xw30iBXcnWst2WphMmN6o-lfXcpFMgzr7qhdLGzdA3uNRBy4YJbNrYhSce8AE5dqAKMI9rglZ25qtlERzgwuqKvbkkC1eMThkPl9R4VTo68sFvB2FZvNQHUkPaLkpJJKjEHqUx2gTaT7f2Yy91ROiWyLBnLdLzfArHZoS03wVGmgbrvL6z6qZbSfi2nvw4KSchCP6mD71bW9oMGJql_4zDa7D9SuESdbnxgt_qURvvx1X7ru_SE_GgWiO-Kn8MyqH5jMDZUcmawp0QPRSLArIcU7XVfmSoxWn6HXLK0kYW4vFyI3lsWFf-XbfK_1yV535Ki9WantdnIBTyU3MfwD9ehS-PSbde-EZEKV8-XLmpvCNsm2s6frzaP1B-z5t3JQNBn1RkrbZPLjgc1Jvb2j-lyAjdpWiR28Uxk7yMz4uvyhk2N96poRxQB0pGPWDBqvxdP_7R1tpB08K4N-itNDbLX6tD2o1A0Z8CKpWgSUwRDtHSE-gT0mXY-LiSHpbF0JE260OoJFaHpWGBvnNbC7-PYlQZSIxhJFT_kEHp73VW2S6n-KBwU0Rxnie39FpDzUHDP6RlSq44-TpzBL4C_Q5ar8OUwKaGbjfSsFcc011BN9GNqHinbff_Hi19DG-V1HbjdkfU--runA0UYhfs_Jsas9a3YcuxCieeoL2boAYt67J_ZxyTQeVJe5agMBtoSTstdEBLi9SjEcF-Yd5NcVdbLcfE0_9qT4TfsVMNKAB2oz9QakCvhDm15c4On9d8yKq4i-z1YlezMrc9Gi8-mh1i1ebQIrz2aVuLaq0EC2zUWYYYFNGxDwid5xA3hiBQ2Sq45KADlAG6DfM1xzx4DVzStReb7LCkr63asmq4XwclKd8TQ7vxTDrqBMgVy1gJoWm85GzUY2KRsQdk4rIlOEjr_S609z5uv_q2QNXo_xhPXoyDknfPRDEPR5TUDVfwwvTWjVS-1-3EOL623FVEGH7OdUA0d9u8fqSbaTNKFvk7ePRPp4xMA7fmJebyWlRJO_P2fJckL9wN-ebqlJHL_oDQXqRQXq-ZlV7U-As6nhm91z2uuiMwgbSsrwin41NoV0ussL88zlZrhh2TBtGRsTDBd9bkovGnflgspTTA6Z3SYGXO8u-_mbepD_MrDCYFvaG3tjLmLFchfVXo8OwplHeDSpFloScxfV66iBa1Oj3CVCMARhiupaad1yyjOTXl0vSYbkf25Tc73GWwImmmCs4Pi9ca0XcqYIXXSWQ1HHoYbV6XfExNsuof9Vpt5GsRFpQv2WbO4ZJ2n4ZeRDPEUE0wqiRvK62mcO8brkifn9lkZB5JAavoAmjn4bBGqlMny5OubSHKSdvO0FP2dYGhjUjsIm-wG1-cxCAClPwuWO3GMRuYc0eKx2A1m7kDSRGcitOxn2NpgBYguJc-UxzIvA3zoXS6da7xA4HBz5AxLc-U2J_yLwz0G9XbUQobM_ur2Ls-zYpDkdC7hXtJCy5WDTKOJqn57CkU6GnJJGds3zKbV_v5kp2YDI_Sk9nA-TUAwLHWzbOxGHp2YW_UvYILAD8auYE79s5X-6Wi56JPXW7q-f8zckzV2xydwg4vtLFF_cJ73xbaqW_q5EBJo3UiFTMOEmFOGcCwhYBoRblBF6S7tz5OgPXi7yp2NIe_3-OtP3uM8IpkyG98C3MDNem-rGAlIq2SJyRN6DQMflxigLO9hbU23RqroYehlV5GASjugopSl_XP6mSaf_CZUBG4zzToGXEOaJX7MiGEEcBN4iWn_xA4_6ANtfaCdCgq1N--W0fiBqpvmmw8dBameloPqNUTOO-dEPH3NOBNHIAaV_Daa71czgQdv2MUSs7ejk7XGyPDN_2bBctwQ==","summary":[{"type":"summary_text","text":"**Estimating stream object memory usage**"},{"type":"summary_text","text":"**Analyzing process I/O discrepancies**"},{"type":"summary_text","text":"**Confirming absence of broker write compression**"}]},{"id":"rs_047ad50e444c37b9016a59d4653eb8819593be8dbdf318ab96","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRm7JGeCwTNquoq9ILpL6qc7t5a3y0syvktzGkGkFwp40breQ3Sm1WdZCpENVg9G5DKXnQOr4tlMVi8WCEemB2J8mcV1P8pZ6HFr_hX_GuEbqo38tmb3zj100oQcFOdg_9G9MFErrxhzdEGcjy5DnXIc8xFnArc8IR6OWOnvf2KOc9q2KRg9BB8U0bxinjEsx5CPEIzkFihECCA-eqd_5y9o9Ua_uC5o9aA5giUP_sbObPciykbJHtN5plh3taS3ElyNq4hqULXenvCwp2dfQN9yJg3Mncmqydvxl-qcxgNSj1ZKaCQeCCBeb_Rz9x21wWNzEG_b5YR3rvu1hWe3-fhyVTFroGmT9NSXmjr2n2pzLubRt39j5_OgUqwbZ70lpW11AC-5Mxjkem1PWsXha24_80whbwyJXx7Vp218sD_LLfvShnmoGermigeYx9L_W3-lOCFKhvDLEjMpbTCLpfExggxfZj0We0EGiHTka60wSgZXd95po05CbAF8gn_I7xN3UlUvjMUiBjS4VW39Y3cDSYUZyDE1oqvMSascbHBpL1xsCf91nDMjJpd3b8advmNFeGr3Mhe1ibDUDSN-hbqDt84Wf83Mt6ya_eFHHNVYC80M5SJGpQCOIsj98lJ-9015kt5kTVrshs-pwLWr5iHHtd4uQRqk1NfIfCk7xmJ3VvNpicjaHX6viNqvVXo8KPFh1y3Rrrbi1A1NAGxRrTsf-d0UcL6V5cVsM5o4TNL6UHyUzxnJ5TFKSbDUXFiNF0p_uVMqytAawSNYI7hFiq-fCBqis4Om4EbW7Bi5baCWq2SflqXEwubyp6C2ifYLWeD4jTW3y5CDSzDjE2DBVGvKZC8tSQESdhuqDEyPfN4ea_KafPubA0JAYpW5BlkkaorHfbelbRACW_hQGVgcAmjXfeITKxcLqp1pX7vLB8t4pgoZtLhiF8OnOF64p8XeCbTWdzKWNnyECUpgcsir0BuWgUFa9IlrClwp6rAY6hJnxY-8FtUWyrhItd0HG2fO-fBxPAb6oTY1YrMoE-HA6XAlKNpnQe5f_hmwMHHJilnx0u3HuqOx5q833jM-k4MwmT1ghSeh9_pjyBISjU2yMdZ88s3xwDrj7gJWIrzahQTaZA5k-t7kPGj4LhaWg3UT5PCmIeMT3R3aY8wRD1mbzoUumWepCLggo_0OSzhyO4cmp0G7IaKZXoq9-4_zFwMdZgUlFso5zzueZuo0YX4v907R547dJOqXnqT4dtSSkpxw7AEj38cwfr_LafmETo0O9PGZahfykePeCpVlOkLlWTlTv0uzHdA9UI-axe31OQwIYg=","summary":[{"type":"summary_text","text":"**Inspecting worker round scheduling**"}]},{"id":"fc_047ad50e444c37b9016a59d466a7388195bc225ef1a77aa86a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump scheduler marking\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"570-770\"}","call_id":"call_GNM7MMpW4VueEKhAWKRCP7yZ","name":"read"}]},"duration":14469.874199986458,"ttft":1944.0647999942303,"contextSnapshot":{"promptTokens":95158,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GNM7MMpW4VueEKhAWKRCP7yZ|fc_047ad50e444c37b9016a59d466a7388195bc225ef1a77aa86a","toolName":"read","startedAt":"2026-07-17T07:06:16.057Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Reading pump scheduler marking"},"id":"cd366cca","parentId":"32a80922","timestamp":"2026-07-17T07:06:16.057Z"}
{"type":"message","id":"b4503178","parentId":"cd366cca","timestamp":"2026-07-17T07:06:16.074Z","message":{"role":"toolResult","toolCallId":"call_GNM7MMpW4VueEKhAWKRCP7yZ|fc_047ad50e444c37b9016a59d466a7388195bc225ef1a77aa86a","toolName":"read","content":[{"type":"text","text":"pub fn run_peer_pump(\n…\n) -> io::Result<()> {\n…\n    let mut conns: HashMap<String, u64> = HashMap::new();\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\n    // The four cadenced legs behind the worker seam, in fan-out order\n    // (registry → notif → sync → update). The shell drives them generically;\n    // each leg's *what* lives in its module (§Frozen). Construction capture\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\n    // knob it needs — the sync worker additionally owns the conflict-surface\n    // hooks (ADR-0013).\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\n    let mut workers: [&mut dyn PumpWorker; 4] = [\n        &mut registry_worker,\n        &mut notif_worker,\n        &mut sync_worker,\n        &mut update_worker,\n    ];\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\n    // `workers`. `None` = never run yet → due immediately, so the first tick\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\n    let mut last: [Option<Instant>; 4] = [None; 4];\n    let mut last_hb: Option<Instant> = None;\n\n    while !stop.load(Ordering::Relaxed) {\n        let now = Instant::now();\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\n        // while the loop is ALIVE — including idle ticks — never per work round,\n        // so a wedged step and a healthy quiet pump are distinguishable.\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\n        // [impl->REQ-DAEMON-5]\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\n            write_heartbeat(&paths.heartbeat);\n            last_hb = Some(now);\n        }\n        // Poll every worker's wake and decide due (the pure scheduler step):\n        // every wake is consumed EVERY tick so a marker is never stranded (the\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\n        // forced round.\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\n        if !any_due {\n            thread::sleep(TICK);\n            continue;\n        }\n\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\n        // roster-backed policy, read ONCE and handed to every worker.\n        let ctx = RoundCtx::load(paths);\n\n        // Each due worker's node-local pre_round, once per round before ANY\n        // peer_step (only the registry leg does work here — its sweep +\n        // advertise; the rest default to a no-op).\n        pre_rounds(&mut workers, &due_flags, &ctx);\n\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\n        // peer may target several subnets; one conn serves them all).\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\n        }\n        // Health targets = this round's fan-out set; snapshot the registry\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\n        health.note_registry_admit(registry.last_admit_ms());\n        let round_start = Instant::now();\n        let mut pending: HashSet<String> = HashSet::new();\n\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\n        // legs NOW for already-connected ones. The live peer advertises without\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\n        // pending outcome, never a serial blocker.\n        for (peer_hex, subs) in &peer_subnets {\n            if let Some(&conn_id) = conns.get(peer_hex) {\n                run_peer_subnets(\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n                    conn_id, subs, peer_hex, &ctx, round_start,\n                )?;\n            } else if peer_eligible(&sched, peer_hex, round_start) {\n                if let Some(addr) =\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\n                {\n                    match brain.submit_dial(addr) {\n                        Ok(()) => {\n                            pending.insert(peer_hex.clone());\n                        }\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\n                        // genuine broker-IPC desync → bubble → supervised restart.\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\n                        // The broker refused the submit (net disabled / bad addr):\n                        // an ordinary per-peer failure — back off, round continues.\n                        Err(e) => {\n                            eprintln!(\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\n                                now_ms(),\n                                mono_ms()\n                            );\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\n                            let prev = sched.get(peer_hex).copied();\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\n                        }\n                    }\n                } else {\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\n                    // `address-resolution`). Transition-logged: the first\n                    // unroutable round per peer is loud, repeats are quiet —\n                    // the peer retries on its own cadence like before.\n                    if health.note_failed(\n                        peer_hex,\n                        crate::seedproofx::STAGE_RESOLVE,\n                        conns.len(),\n                        now_ms(),\n                    ) {\n                        eprintln!(\n                            \"PUMP_PEER_FAIL:{peer_hex}:stage={}: no route (cache/roster/discovery all empty) wall_ms={} mono_ms={}\",\n                            crate::seedproofx::STAGE_RESOLVE,\n                            now_ms(),\n                            mono_ms()\n                        );\n                    }\n                }\n            }\n        }\n\n        // Drain dial outcomes on the DEDICATED event carrier until the round\n        // deadline (concurrent dials all land within ~one W1 bound). A CONNECTED\n        // runs that peer's legs in THIS round; a DIAL_FAILED backs the peer off; a\n        // peer unresolved at the deadline simply reschedules — NEVER a whole-round\n        // poison. `TimedOut` = quiet (drain done); any other read error = the\n        // broker stopped answering the stream (dead carrier) → bubble → supervised\n        // restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still\n        // restarts the pump; a dead PEER never does).\n        // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n        let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;\n        loop {\n            let read_deadline = if pending.is_empty() {\n                Instant::now() // no outstanding submits — sweep only what is queued\n            } else {\n                round_deadline\n            };\n            match classify_drain_read(events.read_event_until(Some(read_deadline))) {\n                DrainStep::Apply(ev) => handle_presence_event(\n                    ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\n                    &mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,\n                )?,\n                DrainStep::Skip => continue, // the event carrier only carries presence\n                DrainStep::RoundDone => break, // quiet (TimedOut) — drain done\n                DrainStep::Restart(e) => return Err(e), // dead broker/carrier → restart\n            }\n        }\n\n        // Persist the round's reachability truth for the status surfaces\n        // (REQ-PUMP-STAGE-TRUTH: the acceptance surface of this contract).\n        health.save_to(&health_path);\n\n        // Mark-after-round with a POST-round instant (ADR-0018 §V4: the\n        // stagger — ran = ATTEMPTED, not succeeded; the next due is measured\n        // from here, not from the round's start).\n        mark_ran(&mut last, &due_flags, Instant::now());\n    }\n    Ok(())\n}\n\n/// Compute each worker's due flag for this tick (the pure scheduler step):\n/// poll EVERY worker's wake first — even a not-otherwise-due leg, so a marker\n/// is never stranded — then the [`due`] kernel over its RAM-only last-ran\n/// instant. Returns the per-worker flags (parallel to `workers`) and whether\n/// ANY leg is due (an all-idle tick sleeps without loading a round).\nfn compute_due(\n    workers: &mut [&mut dyn PumpWorker],\n    last: &[Option<Instant>],\n    now: Instant,\n) -> (Vec<bool>, bool) {\n    let mut flags = vec![false; workers.len()];\n    let mut any = false;\n    for i in 0..workers.len() {\n        let wake = workers[i].poll_wake();\n        flags[i] = due(last[i], wake, now, workers[i].cadence());\n        any |= flags[i];\n    }\n    (flags, any)\n}\n\n/// Run each due worker's node-local `pre_round` ONCE for the round, before any\n/// peer_step (the registry sweep + advertise; the other legs default no-op).\nfn pre_rounds(workers: &mut [&mut dyn PumpWorker], due_flags: &[bool], ctx: &RoundCtx) {\n    for i in 0..workers.len() {\n        if due_flags[i] {\n            workers[i].pre_round(ctx);\n        }\n    }\n}\n\n/// Run `step` for each due worker index in fan-out order, aborting on the\n/// FIRST error (abort-on-first-fail — the caller drops the peer's conn and\n/// redials next tick). The peer-step IO itself lives in `step`; this is the\n/// pure ordering/abort kernel, so the choreography is a unit fact.\nfn run_due(due_flags: &[bool], mut step: impl FnMut(usize) -> io::Result<()>) -> io::Result<()> {\n    for (i, &is_due) in due_flags.iter().enumerate() {\n        if is_due {\n            step(i)?;\n        }\n    }\n…\n}\n\n[Showing lines 569-773 of 1982. Use :774 to continue]"}],"details":{"truncation":{"content":"    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let now = Instant::now();\r\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\r\n        // while the loop is ALIVE — including idle ticks — never per work round,\r\n        // so a wedged step and a healthy quiet pump are distinguishable.\r\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\r\n        // [impl->REQ-DAEMON-5]\r\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\r\n            write_heartbeat(&paths.heartbeat);\r\n            last_hb = Some(now);\r\n        }\r\n        // Poll every worker's wake and decide due (the pure scheduler step):\r\n        // every wake is consumed EVERY tick so a marker is never stranded (the\r\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\r\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\r\n        // forced round.\r\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\r\n        if !any_due {\r\n            thread::sleep(TICK);\r\n            continue;\r\n        }\r\n\r\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\r\n        // roster-backed policy, read ONCE and handed to every worker.\r\n        let ctx = RoundCtx::load(paths);\r\n\r\n        // Each due worker's node-local pre_round, once per round before ANY\r\n        // peer_step (only the registry leg does work here — its sweep +\r\n        // advertise; the rest default to a no-op).\r\n        pre_rounds(&mut workers, &due_flags, &ctx);\r\n\r\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\r\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\r\n        // peer may target several subnets; one conn serves them all).\r\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\r\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\r\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\r\n        }\r\n        // Health targets = this round's fan-out set; snapshot the registry\r\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\r\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\r\n        health.note_registry_admit(registry.last_admit_ms());\r\n        let round_start = Instant::now();\r\n        let mut pending: HashSet<String> = HashSet::new();\r\n\r\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\r\n        // legs NOW for already-connected ones. The live peer advertises without\r\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\r\n        // pending outcome, never a serial blocker.\r\n        for (peer_hex, subs) in &peer_subnets {\r\n            if let Some(&conn_id) = conns.get(peer_hex) {\r\n                run_peer_subnets(\r\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    conn_id, subs, peer_hex, &ctx, round_start,\r\n                )?;\r\n            } else if peer_eligible(&sched, peer_hex, round_start) {\r\n                if let Some(addr) =\r\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\r\n                {\r\n                    match brain.submit_dial(addr) {\r\n                        Ok(()) => {\r\n                            pending.insert(peer_hex.clone());\r\n                        }\r\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\r\n                        // genuine broker-IPC desync → bubble → supervised restart.\r\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\r\n                        // The broker refused the submit (net disabled / bad addr):\r\n                        // an ordinary per-peer failure — back off, round continues.\r\n                        Err(e) => {\r\n                            eprintln!(\r\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\r\n                                now_ms(),\r\n                                mono_ms()\r\n                            );\r\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\r\n                            let prev = sched.get(peer_hex).copied();\r\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\r\n                        }\r\n                    }\r\n                } else {\r\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\r\n                    // `address-resolution`). Transition-logged: the first\r\n                    // unroutable round per peer is loud, repeats are quiet —\r\n                    // the peer retries on its own cadence like before.\r\n                    if health.note_failed(\r\n                        peer_hex,\r\n                        crate::seedproofx::STAGE_RESOLVE,\r\n                        conns.len(),\r\n                        now_ms(),\r\n                    ) {\r\n                        eprintln!(\r\n                            \"PUMP_PEER_FAIL:{peer_hex}:stage={}: no route (cache/roster/discovery all empty) wall_ms={} mono_ms={}\",\r\n                            crate::seedproofx::STAGE_RESOLVE,\r\n                            now_ms(),\r\n                            mono_ms()\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // Drain dial outcomes on the DEDICATED event carrier until the round\r\n        // deadline (concurrent dials all land within ~one W1 bound). A CONNECTED\r\n        // runs that peer's legs in THIS round; a DIAL_FAILED backs the peer off; a\r\n        // peer unresolved at the deadline simply reschedules — NEVER a whole-round\r\n        // poison. `TimedOut` = quiet (drain done); any other read error = the\r\n        // broker stopped answering the stream (dead carrier) → bubble → supervised\r\n        // restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still\r\n        // restarts the pump; a dead PEER never does).\r\n        // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n        let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;\r\n        loop {\r\n            let read_deadline = if pending.is_empty() {\r\n                Instant::now() // no outstanding submits — sweep only what is queued\r\n            } else {\r\n                round_deadline\r\n            };\r\n            match classify_drain_read(events.read_event_until(Some(read_deadline))) {\r\n                DrainStep::Apply(ev) => handle_presence_event(\r\n                    ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    &mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,\r\n                )?,\r\n                DrainStep::Skip => continue, // the event carrier only carries presence\r\n                DrainStep::RoundDone => break, // quiet (TimedOut) — drain done\r\n                DrainStep::Restart(e) => return Err(e), // dead broker/carrier → restart\r\n            }\r\n        }\r\n\r\n        // Persist the round's reachability truth for the status surfaces\r\n        // (REQ-PUMP-STAGE-TRUTH: the acceptance surface of this contract).\r\n        health.save_to(&health_path);\r\n\r\n        // Mark-after-round with a POST-round instant (ADR-0018 §V4: the\r\n        // stagger — ran = ATTEMPTED, not succeeded; the next due is measured\r\n        // from here, not from the round's start).\r\n        mark_ran(&mut last, &due_flags, Instant::now());\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Compute each worker's due flag for this tick (the pure scheduler step):\r\n/// poll EVERY worker's wake first — even a not-otherwise-due leg, so a marker\r\n/// is never stranded — then the [`due`] kernel over its RAM-only last-ran\r\n/// instant. Returns the per-worker flags (parallel to `workers`) and whether\r\n/// ANY leg is due (an all-idle tick sleeps without loading a round).\r\nfn compute_due(\r\n    workers: &mut [&mut dyn PumpWorker],\r\n    last: &[Option<Instant>],\r\n    now: Instant,\r\n) -> (Vec<bool>, bool) {\r\n    let mut flags = vec![false; workers.len()];\r\n    let mut any = false;\r\n    for i in 0..workers.len() {\r\n        let wake = workers[i].poll_wake();\r\n        flags[i] = due(last[i], wake, now, workers[i].cadence());\r\n        any |= flags[i];\r\n    }\r\n    (flags, any)\r\n}\r\n\r\n/// Run each due worker's node-local `pre_round` ONCE for the round, before any\r\n/// peer_step (the registry sweep + advertise; the other legs default no-op).\r\nfn pre_rounds(workers: &mut [&mut dyn PumpWorker], due_flags: &[bool], ctx: &RoundCtx) {\r\n    for i in 0..workers.len() {\r\n        if due_flags[i] {\r\n            workers[i].pre_round(ctx);\r\n        }\r\n    }\r\n}\r\n\r\n/// Run `step` for each due worker index in fan-out order, aborting on the\r\n/// FIRST error (abort-on-first-fail — the caller drops the peer's conn and\r\n/// redials next tick). The peer-step IO itself lives in `step`; this is the\r\n/// pure ordering/abort kernel, so the choreography is a unit fact.\r\nfn run_due(due_flags: &[bool], mut step: impl FnMut(usize) -> io::Result<()>) -> io::Result<()> {\r\n    for (i, &is_due) in due_flags.iter().enumerate() {\r\n        if is_due {\r\n            step(i)?;\r\n        }\r\n    }\r","truncated":true,"truncatedBy":"lines","totalLines":1414,"totalBytes":10776,"outputLines":205,"outputBytes":10776,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn run_peer_pump(\n…\n) -> io::Result<()> {\n…\n    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let now = Instant::now();\r\n        // Shell-owned liveness heartbeat (M8 decision 23, REQ-DAEMON-5): written\r\n        // while the loop is ALIVE — including idle ticks — never per work round,\r\n        // so a wedged step and a healthy quiet pump are distinguishable.\r\n        // Throttled; per-tick, never a worker (REQ-DAEMON-5), no wake.\r\n        // [impl->REQ-DAEMON-5]\r\n        if due(last_hb, false, now, HEARTBEAT_PERIOD) {\r\n            write_heartbeat(&paths.heartbeat);\r\n            last_hb = Some(now);\r\n        }\r\n        // Poll every worker's wake and decide due (the pure scheduler step):\r\n        // every wake is consumed EVERY tick so a marker is never stranded (the\r\n        // registry advertise-now, REQ-CONV-2; the sync freshness pull,\r\n        // REQ-INST-3). The take IS the remove — one wake claims exactly one\r\n        // forced round.\r\n        let (due_flags, any_due) = compute_due(&mut workers, &last, now);\r\n        if !any_due {\r\n            thread::sleep(TICK);\r\n            continue;\r\n        }\r\n\r\n        // Per-round shared loads (§Frozen-3): subnets / attachment /\r\n        // roster-backed policy, read ONCE and handed to every worker.\r\n        let ctx = RoundCtx::load(paths);\r\n\r\n        // Each due worker's node-local pre_round, once per round before ANY\r\n        // peer_step (only the registry leg does work here — its sweep +\r\n        // advertise; the rest default to a no-op).\r\n        pre_rounds(&mut workers, &due_flags, &ctx);\r\n\r\n        // ── PUMP-TRUTH W2 round (REQ-PUMP-PEER-ISOLATION) — concurrent dials +\r\n        // per-peer fault isolation. Group this round's fan-out targets by peer (a\r\n        // peer may target several subnets; one conn serves them all).\r\n        let mut peer_subnets: HashMap<String, Vec<String>> = HashMap::new();\r\n        for (sub_name, peer_hex) in fan_targets(&ctx, registry.node_hex()) {\r\n            peer_subnets.entry(peer_hex).or_default().push(sub_name);\r\n        }\r\n        // Health targets = this round's fan-out set; snapshot the registry\r\n        // host's last-admitted stamp while here (REQ-PUMP-STAGE-TRUTH).\r\n        health.set_targets(peer_subnets.keys().cloned(), now_ms());\r\n        health.note_registry_admit(registry.last_admit_ms());\r\n        let round_start = Instant::now();\r\n        let mut pending: HashSet<String> = HashSet::new();\r\n\r\n        // Submit NON-BLOCKING dials for eligible unconnected peers; run the due\r\n        // legs NOW for already-connected ones. The live peer advertises without\r\n        // waiting on a dead one (no head-of-line block) — a dead peer is only a\r\n        // pending outcome, never a serial blocker.\r\n        for (peer_hex, subs) in &peer_subnets {\r\n            if let Some(&conn_id) = conns.get(peer_hex) {\r\n                run_peer_subnets(\r\n                    &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    conn_id, subs, peer_hex, &ctx, round_start,\r\n                )?;\r\n            } else if peer_eligible(&sched, peer_hex, round_start) {\r\n                if let Some(addr) =\r\n                    resolve_submit_addr(&resolver, peer_hex, &paths.peer_addrs, &ctx.policy.roster)\r\n                {\r\n                    match brain.submit_dial(addr) {\r\n                        Ok(()) => {\r\n                            pending.insert(peer_hex.clone());\r\n                        }\r\n                        // A raw carrier-op TimedOut on the submit IPC itself is a\r\n                        // genuine broker-IPC desync → bubble → supervised restart.\r\n                        Err(e) if e.kind() == io::ErrorKind::TimedOut => return Err(e),\r\n                        // The broker refused the submit (net disabled / bad addr):\r\n                        // an ordinary per-peer failure — back off, round continues.\r\n                        Err(e) => {\r\n                            eprintln!(\r\n                                \"PUMP_PEER_FAIL:{peer_hex}:{e} wall_ms={} mono_ms={}\",\r\n                                now_ms(),\r\n                                mono_ms()\r\n                            );\r\n                            health.note_failed(peer_hex, \"submit\", conns.len(), now_ms());\r\n                            let prev = sched.get(peer_hex).copied();\r\n                            sched.insert(peer_hex.clone(), next_peer_backoff(prev, round_start));\r\n                        }\r\n                    }\r\n                } else {\r\n                    // No route on any chain leg (REQ-PUMP-STAGE-TRUTH stage\r\n                    // `address-resolution`). Transition-logged: the first\r\n                    // unroutable round per peer is loud, repeats are quiet —\r\n                    // the peer retries on its own cadence like before.\r\n                    if health.note_failed(\r\n                        peer_hex,\r\n                        crate::seedproofx::STAGE_RESOLVE,\r\n                        conns.len(),\r\n                        now_ms(),\r\n                    ) {\r\n                        eprintln!(\r\n                            \"PUMP_PEER_FAIL:{peer_hex}:stage={}: no route (cache/roster/discovery all empty) wall_ms={} mono_ms={}\",\r\n                            crate::seedproofx::STAGE_RESOLVE,\r\n                            now_ms(),\r\n                            mono_ms()\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // Drain dial outcomes on the DEDICATED event carrier until the round\r\n        // deadline (concurrent dials all land within ~one W1 bound). A CONNECTED\r\n        // runs that peer's legs in THIS round; a DIAL_FAILED backs the peer off; a\r\n        // peer unresolved at the deadline simply reschedules — NEVER a whole-round\r\n        // poison. `TimedOut` = quiet (drain done); any other read error = the\r\n        // broker stopped answering the stream (dead carrier) → bubble → supervised\r\n        // restart (the re-pointed REQ-HAZARD-PUMP-IPC-DEADLINE: a dead BROKER still\r\n        // restarts the pump; a dead PEER never does).\r\n        // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n        let round_deadline = round_start + PEER_REPLY_READ_BUDGET + ROUND_DRAIN_MARGIN;\r\n        loop {\r\n            let read_deadline = if pending.is_empty() {\r\n                Instant::now() // no outstanding submits — sweep only what is queued\r\n            } else {\r\n                round_deadline\r\n            };\r\n            match classify_drain_read(events.read_event_until(Some(read_deadline))) {\r\n                DrainStep::Apply(ev) => handle_presence_event(\r\n                    ev, &mut brain, &mut ops, &mut workers, &due_flags, &mut conns, &mut sched,\r\n                    &mut pending, &peer_subnets, &ctx, &paths.peer_addrs, &mut health, round_start,\r\n                )?,\r\n                DrainStep::Skip => continue, // the event carrier only carries presence\r\n                DrainStep::RoundDone => break, // quiet (TimedOut) — drain done\r\n                DrainStep::Restart(e) => return Err(e), // dead broker/carrier → restart\r\n            }\r\n        }\r\n\r\n        // Persist the round's reachability truth for the status surfaces\r\n        // (REQ-PUMP-STAGE-TRUTH: the acceptance surface of this contract).\r\n        health.save_to(&health_path);\r\n\r\n        // Mark-after-round with a POST-round instant (ADR-0018 §V4: the\r\n        // stagger — ran = ATTEMPTED, not succeeded; the next due is measured\r\n        // from here, not from the round's start).\r\n        mark_ran(&mut last, &due_flags, Instant::now());\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Compute each worker's due flag for this tick (the pure scheduler step):\r\n/// poll EVERY worker's wake first — even a not-otherwise-due leg, so a marker\r\n/// is never stranded — then the [`due`] kernel over its RAM-only last-ran\r\n/// instant. Returns the per-worker flags (parallel to `workers`) and whether\r\n/// ANY leg is due (an all-idle tick sleeps without loading a round).\r\nfn compute_due(\r\n    workers: &mut [&mut dyn PumpWorker],\r\n    last: &[Option<Instant>],\r\n    now: Instant,\r\n) -> (Vec<bool>, bool) {\r\n    let mut flags = vec![false; workers.len()];\r\n    let mut any = false;\r\n    for i in 0..workers.len() {\r\n        let wake = workers[i].poll_wake();\r\n        flags[i] = due(last[i], wake, now, workers[i].cadence());\r\n        any |= flags[i];\r\n    }\r\n    (flags, any)\r\n}\r\n\r\n/// Run each due worker's node-local `pre_round` ONCE for the round, before any\r\n/// peer_step (the registry sweep + advertise; the other legs default no-op).\r\nfn pre_rounds(workers: &mut [&mut dyn PumpWorker], due_flags: &[bool], ctx: &RoundCtx) {\r\n    for i in 0..workers.len() {\r\n        if due_flags[i] {\r\n            workers[i].pre_round(ctx);\r\n        }\r\n    }\r\n}\r\n\r\n/// Run `step` for each due worker index in fan-out order, aborting on the\r\n/// FIRST error (abort-on-first-fail — the caller drops the peer's conn and\r\n/// redials next tick). The peer-step IO itself lives in `step`; this is the\r\n/// pure ordering/abort kernel, so the choreography is a unit fact.\r\nfn run_due(due_flags: &[bool], mut step: impl FnMut(usize) -> io::Result<()>) -> io::Result<()> {\r\n    for (i, &is_due) in due_flags.iter().enumerate() {\r\n        if is_due {\r\n            step(i)?;\r\n        }\r\n    }\r\n…\n}","startLine":519,"lineNumbers":[519,null,527,null,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,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,null,775]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\mod.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1982,"totalBytes":10776,"outputLines":205,"outputBytes":10776,"shownRange":{"start":569,"end":773},"nextOffset":774}}},"isError":false,"timestamp":1784271976074}}
{"type":"message","id":"192aa8d5","parentId":"b4503178","timestamp":"2026-07-17T07:06:22.959Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning connection probe instrumentation**"},{"type":"toolCall","id":"call_fHgv27v9HK3wPnoDsVpzXNCO|fc_05f98684426e605c016a59d46db2d48195b7094cb4cb17c58c","name":"read","arguments":{"i":"Reading connection lifecycle telemetry","path":"crates/spt-daemon/src/conn.rs","selector":"390-670"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":7663,"output":169,"cacheRead":90624,"cacheWrite":0,"totalTokens":98456,"reasoningTokens":128,"cost":{"input":0.038315,"output":0.00507,"cacheRead":0.045312,"cacheWrite":0,"total":0.088697}},"stopReason":"toolUse","timestamp":1784271976121,"responseId":"resp_05f98684426e605c016a59d46885cc8195b557a498671820e0","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_05f98684426e605c016a59d46aeb3c8195b5f590b3bf9dbeaf","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRtKOKJ7rXJ4QLOh1z3eUEAFWUuquNYu7YHAgQ8irNBEOkMyiqKf9Ig2capvrN9i-vL5R8fVHATmd9B_hk5IVmfqepfuBHi4-0sMhU9S8SDawJfIm-9AYOb1fSmtsiBTxboZ8i68VkFTaNGDOHUhuZ5BRlfoUDdNG7-Uu_XZd7EDhmHPbLtZkBWfFw-gBV0v_ysDjOaBCWKwN_TFyd-tSJ2T7If5Sg4AIOQXrpmdN_3uvhPtbmCXDQ5R1Hwh2U16DV5zcF9K-rfdeE5PpKHT3hJSmZP0VgOC7lHW0qWVTkdQOW-hUDAYuRFK-W1LPn5hTM-PYvl7Va4jKXswcdcCoYH9qnbF9w12l_ksE-b1NLptw5seVzibs6BTB9QRaQoKWxXOOHseGQXB_zxRr_UJc1WH1nl1q5sP7junYLSfa7okHAKpFubmNvAYbG0nt0W1VJ7iS4iudIJtDzt3mI4LZzU8Sb2XTnPnzDcpuD6Qdp7vUxCYCDJ9q1ukdOPxz7QNkMxXRigjiq4iVc8yi9_s9rBs3_NkJsQlMbsi0o-YTuaozLn2Otua5bnNTt5B6afnZnWqB7u5WHGvcr7sXvb65jEm8l0kTWzese3gc4WYnWVXtTzIDovBrjqd8-l4PfdD0Gex3k3dUTG0JltBZvLVyof80UyXjzQF_E_S1wcbAVIsOGmPfH-h3j8Wn4zzwugKSnFQXYDycKVrG4Hn4wIBTisX9Q8W5yfV5t9dI6KXYUYuY0jRy7KsjGA_zgDJRO3G9sCEdp0TFC4Lx83HZbQF-gVGbsBM09pFKLi5NDQf5H_ClZc2U3wOtirh63BQEGvI-7DTnMjpiYWILUW8_d2Fpsgw8u5rcfJfXpl1zICzrZ9QmW0L-1AmNMgNIbkxf-7GiTDufO6G1Qt-QyDZz5P5-HPM-KMnq-k3VZPSIwx0fW0R3CsePfSGkU41aJ_nQl3aJhC_7oDzhcSfb1IUG5JbLxm0Gr3hZwGMHhz-9CBEqj5mNXuGzNUflWOdKJmNevh5PKIeCoyLwlxdMwXvIhht9hAVvv17N0nbXpKyJG-Vv1rZZiHuNEwrREMBlijlOr1Qvw81hkDOdPX7HLTp9WgLLbj380iSVvS8Px-WefmqahzKFsgqcLuU9JPbDJZeevyI5LWUw214pZn8ERSUhsrSyYVfMKSvW6s1-lP7Gnd8A7IhHyV7NXKjsOmZMYo8o-X8oXJEzyU3HHaqS7-Iq7IHUpXDh6GxCEuizhcUfRaCRIK8WcENOM2s_fDpVA9HEeQ_ZYDCH8GAeSVPifD-U0aHegfSUnVl-yOvZIFBiAG-WMEsSOm9mstFsPyh03I69Yj9R9HNwelR_mSK3MtptZKai2rCfoZdLXYLq00rnxjwFlRSyQ4AWp2IXUZOkvh9Krlz-cAOLgbd-ikQgfF-39HcynK9dZQRbdkE19MK_2jynJ5sQ3TvAPqjtzwbO3FTQQUfJUm7hg1nWWzJ2TyNdW8n_nQOh1073CnH7cN5A5AMjnGEkJj4FPJV7qZAcf32iqOBZ0Wu8dU48lHmmwtjVP4xYlcGsqf0Q3PVQQD2n6wyOUzIqzx4tPh9ALYuiWl3cJm5iGI","summary":[{"type":"summary_text","text":"**Planning connection probe instrumentation**"}]},{"id":"fc_05f98684426e605c016a59d46db2d48195b7094cb4cb17c58c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading connection lifecycle telemetry\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"390-670\"}","call_id":"call_fHgv27v9HK3wPnoDsVpzXNCO","name":"read"}]},"duration":6836.41210000217,"ttft":2812.2506999969482,"contextSnapshot":{"promptTokens":98287,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fHgv27v9HK3wPnoDsVpzXNCO|fc_05f98684426e605c016a59d46db2d48195b7094cb4cb17c58c","toolName":"read","startedAt":"2026-07-17T07:06:22.960Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Reading connection lifecycle telemetry"},"id":"bd7e4671","parentId":"192aa8d5","timestamp":"2026-07-17T07:06:22.960Z"}
{"type":"message","id":"a54bb745","parentId":"bd7e4671","timestamp":"2026-07-17T07:06:22.968Z","message":{"role":"toolResult","toolCallId":"call_fHgv27v9HK3wPnoDsVpzXNCO|fc_05f98684426e605c016a59d46db2d48195b7094cb4cb17c58c","toolName":"read","content":[{"type":"text","text":"impl Inner {\n…\n    fn watchdog(&self) {\n…\n        loop {\n…\n            self.timed_out.store(true, Ordering::Release);\n            self.poison_and_cancel(None);\n            d = recover(&self.dog);\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\n                let (g, _) = self\n                    .dog_cv\n                    .wait_timeout(d, Duration::from_millis(100))\n                    .unwrap_or_else(|p| p.into_inner());\n                d = g;\n                drop(d);\n                self.poison_and_cancel(None);\n                d = recover(&self.dog);\n            }\n            if d.shutdown {\n                return;\n            }\n            drop(d);\n            self.sever();\n            d = recover(&self.dog);\n        }\n    }\n}\n\n/// The shared, serialized, BOUNDED send side of one physical broker\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\npub(crate) struct BrokerConn {\n    inner: Arc<Inner>,\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\n    /// watchdog aborts through strictly outlives every raw use.\n    dog: Option<JoinHandle<()>>,\n}\n\nimpl BrokerConn {\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\n    /// deadline (gate-wait + OS write completion) — the broker passes its\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n        let raw = raw_of(&half);\n        let inner = Arc::new(Inner {\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\n            label: Mutex::new(String::new()),\n            first_write_logged: AtomicBool::new(false),\n            gate: Mutex::new(Gate { half: Some(half) }),\n            gate_cv: Condvar::new(),\n            dog: Mutex::new(Dog {\n                inflight: None,\n                shutdown: false,\n            }),\n            dog_cv: Condvar::new(),\n            poisoned: AtomicBool::new(false),\n            timed_out: AtomicBool::new(false),\n            severed: AtomicBool::new(false),\n            graveyard: Mutex::new(None),\n            op_seq: AtomicU64::new(0),\n            bound,\n            raw,\n        });\n        let dog_inner = Arc::clone(&inner);\n        let dog = thread::Builder::new()\n            .name(\"conn-watchdog\".into())\n            .spawn(move || dog_inner.watchdog())\n            .ok();\n        BrokerConn { inner, dog }\n    }\n\n    /// The stable per-physical-conn id — what lifecycle records key on\n    /// (`Arc::ptr_eq` identity does not survive a log line).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn id(&self) -> u64 {\n        self.inner.id\n    }\n\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\n    /// endpoint/session where known — e.g. `role=brain`,\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn describe(&self, fact: &str) {\n        let mut l = recover(&self.inner.label);\n        if l.ends_with('…') {\n            return;\n        }\n        if l.len() + fact.len() + 1 > LABEL_CAP {\n            l.push('…');\n            return;\n        }\n        if !l.is_empty() {\n            l.push(' ');\n        }\n        l.push_str(fact);\n    }\n\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\n    /// attribution format — the broker's sink machinery uses it for\n    /// `writer-exit`, attach/replace, and detach events (leg d).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\n        self.inner.lifecycle(event, extra);\n    }\n\n    /// Bounded framed write. See [`Self::write_gated`].\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\n        self.write_gated(env, || true)\n    }\n\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\n    /// serialized gate is acquired and BEFORE any byte is written — the same\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n    /// superseded controller writer still bails atomically with respect to\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n    pub(crate) fn write_gated(\n        &self,\n        env: &Envelope,\n        pre: impl FnOnce() -> bool,\n    ) -> io::Result<ConnWrite> {\n        let inner = &*self.inner;\n        let deadline = Instant::now() + inner.bound;\n\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\n        let mut g = recover(&inner.gate);\n        loop {\n            if inner.poisoned.load(Ordering::Acquire) {\n                return Err(poisoned_err());\n            }\n            if g.half.is_some() {\n                break;\n            }\n            let now = Instant::now();\n            if now >= deadline {\n                // The write we queued behind is stuck past OUR whole bound:\n                // that op is the wedge. Abort it out of band and fail — the\n                // watchdog's own deadline (armed at that op's entry) is also\n                // in flight; both paths converge on the same idempotent\n                // poison/cancel.\n                drop(g);\n                inner.timed_out.store(true, Ordering::Release);\n                inner.poison_and_cancel(None);\n                return Err(timeout_err());\n            }\n            let (g2, _) = inner\n                .gate_cv\n                .wait_timeout(g, deadline - now)\n                .unwrap_or_else(|p| p.into_inner());\n            g = g2;\n        }\n\n        if !pre() {\n            return Ok(ConnWrite::Superseded);\n        }\n\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\n        // first committed write, so a conn that later retires can be correlated\n        // to when it went active (fresh-carrier churn shows as open→start→retire\n        // triplets with fresh ids).\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\n            inner.lifecycle(\"write-start\", \"\");\n        }\n\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\n        {\n            let mut d = recover(&inner.dog);\n            d.inflight = Some((op, deadline));\n            inner.dog_cv.notify_all();\n        }\n        drop(g);\n\n        let res = write_frame(&mut half, env);\n\n        // Completion handshake: `write_frame` has returned, so the OS op —\n        // canceled or not — reported completion and the frame buffer is\n        // released. Clear the arm BEFORE deciding health, so the watchdog\n        // never severs while the op is formally in flight.\n        {\n            let mut d = recover(&inner.dog);\n            d.inflight = None;\n            inner.dog_cv.notify_all();\n        }\n\n        let mut g = recover(&inner.gate);\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\n            g.half = Some(half);\n            drop(g);\n            inner.gate_cv.notify_all();\n            return Ok(ConnWrite::Done);\n        }\n\n        // ── Retire: poison the whole conn, abort the read side so\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\n        // graveyard (its handle must outlive any late raw abort; never\n        // re-gated, never reused — a timed-out length-prefixed frame may be\n        // partially written). ──\n        drop(g);\n        inner.poison_and_cancel(res.as_ref().err());\n        inner.sever();\n        *recover(&inner.graveyard) = Some(half);\n        inner.gate_cv.notify_all();\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\n        // (peer death) keeps its own error.\n        if inner.timed_out.load(Ordering::Acquire) {\n            return Err(timeout_err());\n        }\n        Err(match res {\n            Err(e) => e,\n            Ok(()) => poisoned_err(),\n        })\n    }\n}\n\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\n/// window (the P1c epoch-gate keystone unit relies on it).\n#[cfg(test)]\npub(crate) struct TestGatePin {\n    inner: Arc<Inner>,\n    half: Option<SendHalf>,\n}\n\n#[cfg(test)]\nimpl Drop for TestGatePin {\n    fn drop(&mut self) {\n        let mut g = recover(&self.inner.gate);\n        g.half = self.half.take();\n        drop(g);\n        self.inner.gate_cv.notify_all();\n    }\n}\n\n#[cfg(test)]\nimpl BrokerConn {\n    /// Check the half out of the gate without arming the watchdog — writers\n    /// queue behind the gate (bounded by their own deadlines) until the\n    /// returned pin drops. Test-only; never a production path.\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\n        let mut g = recover(&self.inner.gate);\n        let half = g.half.take().expect(\"pin: gate half present\");\n        TestGatePin {\n            inner: Arc::clone(&self.inner),\n            half: Some(half),\n        }\n    }\n}\n\nimpl Drop for BrokerConn {\n    fn drop(&mut self) {\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n        // is about to close — the terminal record of this conn id's life, with\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\n        self.inner.lifecycle(\n            \"transport-close\",\n            &format!(\n                \"poisoned={} timed_out={}\",\n                self.inner.poisoned.load(Ordering::Acquire),\n                self.inner.timed_out.load(Ordering::Acquire)\n            ),\n        );\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…\n    )\n}\n\n[Showing lines 389-673 of 815. Use :674 to continue]"}],"details":{"truncation":{"content":"            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r\n    /// attribution format — the broker's sink machinery uses it for\r\n    /// `writer-exit`, attach/replace, and detach events (leg d).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\r\n        self.inner.lifecycle(event, extra);\r\n    }\r\n\r\n    /// Bounded framed write. See [`Self::write_gated`].\r\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\r\n        self.write_gated(env, || true)\r\n    }\r\n\r\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\r\n    /// serialized gate is acquired and BEFORE any byte is written — the same\r\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\r\n    /// superseded controller writer still bails atomically with respect to\r\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\r\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\r\n    pub(crate) fn write_gated(\r\n        &self,\r\n        env: &Envelope,\r\n        pre: impl FnOnce() -> bool,\r\n    ) -> io::Result<ConnWrite> {\r\n        let inner = &*self.inner;\r\n        let deadline = Instant::now() + inner.bound;\r\n\r\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\r\n        let mut g = recover(&inner.gate);\r\n        loop {\r\n            if inner.poisoned.load(Ordering::Acquire) {\r\n                return Err(poisoned_err());\r\n            }\r\n            if g.half.is_some() {\r\n                break;\r\n            }\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                // The write we queued behind is stuck past OUR whole bound:\r\n                // that op is the wedge. Abort it out of band and fail — the\r\n                // watchdog's own deadline (armed at that op's entry) is also\r\n                // in flight; both paths converge on the same idempotent\r\n                // poison/cancel.\r\n                drop(g);\r\n                inner.timed_out.store(true, Ordering::Release);\r\n                inner.poison_and_cancel(None);\r\n                return Err(timeout_err());\r\n            }\r\n            let (g2, _) = inner\r\n                .gate_cv\r\n                .wait_timeout(g, deadline - now)\r\n                .unwrap_or_else(|p| p.into_inner());\r\n            g = g2;\r\n        }\r\n\r\n        if !pre() {\r\n            return Ok(ConnWrite::Superseded);\r\n        }\r\n\r\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\r\n        // first committed write, so a conn that later retires can be correlated\r\n        // to when it went active (fresh-carrier churn shows as open→start→retire\r\n        // triplets with fresh ids).\r\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\r\n            inner.lifecycle(\"write-start\", \"\");\r\n        }\r\n\r\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\r\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\r\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = Some((op, deadline));\r\n            inner.dog_cv.notify_all();\r\n        }\r\n        drop(g);\r\n\r\n        let res = write_frame(&mut half, env);\r\n\r\n        // Completion handshake: `write_frame` has returned, so the OS op —\r\n        // canceled or not — reported completion and the frame buffer is\r\n        // released. Clear the arm BEFORE deciding health, so the watchdog\r\n        // never severs while the op is formally in flight.\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = None;\r\n            inner.dog_cv.notify_all();\r\n        }\r\n\r\n        let mut g = recover(&inner.gate);\r\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\r\n            g.half = Some(half);\r\n            drop(g);\r\n            inner.gate_cv.notify_all();\r\n            return Ok(ConnWrite::Done);\r\n        }\r\n\r\n        // ── Retire: poison the whole conn, abort the read side so\r\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\r\n        // graveyard (its handle must outlive any late raw abort; never\r\n        // re-gated, never reused — a timed-out length-prefixed frame may be\r\n        // partially written). ──\r\n        drop(g);\r\n        inner.poison_and_cancel(res.as_ref().err());\r\n        inner.sever();\r\n        *recover(&inner.graveyard) = Some(half);\r\n        inner.gate_cv.notify_all();\r\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\r\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\r\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\r\n        // (peer death) keeps its own error.\r\n        if inner.timed_out.load(Ordering::Acquire) {\r\n            return Err(timeout_err());\r\n        }\r\n        Err(match res {\r\n            Err(e) => e,\r\n            Ok(()) => poisoned_err(),\r\n        })\r\n    }\r\n}\r\n\r\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\r\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\r\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\r\n/// window (the P1c epoch-gate keystone unit relies on it).\r\n#[cfg(test)]\r\npub(crate) struct TestGatePin {\r\n    inner: Arc<Inner>,\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n#[cfg(test)]\r\nimpl Drop for TestGatePin {\r\n    fn drop(&mut self) {\r\n        let mut g = recover(&self.inner.gate);\r\n        g.half = self.half.take();\r\n        drop(g);\r\n        self.inner.gate_cv.notify_all();\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nimpl BrokerConn {\r\n    /// Check the half out of the gate without arming the watchdog — writers\r\n    /// queue behind the gate (bounded by their own deadlines) until the\r\n    /// returned pin drops. Test-only; never a production path.\r\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r\n        self.inner.lifecycle(\r\n            \"transport-close\",\r\n            &format!(\r\n                \"poisoned={} timed_out={}\",\r\n                self.inner.poisoned.load(Ordering::Acquire),\r\n                self.inner.timed_out.load(Ordering::Acquire)\r\n            ),\r\n        );\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","truncated":true,"truncatedBy":"lines","totalLines":427,"totalBytes":11054,"outputLines":285,"outputBytes":11054,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl Inner {\n…\n    fn watchdog(&self) {\n…\n        loop {\n…\n            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r\n    /// attribution format — the broker's sink machinery uses it for\r\n    /// `writer-exit`, attach/replace, and detach events (leg d).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\r\n        self.inner.lifecycle(event, extra);\r\n    }\r\n\r\n    /// Bounded framed write. See [`Self::write_gated`].\r\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\r\n        self.write_gated(env, || true)\r\n    }\r\n\r\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\r\n    /// serialized gate is acquired and BEFORE any byte is written — the same\r\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\r\n    /// superseded controller writer still bails atomically with respect to\r\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\r\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\r\n    pub(crate) fn write_gated(\r\n        &self,\r\n        env: &Envelope,\r\n        pre: impl FnOnce() -> bool,\r\n    ) -> io::Result<ConnWrite> {\r\n        let inner = &*self.inner;\r\n        let deadline = Instant::now() + inner.bound;\r\n\r\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\r\n        let mut g = recover(&inner.gate);\r\n        loop {\r\n            if inner.poisoned.load(Ordering::Acquire) {\r\n                return Err(poisoned_err());\r\n            }\r\n            if g.half.is_some() {\r\n                break;\r\n            }\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                // The write we queued behind is stuck past OUR whole bound:\r\n                // that op is the wedge. Abort it out of band and fail — the\r\n                // watchdog's own deadline (armed at that op's entry) is also\r\n                // in flight; both paths converge on the same idempotent\r\n                // poison/cancel.\r\n                drop(g);\r\n                inner.timed_out.store(true, Ordering::Release);\r\n                inner.poison_and_cancel(None);\r\n                return Err(timeout_err());\r\n            }\r\n            let (g2, _) = inner\r\n                .gate_cv\r\n                .wait_timeout(g, deadline - now)\r\n                .unwrap_or_else(|p| p.into_inner());\r\n            g = g2;\r\n        }\r\n\r\n        if !pre() {\r\n            return Ok(ConnWrite::Superseded);\r\n        }\r\n\r\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\r\n        // first committed write, so a conn that later retires can be correlated\r\n        // to when it went active (fresh-carrier churn shows as open→start→retire\r\n        // triplets with fresh ids).\r\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\r\n            inner.lifecycle(\"write-start\", \"\");\r\n        }\r\n\r\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\r\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\r\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = Some((op, deadline));\r\n            inner.dog_cv.notify_all();\r\n        }\r\n        drop(g);\r\n\r\n        let res = write_frame(&mut half, env);\r\n\r\n        // Completion handshake: `write_frame` has returned, so the OS op —\r\n        // canceled or not — reported completion and the frame buffer is\r\n        // released. Clear the arm BEFORE deciding health, so the watchdog\r\n        // never severs while the op is formally in flight.\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = None;\r\n            inner.dog_cv.notify_all();\r\n        }\r\n\r\n        let mut g = recover(&inner.gate);\r\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\r\n            g.half = Some(half);\r\n            drop(g);\r\n            inner.gate_cv.notify_all();\r\n            return Ok(ConnWrite::Done);\r\n        }\r\n\r\n        // ── Retire: poison the whole conn, abort the read side so\r\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\r\n        // graveyard (its handle must outlive any late raw abort; never\r\n        // re-gated, never reused — a timed-out length-prefixed frame may be\r\n        // partially written). ──\r\n        drop(g);\r\n        inner.poison_and_cancel(res.as_ref().err());\r\n        inner.sever();\r\n        *recover(&inner.graveyard) = Some(half);\r\n        inner.gate_cv.notify_all();\r\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\r\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\r\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\r\n        // (peer death) keeps its own error.\r\n        if inner.timed_out.load(Ordering::Acquire) {\r\n            return Err(timeout_err());\r\n        }\r\n        Err(match res {\r\n            Err(e) => e,\r\n            Ok(()) => poisoned_err(),\r\n        })\r\n    }\r\n}\r\n\r\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\r\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\r\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\r\n/// window (the P1c epoch-gate keystone unit relies on it).\r\n#[cfg(test)]\r\npub(crate) struct TestGatePin {\r\n    inner: Arc<Inner>,\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n#[cfg(test)]\r\nimpl Drop for TestGatePin {\r\n    fn drop(&mut self) {\r\n        let mut g = recover(&self.inner.gate);\r\n        g.half = self.half.take();\r\n        drop(g);\r\n        self.inner.gate_cv.notify_all();\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nimpl BrokerConn {\r\n    /// Check the half out of the gate without arming the watchdog — writers\r\n    /// queue behind the gate (bounded by their own deadlines) until the\r\n    /// returned pin drops. Test-only; never a production path.\r\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r\n        self.inner.lifecycle(\r\n            \"transport-close\",\r\n            &format!(\r\n                \"poisoned={} timed_out={}\",\r\n                self.inner.poisoned.load(Ordering::Acquire),\r\n                self.inner.timed_out.load(Ordering::Acquire)\r\n            ),\r\n        );\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…\n    )\n}","startLine":219,"lineNumbers":[219,null,343,null,345,null,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,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,null,676,677]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":815,"totalBytes":11054,"outputLines":285,"outputBytes":11054,"shownRange":{"start":389,"end":673},"nextOffset":674}}},"isError":false,"timestamp":1784271982967}}
{"type":"message","id":"88547b52","parentId":"a54bb745","timestamp":"2026-07-17T07:06:27.913Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing lifecycle event discrepancies**"},{"type":"toolCall","id":"call_Hg6NulyzJbvl3sdOuUVAGtgS|fc_0f9c54db51a031f0016a59d47249648196be9519ca9b335962","name":"grep","arguments":{"i":"Finding lifecycle log format","pattern":"fn lifecycle|CONN_|lifecycle\\(","path":"crates/spt-daemon/src/conn.rs","selector":"130-390","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":7369,"output":117,"cacheRead":93696,"cacheWrite":0,"totalTokens":101182,"reasoningTokens":50,"cost":{"input":0.036845,"output":0.00351,"cacheRead":0.046848,"cacheWrite":0,"total":0.087203}},"stopReason":"toolUse","timestamp":1784271983018,"responseId":"resp_0f9c54db51a031f0016a59d46f81e4819685684d7f5d3b4918","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0f9c54db51a031f0016a59d470e4bc8196933a42e6b4d4134a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdRyDZn7YuHzn3kiyjAYJotJmz4MJGtcJ9xUnSOo6MhsIWjI4-rZ-hg1YV5l_YUJCP-V91Ki7mds6lg3Iv2YK34y7Hz-QnTto1rFOQFFoxQOmRk1vb9LLc6cS5QVg4WbBDT257oNzqPkWpwhEm4qrwsMu9RzGJWHOYMsduLlPXydA5K5dITAbslcNdEhyVD8EqwZ10ioZa7Fwl4c7KrIOyvbj_ePLbQR9dx_sKfPa4ozl8rPC7G0oqLzJxvedA8Hu3Jem1_iTc6__ricsppK7Hig09vGQLkSm3HXmn7qrQkRKeBqi6rGuy2JoENOO_t67eEwt9wj3HBZnUk7KK5V0tcXjPvzwWg9BhwTI4DyQXbhILB-AGAIbZIPUEqEXtJeg9WkaybD2QZgutvUwLgw73yxwR17krCHsXW8jvOeerhB5wITf4DWv8f5ZP6cvKhLTdqNpBXkO14Umf7-siRhJtqZQzdOjbMwMGl6WdGyC_t8e3q3-ssL0qD2oAhu4NCYYB0v4PH-tl2M__XMYrcTmBka-VlbIbbo_rWepIPQvWOXxUQSvAyQlHhkRKjj0iXFWLQ80mIzLryglX7Un88OydrHA35vv7oA9KwDSPvyrr84ZHLLifmXDVDHf1hT962jSjfaKpRlxjXxq_-kyuC0IAtZs3nnDeG5jxnaoAzMHTsCt46ir67ZiTw-NFtAe6Tau9RT7HfWdQvBkca3isjcXSkkpGdFv65nGepyjJ8euj57JvE4HWXDlTqqUzwijJmkzFdohO5bt_KvWFmQviGRVsiXP7gVDZsjZ95XPzfU0ZLMeNZ2gioG0RbGr2t-U5w2Fr5BcjxwjKHL-FLhX9AxINLEChJIGPOSzik1rPg0jncd9i5ZVIv3n0Ge98hANj28PyqrNhTGdjgkt9kRU6Adma0cjIQPhd9sAlqURgVBjO6y8uHiGDzlx46usaXq-V1r4h_kGVi2u5aMwbL7wu1ckSfOXwEtP_TcMibgk1BXscCB-C_rCiURShNmjwjFHvuOtjltgWK0EwBUmu4AvJWjMhEqhIvVtqXZryBfM0SpC7G4783Xu6mXWzcy6r0XTDDrPNMDXf06zeSdmvzPFbmzOHpGusVPtQahcbEplNgmeTXbsS8VpricfyqPd15y45rTGkyHSsxZ3kkv5uZzDbQtz8IMo0qdj_sGFa2X4KazX2xtdyFBYqJV7qisMAI0f4McAmGc","summary":[{"type":"summary_text","text":"**Tracing lifecycle event discrepancies**"}]},{"id":"fc_0f9c54db51a031f0016a59d47249648196be9519ca9b335962","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding lifecycle log format\",\"pattern\":\"fn lifecycle|CONN_|lifecycle\\\\(\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"130-390\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_Hg6NulyzJbvl3sdOuUVAGtgS","name":"grep"}]},"duration":4894.28449998796,"ttft":1882.2472999989986,"contextSnapshot":{"promptTokens":101065,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Hg6NulyzJbvl3sdOuUVAGtgS|fc_0f9c54db51a031f0016a59d47249648196be9519ca9b335962","toolName":"grep","startedAt":"2026-07-17T07:06:27.914Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Finding lifecycle log format"},"id":"ab955ead","parentId":"88547b52","timestamp":"2026-07-17T07:06:27.914Z"}
{"type":"message","id":"82525ee9","parentId":"ab955ead","timestamp":"2026-07-17T07:06:27.916Z","message":{"role":"toolResult","toolCallId":"call_Hg6NulyzJbvl3sdOuUVAGtgS|fc_0f9c54db51a031f0016a59d47249648196be9519ca9b335962","toolName":"grep","content":[{"type":"text","text":" 141|struct Inner {\n*142|    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143|    id: u64,\n 144|    /// Accreted attribution facts (subscriber role, endpoint/session where\n 145|    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\n...\n 229|    /// Render the once-per-conn retirement record — the F-039 token split\n*230|    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231|    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232|    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233|    /// already-dead counterpart, the offline-peer dial churn) emits the\n*234|    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n 235|    /// SAME attribution fields — quieter class, never thinner (the retired\n 236|    /// class is what discriminates fresh-carrier churn from interleave\n 237|    /// artifact in the field). Behavior is identical either way: poison,\n...\n 242|            format!(\n*243|                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244|                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245|                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246|                self.attribution(),\n...\n 254|            format!(\n*255|                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n 256|                 broker conn poisoned + retired, never reused (same retire behavior \\\n 257|                 as POISONED — only the failure class differs) \\\n 258|                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n...\n 269|    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n*270|    fn lifecycle(&self, event: &str, extra: &str) {\n 271|        let sep = if extra.is_empty() { \"\" } else { \" \" };\n 272|        eprintln!(\n*273|            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n 274|            self.attribution()\n 275|        );\n 276|    }\n...\n 287|            // First poison of this conn: loud, once. The token is class-split\n*288|            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n*289|            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290|            eprintln!(\n 291|                \"{}\",\n 292|                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)"}],"details":{"scopePath":"crates/spt-daemon/src/conn.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/conn.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":9,"fileCount":1,"files":["crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/conn.rs","count":9}],"truncated":false,"displayContent":" 141│struct Inner {\n*142│    /// Stable per-physical-conn id ([`CONN_ID_SEQ`]) — on every lifecycle record.\n 143│    id: u64,\n 144│    /// Accreted attribution facts (subscriber role, endpoint/session where\n 145│    /// known — [`BrokerConn::describe`]). Bounded by [`LABEL_CAP`].\n    │...\n 229│    /// Render the once-per-conn retirement record — the F-039 token split\n*230│    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231│    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232│    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233│    /// already-dead counterpart, the offline-peer dial churn) emits the\n*234│    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n 235│    /// SAME attribution fields — quieter class, never thinner (the retired\n 236│    /// class is what discriminates fresh-carrier churn from interleave\n 237│    /// artifact in the field). Behavior is identical either way: poison,\n    │...\n 242│            format!(\n*243│                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244│                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245│                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246│                self.attribution(),\n    │...\n 254│            format!(\n*255│                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n 256│                 broker conn poisoned + retired, never reused (same retire behavior \\\n 257│                 as POISONED — only the failure class differs) \\\n 258│                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n    │...\n 269│    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n*270│    fn lifecycle(&self, event: &str, extra: &str) {\n 271│        let sep = if extra.is_empty() { \"\" } else { \" \" };\n 272│        eprintln!(\n*273│            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n 274│            self.attribution()\n 275│        );\n 276│    }\n    │...\n 287│            // First poison of this conn: loud, once. The token is class-split\n*288│            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n*289│            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290│            eprintln!(\n 291│                \"{}\",\n 292│                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)"},"isError":false,"timestamp":1784271987915}}
{"type":"message","id":"8aa1634f","parentId":"82525ee9","timestamp":"2026-07-17T07:06:40.704Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Preparing Main report**"},{"type":"toolCall","id":"call_il11Y90bCccR0U3hmy8UeUqU|fc_03bb3b3fa563ab2b016a59d477fe90819498261b452fbdb63f","name":"irc","arguments":{"i":"Sharing redispatch root cause","op":"send","to":"Main","message":"REDISPATCH RCA: `dispatch.rs:456-563` does `brain.net_streams()` every 100ms (`:87`) whenever breaker closed, and broker `nethost.rs:1804-1823` serializes EVERY non-retired StreamEntry before dispatcher filters `initiated_locally` at `dispatch.rs:464-470`. Registry pump opens+FINs a fresh local stream per peer/30s (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`); local rows are never dispatched/retired. `register_conn` only removes stream rows on whole QUIC conn close (`nethost.rs:949-970`), while pump caches healthy conns. Thus eligible table is O(time×peers), and brain rereads/deserializes it 10Hz: strong fit for sustained brain CPU/read I/O with few CPU-active threads. Incoming Served rows are hidden but physically retained (`retire_stream` :1826-1853), so memory remains O(time) until conn closes. Replay loop: SeatBlocked does not burn retry budget (`dispatch.rs:324-341`) and can retry forever; global breaker 2→30s, reset by any sibling Served (`:510-523`), so a mix can keep one poison at ~2s. Churn: each worker uses opener-query conn + serve conn + success retire conn; pool=8 but at 100ms can still cycle up to 80 workers/s / ~240 short IPC conns/s over a backlog. 532 broker threads cannot be steady current worker count (pool 8; NetHost Tokio fixed 2); probe `conn_handler_count` + thread stacks / CONN_LIFECYCLE write-start minus transport-close to distinguish teardown lag. Will terminal-yield exact probe/test seam shortly.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5070,"output":459,"cacheRead":96768,"cacheWrite":0,"totalTokens":102297,"reasoningTokens":27,"cost":{"input":0.02535,"output":0.013770000000000001,"cacheRead":0.048383999999999996,"cacheWrite":0,"total":0.087504}},"stopReason":"toolUse","timestamp":1784271987922,"responseId":"resp_03bb3b3fa563ab2b016a59d47466348194971e6f240148d0d8","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_03bb3b3fa563ab2b016a59d477120c819499a48e1810ad4dab","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdR4lngXPjxxElnmefHq-mkfzdZhCuNtuf0QR57b4wj1e9w6C3FDnHmvNzLuGfbfEyI71yF5VEp-6VbR3-bqArNk7Ro6oQ2N3wqsSQkeHQVTwM7mCvo8QkYVVe672AfjX8RehXulNZxIrdgJLYzVNyh_HO0zv0J5krnwBnDK6zDYi06-nJCPGGVSFZvIHBe7rJcYc6Vkl7egIEw3gGU9527PbEJft8hjpi7SxWzFCfZSdN1n0A02lC_9OzwQDI-RVYsctxPcVovpVzuR7antdk1H5oS4N_A776LFefZfeQUVa8SYEAhT7K7UHLLFoqT7juvh1gneUkUW9zxX92GXOAlVLAjPm0wxor4VnZAec_My6CHV9lEe8VYvqLfIKL73kQLFg2n4YxVXS9ZopxvbMZkzGALO1VhzZWCHcAm8QmKUvTDUbbf1Kz9b7EHKetkYbqVXICZr9GGWDaUy9r8VEpUyZBx8h1-RoCrIJybXhkK4lpFXhMVBv7tt512VejXPElbtYwuExpHZy1MmLUnY0-XPNCMOT9EavCC5Py7wQnUgl0tmpGl87rpZtpTtp0sdYCnllxGYI1Ld-2KAeh-aZ1EIqY8l6VoaKsoliZTBINs5RY4C6o1Xpipa8Dk1OouSjDpcouGVFPOLpO4WoTU1gLYVMHkpRanyDDUDSJEp4mAiU7DwFKovo79W3LXB6R6yn9IQHbHUY_VhKDsYRa6yuqKFrDV4_x9J4gr4uljUge0WIBMKGpX-vO-ju82voSZ4T31d2hD3-WWsIiPIjkOdB-4nyKz8Htv9Js6DP4-7maOAAJFOCAYT71pa8sMrkXd8ZPPKCcHdxPuBVnx-9fTe-LZtAq6oXDBO2yktKfPi82KSQmuk75FiB5CO6uXdpIU9OX32Pc1hGnBOrUeI7jvX7mGOAdnFOEAQvPwf8Pc6euV0EFJtuUWcpuq9Tntx1fF95-XN27azoH8ZrqRgBakJVUacftEf9_gg_k_qD2BNGMRy1y2_j1cV4kpeC1KD_CQ7Ko1QEPBNtCYh6lE6SMa2g_T3a1bt2nmstlmMxRoj5ocYPsw=","summary":[{"type":"summary_text","text":"**Preparing Main report**"}]},{"id":"fc_03bb3b3fa563ab2b016a59d477fe90819498261b452fbdb63f","type":"function_call","status":"completed","arguments":"{\"i\":\"Sharing redispatch root cause\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"REDISPATCH RCA: `dispatch.rs:456-563` does `brain.net_streams()` every 100ms (`:87`) whenever breaker closed, and broker `nethost.rs:1804-1823` serializes EVERY non-retired StreamEntry before dispatcher filters `initiated_locally` at `dispatch.rs:464-470`. Registry pump opens+FINs a fresh local stream per peer/30s (`pump/registry.rs:158-176`, `pump/mod.rs:1116-1122`); local rows are never dispatched/retired. `register_conn` only removes stream rows on whole QUIC conn close (`nethost.rs:949-970`), while pump caches healthy conns. Thus eligible table is O(time×peers), and brain rereads/deserializes it 10Hz: strong fit for sustained brain CPU/read I/O with few CPU-active threads. Incoming Served rows are hidden but physically retained (`retire_stream` :1826-1853), so memory remains O(time) until conn closes. Replay loop: SeatBlocked does not burn retry budget (`dispatch.rs:324-341`) and can retry forever; global breaker 2→30s, reset by any sibling Served (`:510-523`), so a mix can keep one poison at ~2s. Churn: each worker uses opener-query conn + serve conn + success retire conn; pool=8 but at 100ms can still cycle up to 80 workers/s / ~240 short IPC conns/s over a backlog. 532 broker threads cannot be steady current worker count (pool 8; NetHost Tokio fixed 2); probe `conn_handler_count` + thread stacks / CONN_LIFECYCLE write-start minus transport-close to distinguish teardown lag. Will terminal-yield exact probe/test seam shortly.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_il11Y90bCccR0U3hmy8UeUqU","name":"irc"}]},"duration":12780.998700007796,"ttft":3166.462200000882,"contextSnapshot":{"promptTokens":101838,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_il11Y90bCccR0U3hmy8UeUqU|fc_03bb3b3fa563ab2b016a59d477fe90819498261b452fbdb63f","toolName":"irc","startedAt":"2026-07-17T07:06:40.704Z","intent":"Sharing redispatch root cause"},"id":"2cd784c3","parentId":"8aa1634f","timestamp":"2026-07-17T07:06:40.704Z"}
{"type":"message","id":"a3eaaf92","parentId":"2cd784c3","timestamp":"2026-07-17T07:06:40.704Z","message":{"role":"toolResult","toolCallId":"call_il11Y90bCccR0U3hmy8UeUqU|fc_03bb3b3fa563ab2b016a59d477fe90819498261b452fbdb63f","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1784272000704}}
{"type":"message","id":"5543f7d4","parentId":"a3eaaf92","timestamp":"2026-07-17T07:06:45.867Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning coordination between CPU RCAs**"},{"type":"toolCall","id":"call_V4cttgLpI26ySxvmWZjhgRh1|fc_0d482fddaece98d1016a59d4839de081949425922ca5edabc6","name":"irc","arguments":{"i":"Checking CPU investigator overlap","op":"send","to":"BrokerCpuRca","message":"I found unbounded stream_infos hot enumeration + Registry local stream retention. Do your thread findings identify what accounts for ~532 broker threads (conn handlers/watchdogs vs PTY/other)? One-line answer useful.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4527,"output":131,"cacheRead":97792,"cacheWrite":0,"totalTokens":102450,"reasoningTokens":31,"cost":{"input":0.022635000000000002,"output":0.00393,"cacheRead":0.048895999999999995,"cacheWrite":0,"total":0.075461}},"stopReason":"toolUse","timestamp":1784272000755,"responseId":"resp_0d482fddaece98d1016a59d481228c819491bbe0cc981a2967","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0d482fddaece98d1016a59d48288ac81948a176617738d1360","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdSDC2R13VPRbSo9JVFU1DmYG2LDO3g50w3yKCOAjdqMmx_Avbki2jhnBjKTZabt1ArsIHphj_jFdwE2mXljmu0vJasN7UfKACSj1f_3yu8pRIDqp_wFrTrv3Q3m0Z34k-z9DRy8vIVoWYziieFwWO46OnbnekPSHTZdOG5gFWr22qvhu8RK2to4wKMgIntFmptbn3XbnkKgizdfZx3nwRUSlJwdyldRpU5eVVh3fQFN1N48DvOyTPSZ9T4IqLZdrl8As3V6cXlTtoM7jxh3ZjazJSzZgD2bnOZ1lqy-YYweVsN9A7ply-WMDJiAhybO9Bekof1tXWm0pq0AMNCf9eU7PqGUm7YzGpXo1jtFRxIAbnuK4AmO78SITSyXXG81GHzQeDJ61ucFcoLFWT8Y7O4Px_WibcHrr1jfLGAjKyZZMUX1tWmF0xEnD2ENvcB6IbpcCBig4_uOUxyPiGpOvXVD90lAnlTaszN2yLG2IinJ7sMtGYE0RzhJiQ3oC5h9F6WLs60PirdTxHTgJBBdN8LaCZapwjQ0u25fWUcVm99O-zqXC0qYE7WfRW0lMeL16azOHysCf-lzLUtXy02IFkLMFN_TyT00aw_eyM3FTfLYVvVtmVWk5zQrA0ht-RwHhNeKRhx8x_Zw1zCFUwckgWEX30LTV-RE_kk1Yb2baYwofC-p3YGvO41sxt-sRGQQNfJ0BZ5AY9gWoMkzbAQa53yml7xBIGUJsgwyw7JNO6wpK8Epxo_sm10N8jUB7-y4tHLXcnjYBha5maGNVkPGSxroc2H67Qh8uhu7AwvEP29FVq1bGU3DHeNf8-baZ9PPYdgVTaGT8gak0FGVaxBXPk7TRTcXgd6DdzZnhQQV4r4c03a9ymnPTOMvcuPk7Uskm0ZCbUTD5VC5_o4PEsV_mvBnKX_MEaEZaDCvufayqfbrW3WDLnSUpEiIYsA9XcgCJ9Q2lWXPa2Tn-usjUIp8PjEolF3U3z0ugIC90lnFIdC35QL09S5Ax-RPZdF_UFi4totniSMf7Vj7x5DeG7dBdd3lDtnsnqQtC0W1J3j1oph75WdG0guXgIEG3xjYuuDUvtThVom3tgGvjAguGwlEHf8YqA==","summary":[{"type":"summary_text","text":"**Planning coordination between CPU RCAs**"}]},{"id":"fc_0d482fddaece98d1016a59d4839de081949425922ca5edabc6","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking CPU investigator overlap\",\"op\":\"send\",\"to\":\"BrokerCpuRca\",\"message\":\"I found unbounded stream_infos hot enumeration + Registry local stream retention. Do your thread findings identify what accounts for ~532 broker threads (conn handlers/watchdogs vs PTY/other)? One-line answer useful.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_V4cttgLpI26ySxvmWZjhgRh1","name":"irc"}]},"duration":5110.5841999948025,"ttft":1782.9605000019073,"contextSnapshot":{"promptTokens":102319,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_V4cttgLpI26ySxvmWZjhgRh1|fc_0d482fddaece98d1016a59d4839de081949425922ca5edabc6","toolName":"irc","startedAt":"2026-07-17T07:06:45.867Z","intent":"Checking CPU investigator overlap"},"id":"b847bbeb","parentId":"5543f7d4","timestamp":"2026-07-17T07:06:45.867Z"}
{"type":"message","id":"7b3dcaa2","parentId":"b847bbeb","timestamp":"2026-07-17T07:06:45.868Z","message":{"role":"toolResult","toolCallId":"call_V4cttgLpI26ySxvmWZjhgRh1|fc_0d482fddaece98d1016a59d4839de081949425922ca5edabc6","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- BrokerCpuRca: injected"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"BrokerCpuRca","receipts":[{"to":"BrokerCpuRca","outcome":"injected"}]},"isError":false,"timestamp":1784272005867}}
{"type":"message","id":"c2cde44a","parentId":"7b3dcaa2","timestamp":"2026-07-17T07:06:55.650Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_xdjWHS0F7Sw6oYqRt2R1MKjM|fc_0d49cba2043f1af0016a59d48b2a4c819786027b7d9f0c557d","name":"irc","arguments":{"i":"Checking brain investigator overlap","op":"send","to":"BrainCpuRca","message":"I found dispatcher net_streams 10Hz over unbounded locally-initiated Registry rows. Did your I/O trace support this versus registry sessions.log scans? One-line answer useful.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4683,"output":93,"cacheRead":97792,"cacheWrite":0,"totalTokens":102568,"cost":{"input":0.023415000000000002,"output":0.00279,"cacheRead":0.048895999999999995,"cacheWrite":0,"total":0.075101}},"stopReason":"toolUse","timestamp":1784272005870,"responseId":"resp_0d49cba2043f1af0016a59d4862fd48197a52ad245668d875f","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0d49cba2043f1af0016a59d48b2a4c819786027b7d9f0c557d","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking brain investigator overlap\",\"op\":\"send\",\"to\":\"BrainCpuRca\",\"message\":\"I found dispatcher net_streams 10Hz over unbounded locally-initiated Registry rows. Did your I/O trace support this versus registry sessions.log scans? One-line answer useful.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_xdjWHS0F7Sw6oYqRt2R1MKjM","name":"irc"}]},"duration":9779.150700002909,"ttft":5322.065300002694,"contextSnapshot":{"promptTokens":102475,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_xdjWHS0F7Sw6oYqRt2R1MKjM|fc_0d49cba2043f1af0016a59d48b2a4c819786027b7d9f0c557d","toolName":"irc","startedAt":"2026-07-17T07:06:55.650Z","intent":"Checking brain investigator overlap"},"id":"0fc6c450","parentId":"c2cde44a","timestamp":"2026-07-17T07:06:55.650Z"}
{"type":"message","id":"793d4cd9","parentId":"0fc6c450","timestamp":"2026-07-17T07:06:55.651Z","message":{"role":"toolResult","toolCallId":"call_xdjWHS0F7Sw6oYqRt2R1MKjM|fc_0d49cba2043f1af0016a59d48b2a4c819786027b7d9f0c557d","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- BrainCpuRca: woken"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"BrainCpuRca","receipts":[{"to":"BrainCpuRca","outcome":"woken"}]},"isError":false,"timestamp":1784272015650}}
{"type":"message","id":"6a3328e7","parentId":"793d4cd9","timestamp":"2026-07-17T07:07:05.586Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Inspecting test coverage and targets**"},{"type":"toolCall","id":"call_pZ1vjyIepRXSN8U3riVaPVms|fc_047e947d7b905c47016a59d498570c8193bb5181f71db2410d","name":"read","arguments":{"i":"Reading current pool test","path":"crates/spt-daemon/src/dispatch.rs","selector":"1450-1510"}},{"type":"toolCall","id":"call_cWKKKj0GVzzGdNSpGU7nmSm9|fc_047e947d7b905c47016a59d498578c8193b144d01f0ac54664","name":"grep","arguments":{"i":"Finding row plateau tests","pattern":"plateau|stream_infos\\(\\).*len|initiated_locally|retire.*local|physical.*stream|stream.*bounded","path":"crates/spt-daemon/tests; crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/dispatch.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_Q3BUtnIqVNYoY2oqynGsA37p|fc_047e947d7b905c47016a59d498580c8193901fff46eb59b94d","name":"grep","arguments":{"i":"Finding repeated feed harness APIs","pattern":"push_feed|RegistryWorker|run_peer_pump|registry feed|net_stream_open","path":"crates/spt-daemon/tests/mesh_recovery.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/pump.rs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_quWq1ZXnfwuPzN5FIvIx6RMP|fc_047e947d7b905c47016a59d49858888193aa5403df63e9ccdc","name":"read","arguments":{"i":"Reading mesh recovery structure","path":"crates/spt-daemon/tests/mesh_recovery.rs","selector":"1-180"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4802,"output":354,"cacheRead":97792,"cacheWrite":0,"totalTokens":102948,"reasoningTokens":71,"cost":{"input":0.024010000000000004,"output":0.010620000000000001,"cacheRead":0.048895999999999995,"cacheWrite":0,"total":0.083526}},"stopReason":"toolUse","timestamp":1784272015696,"responseId":"resp_047e947d7b905c47016a59d490109c81939c7347a006978b3c","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_047e947d7b905c47016a59d491dcfc8193941edbdb00b5845b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdSYvtQLf3pxXguKoQJqRAFWKKPv6MLlyEuqUIdOhne6qvdhIaj3TTtMWzfAiT98kinzqRj8KAmqPNT-tJKhvKWd76mPHJblTazTkJh0k8bg-zC2E2Xuc21j-D6AYBGuEBvNs2EoJSQPKkQRzyD62z5Bl6Q2_rCn1h5qyzSIY-WqErcjlP-wHVzgO9aDodrIF0hV__XHIMIB8YBIgfMUccHzfBiX6s8vVIbLepqr5ljTwgNMVvr4VoBSgkDSeZG59Kcw2aEmOVr7ejabPvON3SDgNtcnl5tL6t2gOEXXq8vPaKW7M7-gZglVhCgkfZR4xiAXvvnpOGXBTc7zcFI1xa90gVKJXgAbSJGQ6HbpH3UoGUklbmBY8emRPOBsyUKGh3sdk7q-VTyLQut6ZqzM3w1v7gpv17q3o9_TqWGbRTMzbXfppjIeOdoKYvPa1USiqyZ6L6t-RHyzRBIz20tZ5fRAAhNDFGLg3rFYva6pbjgOsgt2HQnASwR8cUcZoOe8twldf46P8nHwU4DEJ93J_GR06QjzBABRe8T_OYdWqHvzoWnaepHNn3fPjacZFuiy8Z1LQwAxIEXzlqp93iTqHsDapJ_GDCKxH4GmevACi_myhMbs1l2PAVkj5aoo1dZYcKMF0oDq8PG01paiipPf5IfhvJgzLIF_G-IZ_sFPysAx4TmNC1Ys-tOkLVtNMcL2_qNUxf9Ng2PEc41t_I9BKuAqPZ_B8ocMMOm70by-rco4tYFW20tpwfuA3Qblzf0PDRKGOLQ110QlksIR5YuCNWYgpmYVADKH9l6APvfLKp4iqcFLUlQHcJEzjK_2-BvIZphXFKoOI2m_8azMWiKH-g8C0gKDvgo8lvM6KClINW4ydTPv3TCvMnhu_c57DwAAduVyrr774iriLbN6AC2wCaEJ1Kx5sr4w-Xg_uajIaOUIuOn0Zo5-2mAftzsTSNAegXsgsdCPveX-zcXqgnq8A0EOn-I0csHnB7ImsQQdp907CVSN3W3n_qgZGVRl-oQK0b_gvd6j_O2r4JTZLZKuOYLu_O8mPHbLyrUAwLdXvXcaETZ_Ujnv7Cmpw0UJ7Ny9RTwZGIWSP2vDisaix-oFAv9pIoPxMqS-gZ7TIbEWxrJ88hUvfmCqBNhAged4lrh9NLhquCzVSZ2ACV4tcpntiS7_JVs1o_czsaHUzoKcEer6ipE2Qzbdb_KSj4VUVWHh2CaWgbaRb7shg8lhUZQMihBAwrOynMWBb2wwWz7RagoSj_Ih3RQeJ4wPwk4IJqBA-Gkdlwmx3o6zdaeTxWkML8DiRKe1fqqFsz1_n5-2vK5IbLuz6hrm9JBD1oBuj3ZqsX8mKyiqnRyfIEmtOFH9N3MraQ==","summary":[{"type":"summary_text","text":"**Inspecting test coverage and targets**"}]},{"id":"fc_047e947d7b905c47016a59d498570c8193bb5181f71db2410d","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading current pool test\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"1450-1510\"}","call_id":"call_pZ1vjyIepRXSN8U3riVaPVms","name":"read"},{"id":"fc_047e947d7b905c47016a59d498578c8193b144d01f0ac54664","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding row plateau tests\",\"pattern\":\"plateau|stream_infos\\\\(\\\\).*len|initiated_locally|retire.*local|physical.*stream|stream.*bounded\",\"path\":\"crates/spt-daemon/tests; crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/dispatch.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_cWKKKj0GVzzGdNSpGU7nmSm9","name":"grep"},{"id":"fc_047e947d7b905c47016a59d498580c8193901fff46eb59b94d","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding repeated feed harness APIs\",\"pattern\":\"push_feed|RegistryWorker|run_peer_pump|registry feed|net_stream_open\",\"path\":\"crates/spt-daemon/tests/mesh_recovery.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/pump.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_Q3BUtnIqVNYoY2oqynGsA37p","name":"grep"},{"id":"fc_047e947d7b905c47016a59d49858888193aa5403df63e9ccdc","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading mesh recovery structure\",\"path\":\"crates/spt-daemon/tests/mesh_recovery.rs\",\"selector\":\"1-180\"}","call_id":"call_quWq1ZXnfwuPzN5FIvIx6RMP","name":"read"}]},"duration":9887.85260000825,"ttft":2264.106700003147,"contextSnapshot":{"promptTokens":102594,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_pZ1vjyIepRXSN8U3riVaPVms|fc_047e947d7b905c47016a59d498570c8193bb5181f71db2410d","toolName":"read","startedAt":"2026-07-17T07:07:05.586Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading current pool test"},"id":"a146f8a2","parentId":"6a3328e7","timestamp":"2026-07-17T07:07:05.586Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_cWKKKj0GVzzGdNSpGU7nmSm9|fc_047e947d7b905c47016a59d498578c8193b144d01f0ac54664","toolName":"grep","startedAt":"2026-07-17T07:07:05.586Z","args":{"path":"crates/spt-daemon/tests; crates/spt-daemon/src/nethost.rs; crates/spt-daemon/src/dispatch.rs"},"intent":"Finding row plateau tests"},"id":"c293f3fa","parentId":"a146f8a2","timestamp":"2026-07-17T07:07:05.586Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Q3BUtnIqVNYoY2oqynGsA37p|fc_047e947d7b905c47016a59d498580c8193901fff46eb59b94d","toolName":"grep","startedAt":"2026-07-17T07:07:05.586Z","args":{"path":"crates/spt-daemon/tests/mesh_recovery.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/pump.rs"},"intent":"Finding repeated feed harness APIs"},"id":"1cb35d7d","parentId":"c293f3fa","timestamp":"2026-07-17T07:07:05.586Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_quWq1ZXnfwuPzN5FIvIx6RMP|fc_047e947d7b905c47016a59d49858888193aa5403df63e9ccdc","toolName":"read","startedAt":"2026-07-17T07:07:05.586Z","args":{"path":"crates/spt-daemon/tests/mesh_recovery.rs"},"intent":"Reading mesh recovery structure"},"id":"92477bc9","parentId":"1cb35d7d","timestamp":"2026-07-17T07:07:05.586Z"}
{"type":"message","id":"aa3c2abe","parentId":"92477bc9","timestamp":"2026-07-17T07:07:05.597Z","message":{"role":"toolResult","toolCallId":"call_quWq1ZXnfwuPzN5FIvIx6RMP|fc_047e947d7b905c47016a59d49858888193aa5403df63e9ccdc","toolName":"read","content":[{"type":"text","text":"//! MESH-RECOVERY production-path regressions (ADR-0039, KNOWN-HAZARDS 7.42):\n//! the bootstrap deletion trap and its fix, exercised at the REAL pump\n//! resolver / failure-lifecycle seam — `run_peer_pump` against real brokers,\n//! real QUIC dials, real `PRESENCE_*` events. No hand-injected routes, no\n//! direct store calls standing in for the pump (gate ruling 1).\n//!\n//! The 2026-07-10 field shape these pin: one transient dial failure used to\n//! DELETE the only cached route while the resolver never consulted the\n//! roster's valid address — with discovery unavailable, isolation prevented\n//! its own repair, and every status surface stayed green.\n\nuse std::sync::atomic::{AtomicBool, AtomicU32, Ordering};\nuse std::sync::{Arc, Mutex, OnceLock};\nuse std::thread;\nuse std::time::Duration;\n\nuse spt_daemon::brain::Brain;\nuse spt_daemon::broker::Broker;\nuse spt_daemon::dispatch::{run_dispatch_loop, DispatchPaths};\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::pump::health::PumpHealth;\nuse spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\nuse spt_daemon::registryhost::RegistryHost;\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_store::epoch::EpochSource;\nuse spt_store::peeraddrs::PeerAddrStore;\nuse spt_store::subnet::SubnetStore;\nuse tempfile::TempDir;\n\nstatic HOME: OnceLock<TempDir> = OnceLock::new();\nstatic LOCK: Mutex<()> = Mutex::new(());\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-meshrec-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    net_broker_with_dial_timeout(name, dir, None)\n}\n\n/// A broker whose host fails dead-peer dials on a SHORT bound instead of the\n/// production 10s (the dialer-side rig knob — `set_quic_op_timeout`), so a\n/// transient-to-a-dead-socket costs the test ~1.5s, not 10.\nfn net_broker_with_dial_timeout(\n    name: &str,\n    dir: &std::path::Path,\n    dial_timeout: Option<Duration>,\n) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    if let Some(t) = dial_timeout {\n        host.set_quic_op_timeout(t);\n    }\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn converge(what: &str, mut probe: impl FnMut() -> bool) {\n    for _ in 0..600 {\n        if probe() {\n            return;\n        }\n        thread::sleep(Duration::from_millis(25));\n    }\n    panic!(\"never converged: {what}\");\n}\n\n/// Run the production dispatcher on the serving node (pump legs pull against\n/// it — without one, a leg's reply-read blocks the whole round on a silent\n/// peer, which is not the seam under test here).\nfn spawn_dispatcher(\n    broker: &str,\n    node_hex: &str,\n    dir: &std::path::Path,\n    stop: &Arc<AtomicBool>,\n) {\n    let registry = Arc::new(RegistryHost::new_at(\n        node_hex,\n        EpochSource::load_from(&dir.join(\"dispatch-epoch.json\")),\n        dir.join(\"dispatch-registry\"),\n    ));\n    let name = broker.to_string();\n    let flag = Arc::clone(stop);\n    let paths = DispatchPaths {\n        owlery: spt_store::perch::owlery_dir(),\n        releases: spt_store::perch::spt_home().join(\"releases\"),\n        scratch: dir.join(\"dispatch-scratch\"),\n    };\n    thread::spawn(move || {\n        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\n    });\n}\n\n/// Build the pump-side node root: paths, subnet membership, and a roster\n/// entry for `peer_hex` carrying `peer_addr` (the valid roster address the\n/// incident nodes held the whole time). Returns the paths.\nfn pump_node(root: &std::path::Path, subnet: &str, peer_hex: &str, peer_addr: Option<serde_json::Value>) -> PumpPaths {\n    let paths = PumpPaths {\n        owlery: root.join(\"owlery\"),\n        ctx_root: root.join(\"context\"),\n        notif_db: root.join(\"notifs.db\"),\n        scratch: root.join(\"scratch\"),\n        subnets: root.join(\"subnet.json\"),\n        vis: root.join(\"visibility.json\"),\n        ops: root.join(\"pump-ops.json\"),\n        releases: root.join(\"releases\"),\n        release_keys: root.join(\"release-keys.json\"),\n        pull_marker: root.join(\"sync-pull-now\"),\n        advertise_marker: root.join(\"advertise-now\"),\n        peer_addrs: root.join(\"peer-addrs.json\"),\n        heartbeat: root.join(\"pump-heartbeat.json\"),\n        rotations: root.join(\"rotation-pending.json\"),\n    };\n    std::fs::create_dir_all(root).unwrap();\n    let mut subnets = SubnetStore::default();\n    subnets.create_subnet(subnet).expect(\"subnet\");\n    subnets.save_to(&paths.subnets).expect(\"save subnets\");\n    let mut roster = spt_store::roster::RosterStore::default();\n    roster.merge_entry(spt_store::roster::RosterEntry {\n        pubkey_hex: peer_hex.to_string(),\n        subnet: subnet.to_string(),\n        label: String::new(),\n        machine_id: String::new(),\n        address: peer_addr,\n        last_seen: String::new(),\n        lease_epoch: 1,\n    });\n    roster.save_to(&root.join(\"roster.json\")).expect(\"save roster\");\n    paths\n}\n\n/// Spawn the production pump loop for the node at `paths` with `resolver` as\n/// its id-only discovery leg and fast cadences. Returns the stop flag.\nfn spawn_pump(\n    broker: &str,\n    self_hex: &str,\n    root: &std::path::Path,\n    paths: PumpPaths,\n    resolver: PeerResolver,\n) -> Arc<AtomicBool> {\n    let stop = Arc::new(AtomicBool::new(false));\n    let registry = Arc::new(RegistryHost::new_at(\n        self_hex,\n        EpochSource::load_from(&root.join(\"epoch.json\")),\n        root.join(\"registry\"),\n    ));\n    let name = broker.to_string();\n    let flag = Arc::clone(&stop);\n    thread::spawn(move || {\n…\n    });\n…\n}\n\n[Showing lines 1-183 of 412. Use :184 to continue]"}],"details":{"truncation":{"content":"//! MESH-RECOVERY production-path regressions (ADR-0039, KNOWN-HAZARDS 7.42):\r\n//! the bootstrap deletion trap and its fix, exercised at the REAL pump\r\n//! resolver / failure-lifecycle seam — `run_peer_pump` against real brokers,\r\n//! real QUIC dials, real `PRESENCE_*` events. No hand-injected routes, no\r\n//! direct store calls standing in for the pump (gate ruling 1).\r\n//!\r\n//! The 2026-07-10 field shape these pin: one transient dial failure used to\r\n//! DELETE the only cached route while the resolver never consulted the\r\n//! roster's valid address — with discovery unavailable, isolation prevented\r\n//! its own repair, and every status surface stayed green.\r\n\r\nuse std::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::sync::{Arc, Mutex, OnceLock};\r\nuse std::thread;\r\nuse std::time::Duration;\r\n\r\nuse spt_daemon::brain::Brain;\r\nuse spt_daemon::broker::Broker;\r\nuse spt_daemon::dispatch::{run_dispatch_loop, DispatchPaths};\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::pump::health::PumpHealth;\r\nuse spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\r\nuse spt_daemon::registryhost::RegistryHost;\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_store::epoch::EpochSource;\r\nuse spt_store::peeraddrs::PeerAddrStore;\r\nuse spt_store::subnet::SubnetStore;\r\nuse tempfile::TempDir;\r\n\r\nstatic HOME: OnceLock<TempDir> = OnceLock::new();\r\nstatic LOCK: Mutex<()> = Mutex::new(());\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-meshrec-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    net_broker_with_dial_timeout(name, dir, None)\r\n}\r\n\r\n/// A broker whose host fails dead-peer dials on a SHORT bound instead of the\r\n/// production 10s (the dialer-side rig knob — `set_quic_op_timeout`), so a\r\n/// transient-to-a-dead-socket costs the test ~1.5s, not 10.\r\nfn net_broker_with_dial_timeout(\r\n    name: &str,\r\n    dir: &std::path::Path,\r\n    dial_timeout: Option<Duration>,\r\n) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    if let Some(t) = dial_timeout {\r\n        host.set_quic_op_timeout(t);\r\n    }\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn converge(what: &str, mut probe: impl FnMut() -> bool) {\r\n    for _ in 0..600 {\r\n        if probe() {\r\n            return;\r\n        }\r\n        thread::sleep(Duration::from_millis(25));\r\n    }\r\n    panic!(\"never converged: {what}\");\r\n}\r\n\r\n/// Run the production dispatcher on the serving node (pump legs pull against\r\n/// it — without one, a leg's reply-read blocks the whole round on a silent\r\n/// peer, which is not the seam under test here).\r\nfn spawn_dispatcher(\r\n    broker: &str,\r\n    node_hex: &str,\r\n    dir: &std::path::Path,\r\n    stop: &Arc<AtomicBool>,\r\n) {\r\n    let registry = Arc::new(RegistryHost::new_at(\r\n        node_hex,\r\n        EpochSource::load_from(&dir.join(\"dispatch-epoch.json\")),\r\n        dir.join(\"dispatch-registry\"),\r\n    ));\r\n    let name = broker.to_string();\r\n    let flag = Arc::clone(stop);\r\n    let paths = DispatchPaths {\r\n        owlery: spt_store::perch::owlery_dir(),\r\n        releases: spt_store::perch::spt_home().join(\"releases\"),\r\n        scratch: dir.join(\"dispatch-scratch\"),\r\n    };\r\n    thread::spawn(move || {\r\n        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\r\n    });\r\n}\r\n\r\n/// Build the pump-side node root: paths, subnet membership, and a roster\r\n/// entry for `peer_hex` carrying `peer_addr` (the valid roster address the\r\n/// incident nodes held the whole time). Returns the paths.\r\nfn pump_node(root: &std::path::Path, subnet: &str, peer_hex: &str, peer_addr: Option<serde_json::Value>) -> PumpPaths {\r\n    let paths = PumpPaths {\r\n        owlery: root.join(\"owlery\"),\r\n        ctx_root: root.join(\"context\"),\r\n        notif_db: root.join(\"notifs.db\"),\r\n        scratch: root.join(\"scratch\"),\r\n        subnets: root.join(\"subnet.json\"),\r\n        vis: root.join(\"visibility.json\"),\r\n        ops: root.join(\"pump-ops.json\"),\r\n        releases: root.join(\"releases\"),\r\n        release_keys: root.join(\"release-keys.json\"),\r\n        pull_marker: root.join(\"sync-pull-now\"),\r\n        advertise_marker: root.join(\"advertise-now\"),\r\n        peer_addrs: root.join(\"peer-addrs.json\"),\r\n        heartbeat: root.join(\"pump-heartbeat.json\"),\r\n        rotations: root.join(\"rotation-pending.json\"),\r\n    };\r\n    std::fs::create_dir_all(root).unwrap();\r\n    let mut subnets = SubnetStore::default();\r\n    subnets.create_subnet(subnet).expect(\"subnet\");\r\n    subnets.save_to(&paths.subnets).expect(\"save subnets\");\r\n    let mut roster = spt_store::roster::RosterStore::default();\r\n    roster.merge_entry(spt_store::roster::RosterEntry {\r\n        pubkey_hex: peer_hex.to_string(),\r\n        subnet: subnet.to_string(),\r\n        label: String::new(),\r\n        machine_id: String::new(),\r\n        address: peer_addr,\r\n        last_seen: String::new(),\r\n        lease_epoch: 1,\r\n    });\r\n    roster.save_to(&root.join(\"roster.json\")).expect(\"save roster\");\r\n    paths\r\n}\r\n\r\n/// Spawn the production pump loop for the node at `paths` with `resolver` as\r\n/// its id-only discovery leg and fast cadences. Returns the stop flag.\r\nfn spawn_pump(\r\n    broker: &str,\r\n    self_hex: &str,\r\n    root: &std::path::Path,\r\n    paths: PumpPaths,\r\n    resolver: PeerResolver,\r\n) -> Arc<AtomicBool> {\r\n    let stop = Arc::new(AtomicBool::new(false));\r\n    let registry = Arc::new(RegistryHost::new_at(\r\n        self_hex,\r\n        EpochSource::load_from(&root.join(\"epoch.json\")),\r\n        root.join(\"registry\"),\r\n    ));\r\n    let name = broker.to_string();\r\n    let flag = Arc::clone(&stop);\r\n    thread::spawn(move || {\r","truncated":true,"truncatedBy":"lines","totalLines":412,"totalBytes":6758,"outputLines":183,"outputBytes":6758,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! MESH-RECOVERY production-path regressions (ADR-0039, KNOWN-HAZARDS 7.42):\r\n//! the bootstrap deletion trap and its fix, exercised at the REAL pump\r\n//! resolver / failure-lifecycle seam — `run_peer_pump` against real brokers,\r\n//! real QUIC dials, real `PRESENCE_*` events. No hand-injected routes, no\r\n//! direct store calls standing in for the pump (gate ruling 1).\r\n//!\r\n//! The 2026-07-10 field shape these pin: one transient dial failure used to\r\n//! DELETE the only cached route while the resolver never consulted the\r\n//! roster's valid address — with discovery unavailable, isolation prevented\r\n//! its own repair, and every status surface stayed green.\r\n\r\nuse std::sync::atomic::{AtomicBool, AtomicU32, Ordering};\r\nuse std::sync::{Arc, Mutex, OnceLock};\r\nuse std::thread;\r\nuse std::time::Duration;\r\n\r\nuse spt_daemon::brain::Brain;\r\nuse spt_daemon::broker::Broker;\r\nuse spt_daemon::dispatch::{run_dispatch_loop, DispatchPaths};\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::pump::health::PumpHealth;\r\nuse spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\r\nuse spt_daemon::registryhost::RegistryHost;\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_store::epoch::EpochSource;\r\nuse spt_store::peeraddrs::PeerAddrStore;\r\nuse spt_store::subnet::SubnetStore;\r\nuse tempfile::TempDir;\r\n\r\nstatic HOME: OnceLock<TempDir> = OnceLock::new();\r\nstatic LOCK: Mutex<()> = Mutex::new(());\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-meshrec-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    net_broker_with_dial_timeout(name, dir, None)\r\n}\r\n\r\n/// A broker whose host fails dead-peer dials on a SHORT bound instead of the\r\n/// production 10s (the dialer-side rig knob — `set_quic_op_timeout`), so a\r\n/// transient-to-a-dead-socket costs the test ~1.5s, not 10.\r\nfn net_broker_with_dial_timeout(\r\n    name: &str,\r\n    dir: &std::path::Path,\r\n    dial_timeout: Option<Duration>,\r\n) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    if let Some(t) = dial_timeout {\r\n        host.set_quic_op_timeout(t);\r\n    }\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn converge(what: &str, mut probe: impl FnMut() -> bool) {\r\n    for _ in 0..600 {\r\n        if probe() {\r\n            return;\r\n        }\r\n        thread::sleep(Duration::from_millis(25));\r\n    }\r\n    panic!(\"never converged: {what}\");\r\n}\r\n\r\n/// Run the production dispatcher on the serving node (pump legs pull against\r\n/// it — without one, a leg's reply-read blocks the whole round on a silent\r\n/// peer, which is not the seam under test here).\r\nfn spawn_dispatcher(\r\n    broker: &str,\r\n    node_hex: &str,\r\n    dir: &std::path::Path,\r\n    stop: &Arc<AtomicBool>,\r\n) {\r\n    let registry = Arc::new(RegistryHost::new_at(\r\n        node_hex,\r\n        EpochSource::load_from(&dir.join(\"dispatch-epoch.json\")),\r\n        dir.join(\"dispatch-registry\"),\r\n    ));\r\n    let name = broker.to_string();\r\n    let flag = Arc::clone(stop);\r\n    let paths = DispatchPaths {\r\n        owlery: spt_store::perch::owlery_dir(),\r\n        releases: spt_store::perch::spt_home().join(\"releases\"),\r\n        scratch: dir.join(\"dispatch-scratch\"),\r\n    };\r\n    thread::spawn(move || {\r\n        let _ = run_dispatch_loop(&name, &paths, registry, &flag, Duration::from_millis(25));\r\n    });\r\n}\r\n\r\n/// Build the pump-side node root: paths, subnet membership, and a roster\r\n/// entry for `peer_hex` carrying `peer_addr` (the valid roster address the\r\n/// incident nodes held the whole time). Returns the paths.\r\nfn pump_node(root: &std::path::Path, subnet: &str, peer_hex: &str, peer_addr: Option<serde_json::Value>) -> PumpPaths {\r\n    let paths = PumpPaths {\r\n        owlery: root.join(\"owlery\"),\r\n        ctx_root: root.join(\"context\"),\r\n        notif_db: root.join(\"notifs.db\"),\r\n        scratch: root.join(\"scratch\"),\r\n        subnets: root.join(\"subnet.json\"),\r\n        vis: root.join(\"visibility.json\"),\r\n        ops: root.join(\"pump-ops.json\"),\r\n        releases: root.join(\"releases\"),\r\n        release_keys: root.join(\"release-keys.json\"),\r\n        pull_marker: root.join(\"sync-pull-now\"),\r\n        advertise_marker: root.join(\"advertise-now\"),\r\n        peer_addrs: root.join(\"peer-addrs.json\"),\r\n        heartbeat: root.join(\"pump-heartbeat.json\"),\r\n        rotations: root.join(\"rotation-pending.json\"),\r\n    };\r\n    std::fs::create_dir_all(root).unwrap();\r\n    let mut subnets = SubnetStore::default();\r\n    subnets.create_subnet(subnet).expect(\"subnet\");\r\n    subnets.save_to(&paths.subnets).expect(\"save subnets\");\r\n    let mut roster = spt_store::roster::RosterStore::default();\r\n    roster.merge_entry(spt_store::roster::RosterEntry {\r\n        pubkey_hex: peer_hex.to_string(),\r\n        subnet: subnet.to_string(),\r\n        label: String::new(),\r\n        machine_id: String::new(),\r\n        address: peer_addr,\r\n        last_seen: String::new(),\r\n        lease_epoch: 1,\r\n    });\r\n    roster.save_to(&root.join(\"roster.json\")).expect(\"save roster\");\r\n    paths\r\n}\r\n\r\n/// Spawn the production pump loop for the node at `paths` with `resolver` as\r\n/// its id-only discovery leg and fast cadences. Returns the stop flag.\r\nfn spawn_pump(\r\n    broker: &str,\r\n    self_hex: &str,\r\n    root: &std::path::Path,\r\n    paths: PumpPaths,\r\n    resolver: PeerResolver,\r\n) -> Arc<AtomicBool> {\r\n    let stop = Arc::new(AtomicBool::new(false));\r\n    let registry = Arc::new(RegistryHost::new_at(\r\n        self_hex,\r\n        EpochSource::load_from(&root.join(\"epoch.json\")),\r\n        root.join(\"registry\"),\r\n    ));\r\n    let name = broker.to_string();\r\n    let flag = Arc::clone(&stop);\r\n    thread::spawn(move || {\r\n…\n    });\n…\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,null,204,null,206]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\mesh_recovery.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":412,"totalBytes":6758,"outputLines":183,"outputBytes":6758,"shownRange":{"start":1,"end":183},"nextOffset":184}}},"isError":false,"timestamp":1784272025596}}
{"type":"message","id":"c2232f99","parentId":"aa3c2abe","timestamp":"2026-07-17T07:07:05.603Z","message":{"role":"toolResult","toolCallId":"call_Q3BUtnIqVNYoY2oqynGsA37p|fc_047e947d7b905c47016a59d498580c8193901fff46eb59b94d","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## mesh_recovery.rs\n 2|//! the bootstrap deletion trap and its fix, exercised at the REAL pump\n*3|//! resolver / failure-lifecycle seam — `run_peer_pump` against real brokers,\n 4|//! real QUIC dials, real `PRESENCE_*` events. No hand-injected routes, no\n 5|//! direct store calls standing in for the pump (gate ruling 1).\n 6|//!\n...\n 21|use spt_daemon::pump::health::PumpHealth;\n*22|use spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\n 23|use spt_daemon::registryhost::RegistryHost;\n 24|use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n 25|use spt_proto::identity::Identity;\n...\n 193|        };\n*194|        let out = run_peer_pump(\n 195|            &name,\n 196|            registry,\n 197|            &paths,\n## redispatch.rs\n 548|    let pinned = probe\n*549|        .net_stream_opener(s1_a)\n 550|        .expect(\"opener query answers\");\n 551|    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\n 552|\n## pump.rs\n 20|use spt_daemon::pump::{\n*21|    run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths,\n 22|};\n 23|use spt_daemon::registryhost::{RegistryGatePolicy, RegistryHost};\n 24|use spt_daemon::relcache::ReleaseCache;\n...\n 412|            };\n*413|            let _ = run_peer_pump(\n 414|                &name,\n 415|                reg,\n 416|                &paths,\n...\n 479|// peer for the round, and the loop continues — so the heartbeat keeps\n*480|// MONOTONICALLY ADVANCING and `run_peer_pump` never bubbles Err (no supervised\n 481|// restart). Pre-fix the dial awaited the dead peer unbounded and the\n 482|// single-threaded pump froze (the heartbeat stops). The ordinary-vs-TimedOut\n 483|// tier-split itself is unit-covered (`pump::peer_outcome` test); this proves the\n...\n 587|            };\n*588|            let r = run_peer_pump(\n 589|                &name,\n 590|                reg,\n 591|                &paths,\n...\n 628|\n*629|    // No supervised restart: `run_peer_pump` exits CLEANLY on stop (Ok), never\n 630|    // bubbles Err — a TimedOut bubble is precisely what would trigger a supervised\n 631|    // restart, and the bound's ordinary error must never become one.\n 632|    stop.store(true, Ordering::Relaxed);\n...\n 637|        result.is_ok(),\n*638|        \"run_peer_pump exits cleanly on stop, never bubbling Err (no supervised restart): {result:?}\"\n 639|    );\n 640|}\n 641|\n...\n 772|            };\n*773|            let r = run_peer_pump(&name, reg, &paths, resolver, config, PumpHooks::default(), &flag);\n 774|            let _ = tx.send(r);\n 775|        });\n 776|    }\n...\n 824|\n*825|    // No supervised restart from a dead PEER: run_peer_pump exits Ok on stop.\n 826|    stop.store(true, Ordering::Relaxed);\n 827|    let result = rx.recv_timeout(Duration::from_secs(15)).expect(\"pump returns after stop\");\n 828|    assert!("}],"details":{"scopePath":"crates/spt-daemon/tests/mesh_recovery.rs, crates/spt-daemon/tests/redispatch.rs, crates/spt-daemon/tests/pump.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":12,"fileCount":3,"files":["crates/spt-daemon/tests/mesh_recovery.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/pump.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/mesh_recovery.rs","count":3},{"path":"crates/spt-daemon/tests/redispatch.rs","count":1},{"path":"crates/spt-daemon/tests/pump.rs","count":8}],"truncated":false,"displayContent":"# crates/spt-daemon/tests/\n## mesh_recovery.rs\n   2│//! the bootstrap deletion trap and its fix, exercised at the REAL pump\n  *3│//! resolver / failure-lifecycle seam — `run_peer_pump` against real brokers,\n   4│//! real QUIC dials, real `PRESENCE_*` events. No hand-injected routes, no\n   5│//! direct store calls standing in for the pump (gate ruling 1).\n   6│//!\n    │...\n  21│use spt_daemon::pump::health::PumpHealth;\n *22│use spt_daemon::pump::{run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths};\n  23│use spt_daemon::registryhost::RegistryHost;\n  24│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n  25│use spt_proto::identity::Identity;\n    │...\n 193│        };\n*194│        let out = run_peer_pump(\n 195│            &name,\n 196│            registry,\n 197│            &paths,\n## redispatch.rs\n 548│    let pinned = probe\n*549│        .net_stream_opener(s1_a)\n 550│        .expect(\"opener query answers\");\n 551│    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\n 552│\n## pump.rs\n  20│use spt_daemon::pump::{\n *21│    run_peer_pump, PeerResolver, PumpCadence, PumpConfig, PumpHooks, PumpPaths,\n  22│};\n  23│use spt_daemon::registryhost::{RegistryGatePolicy, RegistryHost};\n  24│use spt_daemon::relcache::ReleaseCache;\n    │...\n 412│            };\n*413│            let _ = run_peer_pump(\n 414│                &name,\n 415│                reg,\n 416│                &paths,\n    │...\n 479│// peer for the round, and the loop continues — so the heartbeat keeps\n*480│// MONOTONICALLY ADVANCING and `run_peer_pump` never bubbles Err (no supervised\n 481│// restart). Pre-fix the dial awaited the dead peer unbounded and the\n 482│// single-threaded pump froze (the heartbeat stops). The ordinary-vs-TimedOut\n 483│// tier-split itself is unit-covered (`pump::peer_outcome` test); this proves the\n    │...\n 587│            };\n*588│            let r = run_peer_pump(\n 589│                &name,\n 590│                reg,\n 591│                &paths,\n    │...\n 628│\n*629│    // No supervised restart: `run_peer_pump` exits CLEANLY on stop (Ok), never\n 630│    // bubbles Err — a TimedOut bubble is precisely what would trigger a supervised\n 631│    // restart, and the bound's ordinary error must never become one.\n 632│    stop.store(true, Ordering::Relaxed);\n    │...\n 637│        result.is_ok(),\n*638│        \"run_peer_pump exits cleanly on stop, never bubbling Err (no supervised restart): {result:?}\"\n 639│    );\n 640│}\n 641│\n    │...\n 772│            };\n*773│            let r = run_peer_pump(&name, reg, &paths, resolver, config, PumpHooks::default(), &flag);\n 774│            let _ = tx.send(r);\n 775│        });\n 776│    }\n    │...\n 824│\n*825│    // No supervised restart from a dead PEER: run_peer_pump exits Ok on stop.\n 826│    stop.store(true, Ordering::Relaxed);\n 827│    let result = rx.recv_timeout(Duration::from_secs(15)).expect(\"pump returns after stop\");\n 828│    assert!("},"isError":false,"timestamp":1784272025603}}
{"type":"message","id":"3416f50e","parentId":"c2232f99","timestamp":"2026-07-17T07:07:05.612Z","message":{"role":"toolResult","toolCallId":"call_pZ1vjyIepRXSN8U3riVaPVms|fc_047e947d7b905c47016a59d498570c8193bb5181f71db2410d","toolName":"read","content":[{"type":"text","text":"    fn dispatch_event_carries_the_complete_key_set() {\n        let ev = dispatch_event(\n…\n            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),\n…\n        ];\n…\n    }\n\n[Showing lines 1449-1513 of 1558. Use :1514 to continue]"}],"details":{"truncation":{"content":"            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),","truncated":true,"truncatedBy":"lines","totalLines":110,"totalBytes":2874,"outputLines":65,"outputBytes":2874,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn dispatch_event_carries_the_complete_key_set() {\n        let ev = dispatch_event(\n…\n            Some(StreamFamily::Attach),\n            2,\n            41,\n            \"worker-outcome\",\n            \"Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        );\n        for key in [\n            \"gen=1721111111\",\n            \"stream=7058\",\n            \"family=Attach\",\n            \"attempt=2\",\n            \"conn=41\",\n            \"event=worker-outcome\",\n            \"detail=Failed(x) elapsed_ms=15012 endpoint=webbie\",\n        ] {\n            assert!(ev.contains(key), \"missing {key:?} in {ev:?}\");\n        }\n        assert!(ev.starts_with(\"DISPATCH_EV: \"), \"stable grep prefix\");\n\n        // Pre-classification events name the family honestly.\n        let unclassified = dispatch_event(1, 2, None, 0, 3, \"breaker-trip\", \"window_ms=2000\");\n        assert!(unclassified.contains(\"family=unclassified\"), \"{unclassified:?}\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the pool bound under a cold\n    // flood (Amendment fix 7): walking a cold enumeration through the loop's\n    // admission gate claims exactly MAX_DISPATCH_WORKERS rows per poll —\n    // the rest wait for the next tick; a freed slot re-admits.\n    #[test]\n    fn cold_enumeration_claims_at_most_the_pool_bound_per_poll() {\n        let mut active = 0usize;\n        let mut claimed = 0usize;\n        for _row in 0..50 {\n            if !pool_has_room(active) {\n                break; // the loop's cold-table break\n            }\n            active += 1;\n            claimed += 1;\n        }\n        assert_eq!(\n            claimed, MAX_DISPATCH_WORKERS,\n            \"a 50-row cold table claims exactly the pool bound in one poll\"\n        );\n        // A worker finishing frees a slot for the next tick.\n        active -= 1;\n        assert!(pool_has_room(active), \"a freed slot re-admits next poll\");\n    }\n\n    // [unit->REQ-DISPATCH-HYGIENE-TELEMETRY] the stream->endpoint binding key\n    // (Amendment fix 9, hertz's live catch: stream 7058's identity was\n    // timing-correlation only): each endpoint-bearing family resolves its\n    // opener's identity field; families without an endpoint concept answer\n    // None honestly, as does a blank/missing field.\n    #[test]\n    fn line_endpoint_resolves_each_family_identity_key() {\n        use serde_json::json;\n        let cases = [\n            (StreamFamily::Attach, json!({\"endpoint_id\": \"webbie\"}), Some(\"webbie\")),\n            (StreamFamily::Rest, json!({\"endpoint\": \"ling\"}), Some(\"ling\")),\n            (StreamFamily::Xfer, json!({\"endpoint\": \"oak\"}), Some(\"oak\")),\n            (StreamFamily::ShellLink, json!({\"owner\": \"doyle\"}), Some(\"doyle\")),\n            (StreamFamily::WanMsg, json!({\"target\": \"todlando\"}), Some(\"todlando\")),\n            // No endpoint concept -> honest None.\n            (StreamFamily::Sync, json!({\"endpoint_id\": \"x\", \"endpoint\": \"x\"}), None),\n            (StreamFamily::Registry, json!({\"target\": \"x\"}), None),\n…\n        ];\n…\n    }","startLine":1445,"lineNumbers":[1445,1446,null,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,null,1517,null,1525]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":2874,"outputLines":65,"outputBytes":2874,"shownRange":{"start":1449,"end":1513},"nextOffset":1514}}},"isError":false,"timestamp":1784272025612}}
{"type":"message","id":"c0d3028e","parentId":"3416f50e","timestamp":"2026-07-17T07:07:05.621Z","message":{"role":"toolResult","toolCallId":"call_cWKKKj0GVzzGdNSpGU7nmSm9|fc_047e947d7b905c47016a59d498578c8193b144d01f0ac54664","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## tests/\n### attach.rs\n 114|/// Poll the brain's `net-streams` until a PEER-initiated stream appears; return\n*115|/// `(stream_id, remote_id_hex)`. Filtering on `!initiated_locally` is what the\n 116|/// production dispatch loop does (dispatch.rs) — and it is REQUIRED for the\n 117|/// single-broker loopback case, where the table holds BOTH the operator's own\n 118|/// row and the cross-wired peer row: the serve target is always the peer row.\n...\n 121|        let reply = brain.net_streams().expect(\"net-streams\");\n*122|        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 123|            return (s.stream_id, s.remote_id_hex.clone());\n 124|        }\n 125|        thread::sleep(Duration::from_millis(5));\n### dispatch.rs\n 387|            .iter()\n*388|            .any(|s| s.stream_id == opened.stream_id && s.initiated_locally),\n 389|        \"A's own open is locally-initiated in A's table\"\n 390|    );\n 391|    let b_rows = b_probe.net_streams().expect(\"B streams\").streams;\n 392|    assert!(\n*393|        b_rows.iter().all(|s| !s.initiated_locally),\n 394|        \"every row at B is peer-initiated (B opened nothing)\"\n 395|    );\n 396|\n### inject_control_wedge.rs\n 598|        let reply = brain.net_streams().expect(\"net-streams\");\n*599|        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 600|            return Some((s.stream_id, s.remote_id_hex.clone()));\n 601|        }\n 602|        thread::sleep(Duration::from_millis(5));\n### input_ack_deadlock.rs\n 209|        let reply = brain.net_streams().expect(\"net-streams\");\n*210|        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 211|            return Some((s.stream_id, s.remote_id_hex.clone()));\n 212|        }\n 213|        thread::sleep(Duration::from_millis(5));\n### pumpdeadline.rs\n 10|//! The wedge this guards: the 2.2h hfenduleam pump stall on\n*11|//! `net_open_stream`'s unbounded `loop { read_event }` against a black-holed\n 12|//! peer the broker never answered for.\n 13|\n 14|use std::io;\n### redispatch.rs\n 148|        .iter()\n*149|        .filter(|s| !s.initiated_locally)\n 150|        .map(|s| s.stream_id)\n 151|        .collect()\n 152|}\n...\n 463|                .iter()\n*464|                .find(|s| !s.initiated_locally && s.finished)\n 465|            {\n 466|                break s.stream_id;\n 467|            }\n### redispatch_stall.rs\n 5|//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n*6|//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n 7|//! windows (33 observed, all 15,000-15,154ms).\n 8|//!\n 9|//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns\n...\n 162|        .iter()\n*163|        .filter(|s| !s.initiated_locally)\n 164|        .map(|s| s.stream_id)\n 165|        .collect()\n 166|}\n\n## src/\n### nethost.rs\n 59|/// Broker-side QUIC-op deadline (REQ-HAZARD-BROKER-QUIC-DEADLINE), in ms. Each\n*60|/// brain-waiting QUIC await — `dial` / `open_stream` / `send_stream` — is bounded\n 61|/// by this so a black-holed or dead peer fails PROMPTLY with an ORDINARY error\n 62|/// the broker replies, never an unbounded await that wedges the single-threaded\n 63|/// pump (the B-half of REQ-HAZARD-PUMP-IPC-DEADLINE; the 2.2h hfenduleam wedge).\n...\n 783|    /// that served its own requester stream would deadlock on itself).\n*784|    initiated_locally: bool,\n 785|    /// The send half, serialized behind an async mutex (writes happen on the\n 786|    /// host's runtime via `block_on`).\n 787|    send: tokio::sync::Mutex<SendHalf>,\n...\n 997|/// its read pump. Must run inside the host's runtime context.\n*998|/// `initiated_locally` records the direction fact for the table (true only\n 999|/// from [`NetHost::open_stream`]).\n 1000|fn register_stream(\n 1001|    shared: &Arc<NetShared>,\n...\n 1005|    recv: RecvHalf,\n*1006|    initiated_locally: bool,\n 1007|) -> u64 {\n 1008|    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n 1009|    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n...\n 1015|            remote_id_hex: remote_id_hex.to_string(),\n*1016|            initiated_locally,\n 1017|            send: tokio::sync::Mutex::new(send),\n 1018|            log: Arc::clone(&log),\n 1019|            room: Arc::clone(&room),\n...\n 1032|    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\n*1033|    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\n 1034|    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\n 1035|    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n 1036|    tokio::spawn(async move {\n...\n 1641|    /// so the open mints a CROSS-WIRED PAIR in-process: an operator row\n*1642|    /// (`initiated_locally = true`, the returned id) and a peer row\n*1643|    /// (`initiated_locally = false`) the inbound dispatcher claims and serves via\n 1644|    /// `serve_attach` — exactly as an `accept_bi` would surface a remote-opened\n 1645|    /// stream. One `tokio::io::duplex` wires both directions; each row's read\n 1646|    /// pump and `StreamLog` are the SAME machinery the QUIC path uses (the W1.5\n...\n 1817|                    finished: log.finished,\n*1818|                    initiated_locally: e.initiated_locally,\n 1819|                    floor_seq: log.floor_seq(),\n 1820|                }\n 1821|            })\n...\n 1876|    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\n*1877|    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\n 1878|    /// No-op if the stream is gone.\n 1879|    // [impl->REQ-SHELL-4]\n 1880|    pub fn mark_stream_retentive(&self, stream_id: u64, cap: usize) {\n...\n 1920|            .iter()\n*1921|            .filter(|(_, e)| e.conn_id == conn_id && !e.initiated_locally)\n 1922|            .map(|(id, _)| *id)\n 1923|            .collect()\n 1924|    }\n...\n 2170|            .expect(\"operator row present\");\n*2171|        assert!(op.initiated_locally, \"the returned id is the operator row\");\n 2172|        let peer = infos\n 2173|            .iter()\n*2174|            .find(|s| !s.initiated_locally)\n 2175|            .expect(\"peer row present\");\n 2176|        assert_eq!(peer.conn_id, c1);\n 2177|        assert_eq!(\n...\n 2372|\n*2373|    // [unit->REQ-STREAM-OPENER-DURABLE] capture is BOUNDED: a stream whose\n 2374|    // first bytes never carry a newline gives up at the cap (Oversize is\n 2375|    // terminal — a late newline cannot resurrect it), so a non-NDJSON stream\n 2376|    // costs one bounded buffer, never unbounded memory.\n### dispatch.rs\n 14|//! The stream table holds locally-opened outbound streams too. The dispatcher\n*15|//! claims only rows with `initiated_locally == false` — serving our own\n 16|//! requester stream would deadlock the requester against ourselves. The flag\n 17|//! is broker-recorded at registration (`open_stream` vs the accept loop),\n 18|//! never inferred.\n...\n 469|            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n*470|            if s.initiated_locally || s.next_seq == 0 {\n 471|                continue;\n 472|            }\n 473|            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n...\n 1272|    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n*1273|    // — a persistently-failing stream retries exactly its bounded budget and\n 1274|    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n 1275|    // abandoned the stream on failure #1 forever.\n 1276|    #[test]"}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt-daemon/src/nethost.rs, crates/spt-daemon/src/dispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":28,"fileCount":9,"files":["crates/spt-daemon/tests/attach.rs","crates/spt-daemon/tests/dispatch.rs","crates/spt-daemon/tests/inject_control_wedge.rs","crates/spt-daemon/tests/input_ack_deadlock.rs","crates/spt-daemon/tests/pumpdeadline.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/redispatch_stall.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/attach.rs","count":2},{"path":"crates/spt-daemon/tests/dispatch.rs","count":2},{"path":"crates/spt-daemon/tests/inject_control_wedge.rs","count":1},{"path":"crates/spt-daemon/tests/input_ack_deadlock.rs","count":1},{"path":"crates/spt-daemon/tests/pumpdeadline.rs","count":1},{"path":"crates/spt-daemon/tests/redispatch.rs","count":2},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","count":2},{"path":"crates/spt-daemon/src/nethost.rs","count":14},{"path":"crates/spt-daemon/src/dispatch.rs","count":3}],"truncated":false,"displayContent":"# crates/spt-daemon/\n\n## tests/\n### attach.rs\n 114│/// Poll the brain's `net-streams` until a PEER-initiated stream appears; return\n*115│/// `(stream_id, remote_id_hex)`. Filtering on `!initiated_locally` is what the\n 116│/// production dispatch loop does (dispatch.rs) — and it is REQUIRED for the\n 117│/// single-broker loopback case, where the table holds BOTH the operator's own\n 118│/// row and the cross-wired peer row: the serve target is always the peer row.\n    │...\n 121│        let reply = brain.net_streams().expect(\"net-streams\");\n*122│        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 123│            return (s.stream_id, s.remote_id_hex.clone());\n 124│        }\n 125│        thread::sleep(Duration::from_millis(5));\n### dispatch.rs\n 387│            .iter()\n*388│            .any(|s| s.stream_id == opened.stream_id && s.initiated_locally),\n 389│        \"A's own open is locally-initiated in A's table\"\n 390│    );\n 391│    let b_rows = b_probe.net_streams().expect(\"B streams\").streams;\n 392│    assert!(\n*393│        b_rows.iter().all(|s| !s.initiated_locally),\n 394│        \"every row at B is peer-initiated (B opened nothing)\"\n 395│    );\n 396│\n### inject_control_wedge.rs\n 598│        let reply = brain.net_streams().expect(\"net-streams\");\n*599│        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 600│            return Some((s.stream_id, s.remote_id_hex.clone()));\n 601│        }\n 602│        thread::sleep(Duration::from_millis(5));\n### input_ack_deadlock.rs\n 209│        let reply = brain.net_streams().expect(\"net-streams\");\n*210│        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 211│            return Some((s.stream_id, s.remote_id_hex.clone()));\n 212│        }\n 213│        thread::sleep(Duration::from_millis(5));\n### pumpdeadline.rs\n 10│//! The wedge this guards: the 2.2h hfenduleam pump stall on\n*11│//! `net_open_stream`'s unbounded `loop { read_event }` against a black-holed\n 12│//! peer the broker never answered for.\n 13│\n 14│use std::io;\n### redispatch.rs\n 148│        .iter()\n*149│        .filter(|s| !s.initiated_locally)\n 150│        .map(|s| s.stream_id)\n 151│        .collect()\n 152│}\n    │...\n 463│                .iter()\n*464│                .find(|s| !s.initiated_locally && s.finished)\n 465│            {\n 466│                break s.stream_id;\n 467│            }\n### redispatch_stall.rs\n   5│//! subscribers whose `StreamLog::attach` replayed the whole ring UNDER the\n  *6│//! per-stream mutex against wedged conns — serial 15s bounded-write poison\n   7│//! windows (33 observed, all 15,000-15,154ms).\n   8│//!\n   9│//! Legs (triage matrix T1-T7; T1/T3/T4 ride REAL wedged subscriber conns\n    │...\n 162│        .iter()\n*163│        .filter(|s| !s.initiated_locally)\n 164│        .map(|s| s.stream_id)\n 165│        .collect()\n 166│}\n\n## src/\n### nethost.rs\n   59│/// Broker-side QUIC-op deadline (REQ-HAZARD-BROKER-QUIC-DEADLINE), in ms. Each\n  *60│/// brain-waiting QUIC await — `dial` / `open_stream` / `send_stream` — is bounded\n   61│/// by this so a black-holed or dead peer fails PROMPTLY with an ORDINARY error\n   62│/// the broker replies, never an unbounded await that wedges the single-threaded\n   63│/// pump (the B-half of REQ-HAZARD-PUMP-IPC-DEADLINE; the 2.2h hfenduleam wedge).\n     │...\n  783│    /// that served its own requester stream would deadlock on itself).\n *784│    initiated_locally: bool,\n  785│    /// The send half, serialized behind an async mutex (writes happen on the\n  786│    /// host's runtime via `block_on`).\n  787│    send: tokio::sync::Mutex<SendHalf>,\n     │...\n  997│/// its read pump. Must run inside the host's runtime context.\n *998│/// `initiated_locally` records the direction fact for the table (true only\n  999│/// from [`NetHost::open_stream`]).\n 1000│fn register_stream(\n 1001│    shared: &Arc<NetShared>,\n     │...\n 1005│    recv: RecvHalf,\n*1006│    initiated_locally: bool,\n 1007│) -> u64 {\n 1008│    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n 1009│    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n     │...\n 1015│            remote_id_hex: remote_id_hex.to_string(),\n*1016│            initiated_locally,\n 1017│            send: tokio::sync::Mutex::new(send),\n 1018│            log: Arc::clone(&log),\n 1019│            room: Arc::clone(&room),\n     │...\n 1032│    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so\n*1033│    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the\n 1034│    // SENDER — \"lag, never loss\" (CONTEXT:262). A `drain` wakes `room`. Ordinary\n 1035│    // streams have `retentive_cap == 0`, so this gate is a no-op for them.\n 1036│    tokio::spawn(async move {\n     │...\n 1641│    /// so the open mints a CROSS-WIRED PAIR in-process: an operator row\n*1642│    /// (`initiated_locally = true`, the returned id) and a peer row\n*1643│    /// (`initiated_locally = false`) the inbound dispatcher claims and serves via\n 1644│    /// `serve_attach` — exactly as an `accept_bi` would surface a remote-opened\n 1645│    /// stream. One `tokio::io::duplex` wires both directions; each row's read\n 1646│    /// pump and `StreamLog` are the SAME machinery the QUIC path uses (the W1.5\n     │...\n 1817│                    finished: log.finished,\n*1818│                    initiated_locally: e.initiated_locally,\n 1819│                    floor_seq: log.floor_seq(),\n 1820│                }\n 1821│            })\n     │...\n 1876│    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\n*1877│    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\n 1878│    /// No-op if the stream is gone.\n 1879│    // [impl->REQ-SHELL-4]\n 1880│    pub fn mark_stream_retentive(&self, stream_id: u64, cap: usize) {\n     │...\n 1920│            .iter()\n*1921│            .filter(|(_, e)| e.conn_id == conn_id && !e.initiated_locally)\n 1922│            .map(|(id, _)| *id)\n 1923│            .collect()\n 1924│    }\n     │...\n 2170│            .expect(\"operator row present\");\n*2171│        assert!(op.initiated_locally, \"the returned id is the operator row\");\n 2172│        let peer = infos\n 2173│            .iter()\n*2174│            .find(|s| !s.initiated_locally)\n 2175│            .expect(\"peer row present\");\n 2176│        assert_eq!(peer.conn_id, c1);\n 2177│        assert_eq!(\n     │...\n 2372│\n*2373│    // [unit->REQ-STREAM-OPENER-DURABLE] capture is BOUNDED: a stream whose\n 2374│    // first bytes never carry a newline gives up at the cap (Oversize is\n 2375│    // terminal — a late newline cannot resurrect it), so a non-NDJSON stream\n 2376│    // costs one bounded buffer, never unbounded memory.\n### dispatch.rs\n   14│//! The stream table holds locally-opened outbound streams too. The dispatcher\n  *15│//! claims only rows with `initiated_locally == false` — serving our own\n   16│//! requester stream would deadlock the requester against ourselves. The flag\n   17│//! is broker-recorded at registration (`open_stream` vs the accept loop),\n   18│//! never inferred.\n     │...\n  469│            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n *470│            if s.initiated_locally || s.next_seq == 0 {\n  471│                continue;\n  472│            }\n  473│            if !pool_has_room(active_workers.load(Ordering::Relaxed)) {\n     │...\n 1272│    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n*1273│    // — a persistently-failing stream retries exactly its bounded budget and\n 1274│    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n 1275│    // abandoned the stream on failure #1 forever.\n 1276│    #[test]"},"isError":false,"timestamp":1784272025621}}
{"type":"message","id":"36d2b5b9","parentId":"c0d3028e","timestamp":"2026-07-17T07:07:12.941Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning regression test integration**"},{"type":"toolCall","id":"call_GGQzTM2JLTw8p7PQVC7MvxkV|fc_085c825288103f90016a59d49ead34819594ce6b4e707ee5e8","name":"read","arguments":{"i":"Reading pump harness and cleanup","path":"crates/spt-daemon/tests/mesh_recovery.rs","selector":"160-280,280-420"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":10768,"output":85,"cacheRead":97792,"cacheWrite":0,"totalTokens":108645,"reasoningTokens":37,"cost":{"input":0.053840000000000006,"output":0.00255,"cacheRead":0.048895999999999995,"cacheWrite":0,"total":0.105286}},"stopReason":"toolUse","timestamp":1784272025629,"responseId":"resp_085c825288103f90016a59d499f160819597cd7c2ef2f8780a","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_085c825288103f90016a59d49be4188195b0d8e66da0b7999a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdSethx-40JQSKCVhYSRzfjNZIot2ueZNrtaysS-fg2Vrdz-EDNR-uhVPNEXfpJCHjFFSC9RhnjOJX57PaVShndhqhpJpOw95HnBTzCTYba2tnkZ2m54Feijl-SQZ0OEw--nfDprlB0f47Z5cag2Gco8nCR28jSIHANGzsv5rQZXmv24dW8QMM7wOqdB7WSdDxeqjxE2ZiD45jfYGw2AUETy627InsL-iSUh6KvYlTwMTbXarpVbVJBTVnPV_1jwkzvXm-qdpzFIrGhq0HOlTzFZVZUmfJdagSuQ6PlW4SEwf1z6K2JUvv4eW0yCAS5-ZQTrxOFpf8IMf9l2SjjRlvU-myBlqtJ11_6xUDJoXP4nLPZz-YGUHPp0M6yM8ua4kdtWz9sZlPV_Y2bawXGwTalj6vEoHc5_wgiIVVS8nRAtxBEpg-pCAB3Po-hLQu3P7BJnGduHeRjboHeauDZK8Pp48dGo3hSs3x-LCUPmLbW6xWTacxUeaQfjtBgSpNBlMSWHKX7-5hSBRBhWqGLlIb21LpLXjiEdpwjTRJ1k7Vkkv8M-gLqxBZ39KDQHaqb0SoHYSK6oWwOnBvsjjip9N3d96sNTpcmmRzrWRDyjVGSGa3sNoSsJ7s2q4kYbCkG5ThONY4HmSzGHP77evP6PjMTsd8uXDevZ6ApetgDs04vXODEMk7xp2GAPIU91VPI_qs8OSj4-OS3IO_V8AjNZRorAM_mQI0LaFoMOYzTUVn4UOHOE5SKUVksVmy-M_hZ5dNlnISUSFfTHBA_bXgxxgTNNv2zdqQGIDL3x9RySphm7CNp6FnLC_SKzIETi3qk0Y_S_K41TCdw33LrC5MNjZcrr-iFavw_tSu1NmoB8T3GU4YzqkLfXpVK_lZnUVvzxMY5I9Eu27ECtw6alGjhAW5QZVnWABJGKqoHVIWq5aY3J8kAOtQgpZTFtmlDKzq88pQ_Sc8UdAT-j1YgjETFoVs_bGdIX20y4BAB62qMUDKqyoUVR8pn8hachPKrQEIwncL1_Ofg_RaPcswGa6xAPcNrlCYBf0gXay5kSk-ESRIYbUdFz0aCjTWWnlKRsLBexT5WRUThspnWpACO4NOX42F4LmjEa7b3folszST8sdLcu9iEgzeTRQe-S60MFVv9k0zqvxDlax4ZAnGEhYSCruEOJ_g==","summary":[{"type":"summary_text","text":"**Planning regression test integration**"}]},{"id":"fc_085c825288103f90016a59d49ead34819594ce6b4e707ee5e8","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump harness and cleanup\",\"path\":\"crates/spt-daemon/tests/mesh_recovery.rs\",\"selector\":\"160-280,280-420\"}","call_id":"call_GGQzTM2JLTw8p7PQVC7MvxkV","name":"read"}]},"duration":7311.04999999702,"ttft":2275.348600000143,"contextSnapshot":{"promptTokens":108560,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GGQzTM2JLTw8p7PQVC7MvxkV|fc_085c825288103f90016a59d49ead34819594ce6b4e707ee5e8","toolName":"read","startedAt":"2026-07-17T07:07:12.941Z","args":{"path":"crates/spt-daemon/tests/mesh_recovery.rs"},"intent":"Reading pump harness and cleanup"},"id":"8ab4fafe","parentId":"36d2b5b9","timestamp":"2026-07-17T07:07:12.941Z"}
{"type":"message","id":"7e036e09","parentId":"8ab4fafe","timestamp":"2026-07-17T07:07:12.952Z","message":{"role":"toolResult","toolCallId":"call_GGQzTM2JLTw8p7PQVC7MvxkV|fc_085c825288103f90016a59d49ead34819594ce6b4e707ee5e8","toolName":"read","content":[{"type":"text","text":"fn pump_node(root: &std::path::Path, subnet: &str, peer_hex: &str, peer_addr: Option<serde_json::Value>) -> PumpPaths {\n…\n    roster.merge_entry(spt_store::roster::RosterEntry {\n…\n        last_seen: String::new(),\n        lease_epoch: 1,\n    });\n    roster.save_to(&root.join(\"roster.json\")).expect(\"save roster\");\n    paths\n}\n\n/// Spawn the production pump loop for the node at `paths` with `resolver` as\n/// its id-only discovery leg and fast cadences. Returns the stop flag.\nfn spawn_pump(\n    broker: &str,\n    self_hex: &str,\n    root: &std::path::Path,\n    paths: PumpPaths,\n    resolver: PeerResolver,\n) -> Arc<AtomicBool> {\n    let stop = Arc::new(AtomicBool::new(false));\n    let registry = Arc::new(RegistryHost::new_at(\n        self_hex,\n        EpochSource::load_from(&root.join(\"epoch.json\")),\n        root.join(\"registry\"),\n    ));\n    let name = broker.to_string();\n    let flag = Arc::clone(&stop);\n    thread::spawn(move || {\n        let config = PumpConfig {\n            cadence: PumpCadence {\n                registry: Duration::from_millis(100),\n                notif: Duration::from_millis(100),\n                sync: Duration::from_millis(100),\n                update: Duration::from_millis(100),\n                registry_evict_after: Duration::from_secs(300),\n            },\n            full_auto_update: false,\n        };\n        let out = run_peer_pump(\n            &name,\n            registry,\n            &paths,\n            resolver,\n            config,\n            PumpHooks::default(),\n            &flag,\n        );\n        eprintln!(\"TEST_PUMP_EXIT: {out:?}\");\n    });\n    stop\n}\n\n// [int->REQ-HAZARD-MESH-BOOTSTRAP-TRAP] [int->REQ-PEER-ROUTE-CHAIN] THE trap\n// regression, exactly the RCA's shape: a valid roster address + a stale cache\n// row, ONE real dial failure (the cached address reaches a live socket owned\n// by the WRONG identity — the dial fails like any transient), and id-only\n// discovery DISABLED. Pre-fix: the failure deleted the cache row, the\n// resolver never read the roster, and with discovery dark the node was\n// stranded forever (this test times out). Post-fix: the row is DEMOTED (never\n// deleted — sampled throughout), the next round resolves the ROSTER address,\n// and the mesh converges with ZERO state surgery.\n#[test]\nfn roster_route_survives_a_transient_dial_failure_with_discovery_disabled() {\n    init_home();\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\n    let dir = TempDir::new().unwrap();\n    let (a_name, b_name) = (unique_name(), unique_name());\n    // A's dials fail fast (1.5s) — the everyday transient must not pin the\n    // test on the production 10s bound.\n    let _a_broker =\n        net_broker_with_dial_timeout(&a_name, dir.path(), Some(Duration::from_millis(1500)));\n    let _b_broker = net_broker(&b_name, dir.path());\n    // The stale address: a socket that WAS a real endpoint and is now dead\n    // (host dropped, port closed) — the field shape (machine moved/rebooted).\n    // NOT a live wrong-identity host: iroh accumulates dial candidates per\n    // endpoint id, so a live decoy's socket would poison later dials for B's\n    // id — a distinct hazard class, out of this wave's scope (seeded).\n    let dead_addr = {\n        let ghost = NetHost::start(hermetic()).expect(\"ghost host\");\n        let a = ghost.addr();\n        drop(ghost);\n        a\n    };\n\n    let mut a_probe = connect_retry(&a_name);\n    let mut b_probe = connect_retry(&b_name);\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\n\n    let a_root = dir.path().join(\"a\");\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, Some(b_addr.clone()));\n    let dstop = Arc::new(AtomicBool::new(false));\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\n\n    // The stale cache row: B's key → the dead socket claiming B's id (a\n    // last-known address whose machine moved — the everyday transient).\n    let mut stale = serde_json::to_value(&dead_addr).expect(\"dead addr json\");\n    stale[\"id\"] = serde_json::Value::String(b_hex.clone());\n    let mut pa = PeerAddrStore::default();\n    assert!(pa.put(&b_hex, stale.clone()), \"stale-but-valid row seeds\");\n    pa.save_to(&paths.peer_addrs).unwrap();\n\n    // Id-only discovery DISABLED — the RCA's stalled-discovery arm.\n    let peer_addrs = paths.peer_addrs.clone();\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, Box::new(|_| None));\n\n    // Convergence = a REAL connect to B: health reports a live peer (only a\n    // `PRESENCE_CONNECTED` moves it), and the route under B's key is no\n    // longer the stale decoy value (the observed write-back or the roster\n    // reconcile superseded it — either way, the trap did not hold). While\n    // polling, ALSO assert retention: the row must exist at EVERY sample — a\n    // reintroduced drop-on-fail leaves a visible hole (and then hangs this\n    // test forever, since discovery is dark).\n    converge(\"roster route converged after the transient\", || {\n        let s = PeerAddrStore::load_from(&peer_addrs);\n        assert!(\n            s.get(&b_hex).is_some(),\n            \"the cached route must NEVER disappear (demote, not delete)\"\n        );\n        let healed = matches!(s.valid_route(&b_hex), Some(v) if *v != stale);\n        let live = PumpHealth::load_from(&health_path).live_peers > 0;\n        healed && live\n    });\n    stop.store(true, Ordering::Relaxed);\n    dstop.store(true, Ordering::Relaxed);\n}\n\n// [int->REQ-PEERADDR-INVARIANT] [int->REQ-PEER-ROUTE-CHAIN] the incident's\n// poison-row class on the production path: the cache holds outer-key B → an\n// address claiming a DIFFERENT identity (the `5ff…`-outer shape found live on\n// both incident nodes). First post-upgrade pump start repairs it from the\n// roster (never bare-deletes the file), and the repaired route converges —\n// the poison value is never dialed as B (it cannot be: the chain validates\n// id-match on every leg).\n#[test]\nfn poison_cache_row_is_repaired_from_the_roster_at_startup() {\n    init_home();\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\n    let dir = TempDir::new().unwrap();\n    let (a_name, b_name) = (unique_name(), unique_name());\n    let _a_broker = net_broker(&a_name, dir.path());\n    let _b_broker = net_broker(&b_name, dir.path());\n\n    let mut a_probe = connect_retry(&a_name);\n    let mut b_probe = connect_retry(&b_name);\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\n\n    let a_root = dir.path().join(\"a\");\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, Some(b_addr.clone()));\n\n    let dstop = Arc::new(AtomicBool::new(false));\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\n    // The poison row, seeded the way history did it (pre-validation writers):\n    // outer key B, nested id = somebody else entirely.\n    let mut poison_store = PeerAddrStore::default();\n    poison_store.addrs.insert(\n        b_hex.clone(),\n        serde_json::json!({\"id\": \"0\".repeat(64), \"addrs\": [\"127.0.0.1:9\"]}),\n    );\n    poison_store.save_to(&paths.peer_addrs).unwrap();\n\n    let peer_addrs = paths.peer_addrs.clone();\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, Box::new(|_| None));\n\n    converge(\"poison row repaired + route converged\", || {\n        let s = PeerAddrStore::load_from(&peer_addrs);\n        // Repaired-or-rejected: whatever is under B's key must satisfy the\n        // invariant (never the poison) — and a LIVE peer proves the repaired\n        // route carried a real QUIC connect, not just a rewritten file.\n        s.get(&b_hex)\n            .map(|v| spt_store::peeraddrs::addr_peer_id(v) == Some(b_hex.as_str()))\n            .unwrap_or(false)\n            && s.valid_route(&b_hex).is_some()\n            && PumpHealth::load_from(&health_path).live_peers > 0\n    });\n    stop.store(true, Ordering::Relaxed);\n    dstop.store(true, Ordering::Relaxed);\n}\n\n// [int->REQ-PUMP-STAGE-TRUTH] health tells the truth across a REAL outage and\n// restore: with no route to any peer the health file goes DEGRADED naming the\n// stage — WHILE the pump heartbeat stays fresh (the exact incident\n// fingerprint: green liveness, dead reachability); when discovery restores a\n// real route and a real connect lands, health flips healthy — on progress,\n// never on time.\n#[test]\nfn health_degrades_during_all_peer_failure_and_recovers_on_real_progress() {\n    init_home();\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\n    let dir = TempDir::new().unwrap();\n    let (a_name, b_name) = (unique_name(), unique_name());\n    let _a_broker = net_broker(&a_name, dir.path());\n    let _b_broker = net_broker(&b_name, dir.path());\n\n    let mut a_probe = connect_retry(&a_name);\n    let mut b_probe = connect_retry(&b_name);\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\n\n    let a_root = dir.path().join(\"a\");\n    // Roster knows B as a member but holds NO address for it — combined with\n    // an empty cache and dark discovery, B is unroutable (the outage).\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, None);\n\n    let dstop = Arc::new(AtomicBool::new(false));\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\n    // Switchable discovery: dark during the outage, restored later.\n    let discovered: Arc<Mutex<Option<serde_json::Value>>> = Arc::new(Mutex::new(None));\n    let resolver: PeerResolver = {\n        let discovered = Arc::clone(&discovered);\n        Box::new(move |_| discovered.lock().unwrap().clone())\n    };\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\n    let heartbeat_path = paths.heartbeat.clone();\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, resolver);\n\n    // Degraded, naming the stage — while the heartbeat is FRESH (the\n    // fingerprint that made the field incident invisible).\n    converge(\"health degrades during the outage\", || {\n        let h = PumpHealth::load_from(&health_path);\n        matches!(\n            h.verdict(),\n            spt_daemon::pump::health::HealthVerdict::Degraded { .. }\n        ) && h.failing.get(&b_hex).map(String::as_str) == Some(\"address-resolution\")\n    });\n    let hb: u64 = std::fs::read_to_string(&heartbeat_path)\n        .expect(\"heartbeat exists\")\n        .trim()\n        .parse()\n        .expect(\"heartbeat parses\");\n    let now = std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap()\n        .as_millis() as u64;\n    assert!(\n        now.saturating_sub(hb) < 60_000,\n        \"the heartbeat stays FRESH while reachability is degraded — health answers a \\\n         different question than liveness (hb age {}ms)\",\n        now.saturating_sub(hb)\n    );\n\n    // Restore: discovery finds B. Health goes healthy on the REAL connect.\n    *discovered.lock().unwrap() = Some(b_addr);\n    converge(\"health recovers on real progress\", || {\n        let h = PumpHealth::load_from(&health_path);\n        h.verdict() == spt_daemon::pump::health::HealthVerdict::Healthy\n    });\n    stop.store(true, Ordering::Relaxed);\n    dstop.store(true, Ordering::Relaxed);\n}\n"}],"details":{"displayContent":{"text":"fn pump_node(root: &std::path::Path, subnet: &str, peer_hex: &str, peer_addr: Option<serde_json::Value>) -> PumpPaths {\n…\n    roster.merge_entry(spt_store::roster::RosterEntry {\n…\n        last_seen: String::new(),\r\n        lease_epoch: 1,\r\n    });\r\n    roster.save_to(&root.join(\"roster.json\")).expect(\"save roster\");\r\n    paths\r\n}\r\n\r\n/// Spawn the production pump loop for the node at `paths` with `resolver` as\r\n/// its id-only discovery leg and fast cadences. Returns the stop flag.\r\nfn spawn_pump(\r\n    broker: &str,\r\n    self_hex: &str,\r\n    root: &std::path::Path,\r\n    paths: PumpPaths,\r\n    resolver: PeerResolver,\r\n) -> Arc<AtomicBool> {\r\n    let stop = Arc::new(AtomicBool::new(false));\r\n    let registry = Arc::new(RegistryHost::new_at(\r\n        self_hex,\r\n        EpochSource::load_from(&root.join(\"epoch.json\")),\r\n        root.join(\"registry\"),\r\n    ));\r\n    let name = broker.to_string();\r\n    let flag = Arc::clone(&stop);\r\n    thread::spawn(move || {\r\n        let config = PumpConfig {\r\n            cadence: PumpCadence {\r\n                registry: Duration::from_millis(100),\r\n                notif: Duration::from_millis(100),\r\n                sync: Duration::from_millis(100),\r\n                update: Duration::from_millis(100),\r\n                registry_evict_after: Duration::from_secs(300),\r\n            },\r\n            full_auto_update: false,\r\n        };\r\n        let out = run_peer_pump(\r\n            &name,\r\n            registry,\r\n            &paths,\r\n            resolver,\r\n            config,\r\n            PumpHooks::default(),\r\n            &flag,\r\n        );\r\n        eprintln!(\"TEST_PUMP_EXIT: {out:?}\");\r\n    });\r\n    stop\r\n}\r\n\r\n// [int->REQ-HAZARD-MESH-BOOTSTRAP-TRAP] [int->REQ-PEER-ROUTE-CHAIN] THE trap\r\n// regression, exactly the RCA's shape: a valid roster address + a stale cache\r\n// row, ONE real dial failure (the cached address reaches a live socket owned\r\n// by the WRONG identity — the dial fails like any transient), and id-only\r\n// discovery DISABLED. Pre-fix: the failure deleted the cache row, the\r\n// resolver never read the roster, and with discovery dark the node was\r\n// stranded forever (this test times out). Post-fix: the row is DEMOTED (never\r\n// deleted — sampled throughout), the next round resolves the ROSTER address,\r\n// and the mesh converges with ZERO state surgery.\r\n#[test]\r\nfn roster_route_survives_a_transient_dial_failure_with_discovery_disabled() {\r\n    init_home();\r\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\r\n    let dir = TempDir::new().unwrap();\r\n    let (a_name, b_name) = (unique_name(), unique_name());\r\n    // A's dials fail fast (1.5s) — the everyday transient must not pin the\r\n    // test on the production 10s bound.\r\n    let _a_broker =\r\n        net_broker_with_dial_timeout(&a_name, dir.path(), Some(Duration::from_millis(1500)));\r\n    let _b_broker = net_broker(&b_name, dir.path());\r\n    // The stale address: a socket that WAS a real endpoint and is now dead\r\n    // (host dropped, port closed) — the field shape (machine moved/rebooted).\r\n    // NOT a live wrong-identity host: iroh accumulates dial candidates per\r\n    // endpoint id, so a live decoy's socket would poison later dials for B's\r\n    // id — a distinct hazard class, out of this wave's scope (seeded).\r\n    let dead_addr = {\r\n        let ghost = NetHost::start(hermetic()).expect(\"ghost host\");\r\n        let a = ghost.addr();\r\n        drop(ghost);\r\n        a\r\n    };\r\n\r\n    let mut a_probe = connect_retry(&a_name);\r\n    let mut b_probe = connect_retry(&b_name);\r\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\r\n\r\n    let a_root = dir.path().join(\"a\");\r\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, Some(b_addr.clone()));\r\n    let dstop = Arc::new(AtomicBool::new(false));\r\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\r\n\r\n    // The stale cache row: B's key → the dead socket claiming B's id (a\r\n    // last-known address whose machine moved — the everyday transient).\r\n    let mut stale = serde_json::to_value(&dead_addr).expect(\"dead addr json\");\r\n    stale[\"id\"] = serde_json::Value::String(b_hex.clone());\r\n    let mut pa = PeerAddrStore::default();\r\n    assert!(pa.put(&b_hex, stale.clone()), \"stale-but-valid row seeds\");\r\n    pa.save_to(&paths.peer_addrs).unwrap();\r\n\r\n    // Id-only discovery DISABLED — the RCA's stalled-discovery arm.\r\n    let peer_addrs = paths.peer_addrs.clone();\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, Box::new(|_| None));\r\n\r\n    // Convergence = a REAL connect to B: health reports a live peer (only a\r\n    // `PRESENCE_CONNECTED` moves it), and the route under B's key is no\r\n    // longer the stale decoy value (the observed write-back or the roster\r\n    // reconcile superseded it — either way, the trap did not hold). While\r\n    // polling, ALSO assert retention: the row must exist at EVERY sample — a\r\n    // reintroduced drop-on-fail leaves a visible hole (and then hangs this\r\n    // test forever, since discovery is dark).\r\n    converge(\"roster route converged after the transient\", || {\r\n        let s = PeerAddrStore::load_from(&peer_addrs);\r\n        assert!(\r\n            s.get(&b_hex).is_some(),\r\n            \"the cached route must NEVER disappear (demote, not delete)\"\r\n        );\r\n        let healed = matches!(s.valid_route(&b_hex), Some(v) if *v != stale);\r\n        let live = PumpHealth::load_from(&health_path).live_peers > 0;\r\n        healed && live\r\n    });\r\n    stop.store(true, Ordering::Relaxed);\r\n    dstop.store(true, Ordering::Relaxed);\r\n}\r\n\r\n// [int->REQ-PEERADDR-INVARIANT] [int->REQ-PEER-ROUTE-CHAIN] the incident's\r\n// poison-row class on the production path: the cache holds outer-key B → an\r\n// address claiming a DIFFERENT identity (the `5ff…`-outer shape found live on\r\n// both incident nodes). First post-upgrade pump start repairs it from the\r\n// roster (never bare-deletes the file), and the repaired route converges —\r\n// the poison value is never dialed as B (it cannot be: the chain validates\r\n// id-match on every leg).\r\n#[test]\r\nfn poison_cache_row_is_repaired_from_the_roster_at_startup() {\r\n    init_home();\r\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\r\n    let dir = TempDir::new().unwrap();\r\n    let (a_name, b_name) = (unique_name(), unique_name());\r\n    let _a_broker = net_broker(&a_name, dir.path());\r\n    let _b_broker = net_broker(&b_name, dir.path());\r\n\r\n    let mut a_probe = connect_retry(&a_name);\r\n    let mut b_probe = connect_retry(&b_name);\r\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\r\n\r\n    let a_root = dir.path().join(\"a\");\r\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, Some(b_addr.clone()));\r\n\r\n    let dstop = Arc::new(AtomicBool::new(false));\r\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\r\n    // The poison row, seeded the way history did it (pre-validation writers):\r\n    // outer key B, nested id = somebody else entirely.\r\n    let mut poison_store = PeerAddrStore::default();\r\n    poison_store.addrs.insert(\r\n        b_hex.clone(),\r\n        serde_json::json!({\"id\": \"0\".repeat(64), \"addrs\": [\"127.0.0.1:9\"]}),\r\n    );\r\n    poison_store.save_to(&paths.peer_addrs).unwrap();\r\n\r\n    let peer_addrs = paths.peer_addrs.clone();\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, Box::new(|_| None));\r\n\r\n    converge(\"poison row repaired + route converged\", || {\r\n        let s = PeerAddrStore::load_from(&peer_addrs);\r\n        // Repaired-or-rejected: whatever is under B's key must satisfy the\r\n        // invariant (never the poison) — and a LIVE peer proves the repaired\r\n        // route carried a real QUIC connect, not just a rewritten file.\r\n        s.get(&b_hex)\r\n            .map(|v| spt_store::peeraddrs::addr_peer_id(v) == Some(b_hex.as_str()))\r\n            .unwrap_or(false)\r\n            && s.valid_route(&b_hex).is_some()\r\n            && PumpHealth::load_from(&health_path).live_peers > 0\r\n    });\r\n    stop.store(true, Ordering::Relaxed);\r\n    dstop.store(true, Ordering::Relaxed);\r\n}\r\n\r\n// [int->REQ-PUMP-STAGE-TRUTH] health tells the truth across a REAL outage and\r\n// restore: with no route to any peer the health file goes DEGRADED naming the\r\n// stage — WHILE the pump heartbeat stays fresh (the exact incident\r\n// fingerprint: green liveness, dead reachability); when discovery restores a\r\n// real route and a real connect lands, health flips healthy — on progress,\r\n// never on time.\r\n#[test]\r\nfn health_degrades_during_all_peer_failure_and_recovers_on_real_progress() {\r\n    init_home();\r\n    let _g = LOCK.lock().unwrap_or_else(|p| p.into_inner());\r\n    let dir = TempDir::new().unwrap();\r\n    let (a_name, b_name) = (unique_name(), unique_name());\r\n    let _a_broker = net_broker(&a_name, dir.path());\r\n    let _b_broker = net_broker(&b_name, dir.path());\r\n\r\n    let mut a_probe = connect_retry(&a_name);\r\n    let mut b_probe = connect_retry(&b_name);\r\n    let a_hex = a_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_hex = b_probe.net_status().expect(\"status\").node_id_hex.expect(\"net\");\r\n    let b_addr = b_probe.net_status().expect(\"status\").addr;\r\n\r\n    let a_root = dir.path().join(\"a\");\r\n    // Roster knows B as a member but holds NO address for it — combined with\r\n    // an empty cache and dark discovery, B is unroutable (the outage).\r\n    let paths = pump_node(&a_root, \"meshrec\", &b_hex, None);\r\n\r\n    let dstop = Arc::new(AtomicBool::new(false));\r\n    spawn_dispatcher(&b_name, &b_hex, dir.path(), &dstop);\r\n    // Switchable discovery: dark during the outage, restored later.\r\n    let discovered: Arc<Mutex<Option<serde_json::Value>>> = Arc::new(Mutex::new(None));\r\n    let resolver: PeerResolver = {\r\n        let discovered = Arc::clone(&discovered);\r\n        Box::new(move |_| discovered.lock().unwrap().clone())\r\n    };\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let heartbeat_path = paths.heartbeat.clone();\r\n    let stop = spawn_pump(&a_name, &a_hex, &a_root, paths, resolver);\r\n\r\n    // Degraded, naming the stage — while the heartbeat is FRESH (the\r\n    // fingerprint that made the field incident invisible).\r\n    converge(\"health degrades during the outage\", || {\r\n        let h = PumpHealth::load_from(&health_path);\r\n        matches!(\r\n            h.verdict(),\r\n            spt_daemon::pump::health::HealthVerdict::Degraded { .. }\r\n        ) && h.failing.get(&b_hex).map(String::as_str) == Some(\"address-resolution\")\r\n    });\r\n    let hb: u64 = std::fs::read_to_string(&heartbeat_path)\r\n        .expect(\"heartbeat exists\")\r\n        .trim()\r\n        .parse()\r\n        .expect(\"heartbeat parses\");\r\n    let now = std::time::SystemTime::now()\r\n        .duration_since(std::time::UNIX_EPOCH)\r\n        .unwrap()\r\n        .as_millis() as u64;\r\n    assert!(\r\n        now.saturating_sub(hb) < 60_000,\r\n        \"the heartbeat stays FRESH while reachability is degraded — health answers a \\\r\n         different question than liveness (hb age {}ms)\",\r\n        now.saturating_sub(hb)\r\n    );\r\n\r\n    // Restore: discovery finds B. Health goes healthy on the REAL connect.\r\n    *discovered.lock().unwrap() = Some(b_addr);\r\n    converge(\"health recovers on real progress\", || {\r\n        let h = PumpHealth::load_from(&health_path);\r\n        h.verdict() == spt_daemon::pump::health::HealthVerdict::Healthy\r\n    });\r\n    stop.store(true, Ordering::Relaxed);\r\n    dstop.store(true, Ordering::Relaxed);\r\n}\r\n","startLine":131,"lineNumbers":[131,null,153,null,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\mesh_recovery.rs"}}},"isError":false,"timestamp":1784272032951}}
{"type":"custom_message","customType":"irc:incoming","content":"<irc>\nIncoming IRC message from agent `BrokerCpuRca`:\n\n~532 is best explained by leaked `SubscriberSeat` writer threads on long-lived brain conn 137: one `std::thread::spawn` per subscribe (`nethost.rs:189-208`), seat retained in each `StreamLog`; `retire_stream` only sets retired+clears ring, not subscriber/cancel (`nethost.rs:1810-1834`). Logs show conn=137 accumulating `stream-subscriber stream=...` tags for every Registry wave. Not conn-watchdogs (1/physical conn, fresh short-lived conns close) nor PTYs (~2/session).\n\nAn agent sent this while you were waiting or working. Any active interruptible wait was stopped early so you can read it now.\n\nIf a response is expected, reply with the `irc` tool (`op: \"send\"`, `to: \"BrokerCpuRca\"`) — you may finish your current step first. Nobody replies on your behalf.\n</irc>","display":true,"details":{"id":"153306f585dc5f4b","from":"BrokerCpuRca","message":"~532 is best explained by leaked `SubscriberSeat` writer threads on long-lived brain conn 137: one `std::thread::spawn` per subscribe (`nethost.rs:189-208`), seat retained in each `StreamLog`; `retire_stream` only sets retired+clears ring, not subscriber/cancel (`nethost.rs:1810-1834`). Logs show conn=137 accumulating `stream-subscriber stream=...` tags for every Registry wave. Not conn-watchdogs (1/physical conn, fresh short-lived conns close) nor PTYs (~2/session)."},"attribution":"agent","id":"8aa946db","parentId":"7e036e09","timestamp":"2026-07-17T07:07:12.973Z"}
{"type":"custom_message","customType":"irc:incoming","content":"<irc>\nIncoming IRC message from agent `BrainCpuRca`:\n\nSource says 10Hz `brain.net_streams()` serializes the full in-memory table; locally initiated rows are skipped only after transfer (`dispatch.rs:458-465`), so unbounded Registry rows plausibly explain broker pipe writes/CPU, not brain file reads. The 63.91 MiB/s brain `ReadFile` rate matches exactly 2 × deployed exe size via `write_ready`→`current_exe_hash` every 500ms; registry `sessions.log` scans occur only on 30s/forced advertise rounds and ledgers are capped 64 rows.\n\nAn agent sent this while you were waiting or working. Any active interruptible wait was stopped early so you can read it now.\n\nIf a response is expected, reply with the `irc` tool (`op: \"send\"`, `to: \"BrainCpuRca\"`) — you may finish your current step first. Nobody replies on your behalf.\n</irc>","display":true,"details":{"id":"153306f5be5c5f4c","from":"BrainCpuRca","message":"Source says 10Hz `brain.net_streams()` serializes the full in-memory table; locally initiated rows are skipped only after transfer (`dispatch.rs:458-465`), so unbounded Registry rows plausibly explain broker pipe writes/CPU, not brain file reads. The 63.91 MiB/s brain `ReadFile` rate matches exactly 2 × deployed exe size via `write_ready`→`current_exe_hash` every 500ms; registry `sessions.log` scans occur only on 30s/forced advertise rounds and ledgers are capped 64 rows."},"attribution":"agent","id":"af69b338","parentId":"8aa946db","timestamp":"2026-07-17T07:07:12.973Z"}
{"type":"message","id":"50e56fab","parentId":"af69b338","timestamp":"2026-07-17T07:07:27.660Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Identifying thread leak in StreamLog subscriber handling**\n\n**Analyzing connection reuse in stream subscription buildup**"},{"type":"toolCall","id":"call_LHi3OZ1Wd0QPEmeL8TBCorUJ|fc_05867525d31cbc53016a59d4ac5854819494a11f7ca1861b50","name":"irc","arguments":{"i":"Clarifying seat retention proof","op":"send","to":"BrokerCpuRca","message":"How can one long-lived conn 137 subscribe every Registry wave? `worker()` creates a fresh Brain per stream, then handler EOF cleanup detaches `my_stream_subs` when worker Brain drops. Is conn=137 perhaps a stale-generation process connection that never EOFs? Need exact retention path distinguishing temporary 15s writer vs permanent seat.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8140,"output":535,"cacheRead":103936,"cacheWrite":0,"totalTokens":112611,"reasoningTokens":407,"cost":{"input":0.0407,"output":0.016050000000000002,"cacheRead":0.051968,"cacheWrite":0,"total":0.10871800000000001}},"stopReason":"toolUse","timestamp":1784272032989,"responseId":"resp_05867525d31cbc53016a59d4a16d088194a79285f49a672518","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_05867525d31cbc53016a59d4a3afc881949297c01bb3815fc5","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdSsPRCW12m7lDrQ3yry3xt43El6KFRO9rVl1w2PfmP9472Qo2I9lmMKKsybUQZGmdZm5Cp2SxpMJ4Fc5darUvuHmP8eGKtvw6v2KEl_8pbpNEEtQMNg4USLKvhspN9_LqA5CX48-5JJR0Kc-rDZuAXUpsSuS8CYvZ528TM1OMVnRlsZK3VGffLwojoshWXsB4fWcWaKIdGF7JPTGeScY3MZse3paDWXAAl1wQiQsBXeNNLNN179ZA4mgRKbkr4hMKM4k6xsA3ksvkqQlNRxko0pyR4V8zzZk0eR3uiJANCb1sphwSJ9Z_a7OyMypFVQ9oZVy17I0V-_KGU-YpS6f7cUXIfECm6A4WVeBP_AFvzU6gqfUHErX0EljdbLFfyN6mCE9OrjNiek1QPhg6GqiPOWOsOCnWe73owPhQ3bdL3dSl8G6GIZVV1eQRar-lDjiiPBQSSarjq-oeDj7uOPEERAIJfDXrXoAtLldyE_bGKJTt8LgaqIpwANpl7d83dqtTU5PDVDrb-Bx_s1e2Nv8N7EWbyfE95Yb-U7S6AVZVFzoWyCJaRIX5v9PELsgIqzbSr-kCJGi81SnVMaiaXDyGWLzQp0_aYtenYnk4814Ley0FETW6CSjgQ6e-FEV4d0MHjoZVNltlg8XaVf1ebp6JoVdu_oOpIjeOKkR5gxQh01auuIVo4AJYgo4LtjXebOwG2Q2z_UHPY0u-3t_gyAGnJmblCvEMHDfc2gB-yRqbk8cX4HEo-J_SuSqyg-4YRvZprAJzh5LwDx6RWq8gKGgLZW42yR7F-DhppMhdL2mPivHDAnuitD-YD5pCEQwRBZSDuzq2C3P-sPpfvVrs3bAYQjNs5BG59RNzqmqrype5WC9Ij_hXfCcoe9SI5NkhQjUW_BlImPIdfcaCEq8XCRpl6hhXV8qivfnjFeigZXFHU_LRuJx301zezJp9pTOn-GhTDjfmDEuok4juhcLcwt7SO1KEf3Zu3oaR3kulaw6CDX5ahLnhyN64gKFj6iNcTgcH_YAGobmUuYQfrubZn4TkoDZ7RTOvfXiOE8_9Wa5NZQUBj6MG3jWNsypGXtyIAUgNGN9_yR3o54XvydVngfbVUpJ6O27Bv9JaxLLsbcl6hiy7mvd8nS0KyW69qhegqcX1dMplksWuHS-cuHig9ZO1iJhD990Jmd7JNnpeMJeOCGp8df1A4-r4CJpoyB10d4JbYrlbEFihb0w5cv1ZsU_Vn6A3EgmDetRhqv583GfBksRUMkuqQ3kkhH2k4qSiyuS33M9ve2wCbGzd-DxFgTDs5dohLHUS7O_IQjVY_oMJhb-3-BoVfpBXsZ0hc2ohdd2Ko0OXF89Ai_t-ChIEEU3f2NEQkzopIY6O7btvH15kAwwk9HOHARlBHSWuxdH-Jky4ElYOKUyluZAbnnH9erE8Ix927f2dOiyS_tevKL58ZTPSJjHsKOpYJaCiqr7jfyxQydxlwlJZSohV04BAlDs6RYYt9BhBPasGxlGAiMVR4waDvYdidRcvTit3MB9U0968yAUSnIEC2G1da2j7P1GkocY5Z2eQd3vurEEMEAInVe4wbYTYeVDQIBwq2TTNjmEj92DQa4SR2Ljp04Xi3hoeUn_uphtyZXLjhIsSlhccDMgZVWvkFxl_Clni9BsN6RfhLkJRoY6KUuFouWhrv4J6JPEvqyBHFptvl9cAvsUjzsFxKH5GTDoIo0osQ57kAGlMh-guQh-hERF54rxQFDMxhrSVMHhAShCT3pSFn2Rpd6IP_T9e9OEAESK1OXeac5P4izTxxax58aKTrFK11mEd3xPBUEH5VLyzkWgyIMASklmS4rt92IDvDKia_n_uIJNjZ4TBIrySn1zqn0ec3dsoBuInDbctd4Hrjyq9Jj0m2rKiFOHyr6-e-WluEYrGhT_KEFwgQXbGR2FRuT8PQ3hys4J3ybQIAwU2mxcFXvBd8zfoJs8iCDlbQwbLO-UT72OMqlJg2yfsHIdCTrCODxqtqYROSOdkf06V4PUYw8gdURlI0GJDD7ahjOI3XaT-vUA7rPsMJraXydSz_uvmYbQZWAqFQTeJNm8YSuHJmbk6m2olFEay4pZtEbf9z7iWcgcdyYd0raSGIUyN_c4pVZ8HP6tXGP2HrEiwpB0PjIDBFvrUpgJGxizRC7ROc2zdHyzQ9KzX09mRPy_vwCv7ZJMLBhh9YhvuZkL0S3YJrzUVcj4SmtzabvtobehDtYsWmqJc8hObZIcEe_tghQVzKJ-wUlvagg9Y5QlDNw52oJoAyvKnfLH1qV_Wn_5OeB9TKMQ1UbPBxxYsO4mWI9m1TJShWwyqrDTQTO7oDl3r0AWs7cvvnfwdcaqzwgyRlPUS8iqw82CgwfpC6oVnDCwhH5ABhrHdvqSFrvgx5bvVxQJEfZUJk3iVoiw1uciSTflfwRLR3PdFI6-7EjKLxq0UINFWk9LO7gFSbFN21dyQt_R0kXfh1NCI8kqLqRlabNunUu_meYxscVF_72PYcXrAROu1ko2LbK67_NJr_9ZareAVQTCN6Gk2Xb-aMwMvbtfAVAj6tLDUNRVGLMiGZCU_DndX9BA395oodsq-m1kSMKFfoNtEN0ykU2PTrs74me0oihKkicA4nxdkYoUi4gr9UDT_VRAmh0o7fhq16BrrzSVMlLvRYog-Cq-J0utsShaPMzEYKRgZ3D-_LC1WEHlFU-mD2dKAssbOXnXPWNxvenWKbjmhkDTGFd02zD-HcRXdUDCaAZst9DmzPU666Mu8Fc3gTN5P7OyWpAaRyjXKN0fCZJIFpm0z6EF3V3Lcyb07UIYYQ3nTiGoON0iDHl3Oes3KkVw3BooYdHJXatyV7eaH5uyi18m0AqhAuKw8rGG72Pham1HhErAwosKkvhwvoCMm4TcbZEF4xTVt1A27RJaAjmf6w3g-W2kUOFRV5UuaLxyEc-kuLq4L6GG4LQ0s_Asp4qpbOG1hUJmG74l-QmA2lUi8le4Ls3HEMRsDpc15Ybu78hwoaru5wOSKY4i5dC-QYd6R9fvnIEDnNAhXUlaRftVE8T3ryqtKeQr0yoQLWY0yqghH4NUp5tyS4wtT0Qw_RYX43gINUnQqBSFUVgus11JWmKNVQErYYg-WxIujHaIZplrp5H4dzqWrVl9w1CUCLUHNcVBdux4H1KB7pWHI7rardEb-aPnUVYeokqsZTIpDZ0s7Z5pWxk2M4_1H-tihRrnvFyxroivc-EbVTKAPDq5_LyDogQkSKae6qhN8WCyretmS8a-4ChtFwheMzhdJbLpR17C4zWUsJJzEqupbqcF_h2PKfJsMhUZxByQiCo7tIv09JMDHDnoNFvRzZ-oKeqOFa-Pw0PaaPZEdw6-MVrafH6e0YNxXZwGor3tYCLliiF-bCgKEsITolGrypRViCOWa6DPLrQ-h5GOEllc4jnoFt7s87_gV5bKR7rmANMe54cjDrVM42lojyFuzN2EfwsEatAcLg2BOQwldpd-YnvTewB-q0_3KAkxRkmZ-d4Wermrn7ZwEDMe3Pg3kOHgfmeLvbMi6Led2inrP38637Hi49mpYm7FAIIzdSojVUYmcR81Yq48kj97wrBCeeFORbsTCm4IRejquATBuNrvDGOEak=","summary":[{"type":"summary_text","text":"**Identifying thread leak in StreamLog subscriber handling**"},{"type":"summary_text","text":"**Analyzing connection reuse in stream subscription buildup**"}]},{"id":"fc_05867525d31cbc53016a59d4ac5854819494a11f7ca1861b50","type":"function_call","status":"completed","arguments":"{\"i\":\"Clarifying seat retention proof\",\"op\":\"send\",\"to\":\"BrokerCpuRca\",\"message\":\"How can one long-lived conn 137 subscribe every Registry wave? `worker()` creates a fresh Brain per stream, then handler EOF cleanup detaches `my_stream_subs` when worker Brain drops. Is conn=137 perhaps a stale-generation process connection that never EOFs? Need exact retention path distinguishing temporary 15s writer vs permanent seat.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_LHi3OZ1Wd0QPEmeL8TBCorUJ","name":"irc"}]},"duration":14669.346499994397,"ttft":2702.6771999895573,"contextSnapshot":{"promptTokens":112076,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LHi3OZ1Wd0QPEmeL8TBCorUJ|fc_05867525d31cbc53016a59d4ac5854819494a11f7ca1861b50","toolName":"irc","startedAt":"2026-07-17T07:07:27.660Z","intent":"Clarifying seat retention proof"},"id":"be0caf76","parentId":"50e56fab","timestamp":"2026-07-17T07:07:27.660Z"}
{"type":"message","id":"8d456604","parentId":"be0caf76","timestamp":"2026-07-17T07:07:27.660Z","message":{"role":"toolResult","toolCallId":"call_LHi3OZ1Wd0QPEmeL8TBCorUJ|fc_05867525d31cbc53016a59d4ac5854819494a11f7ca1861b50","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- BrokerCpuRca: injected"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"BrokerCpuRca","receipts":[{"to":"BrokerCpuRca","outcome":"injected"}]},"isError":false,"timestamp":1784272047660}}
{"type":"message","id":"1a148662","parentId":"8d456604","timestamp":"2026-07-17T07:07:45.253Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing writer thread leak cause**\n\n**Investigating possible outdated broker version**"},{"type":"toolCall","id":"call_4jLCUmAOKl79Ldh17Ojf40Dh|fc_06e10c77aaab3d16016a59d4bdb2ac81938e21d9690c5499c7","name":"grep","arguments":{"i":"Locating ready hash loop","pattern":"write_ready|current_exe_hash|from_millis\\(500\\)|500","path":"crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_yoczDJvKt3RWn4fbvoxPKSm9|fc_06e10c77aaab3d16016a59d4bdb2c081939bc5e8edbcebb38b","name":"grep","arguments":{"i":"Locating executable hash implementation","pattern":"fn current_exe_hash|current_exe_hash","path":"crates","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_mk3R9Gct7sUzSjS0Ae93pw5U|fc_06e10c77aaab3d16016a59d4bdb2d08193afdaebbc61f2ad39","name":"grep","arguments":{"i":"Locating session ledger cap","pattern":"64|MAX_.*SESSION|truncate|read_all","path":"crates/spt-store/src/sessions.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4604,"output":619,"cacheRead":108032,"cacheWrite":0,"totalTokens":113255,"reasoningTokens":418,"cost":{"input":0.023020000000000002,"output":0.01857,"cacheRead":0.054015999999999995,"cacheWrite":0,"total":0.095606}},"stopReason":"toolUse","timestamp":1784272047710,"responseId":"resp_06e10c77aaab3d16016a59d4b00ec88193bbb53670245e6669","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_06e10c77aaab3d16016a59d4b228708193bc447c9e4a38b22e","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdS9sqK7t3CtkqTPuRxH-aCBZtGxVneRQeJ11RodKZ7yvEgEjes5zgJ471EFQFyfwK4o-Zy2g29TyZZCB8rmyERU2_3dBvPy13FGwb23jj0AzCF2UtWlEVLrM9We-pn6sOc-PFBEXr8x00CdtBTMZn-RpfBB8wTKTqSdRDYjQaiQsAhQF2Gz32FMfF_B88p-rHY3QtW_SO93waoIgrVrFvSZUbAaLfTg-r_Uk_Wy0vGVSZ3EIQI9HJ_lySZhE5T-TUjMxvX_wiQi2wDXMTbuEnAaK-miibLFgJf9v9DudN1v15uWXhCNSG5899lfx_nqAu3TRSmtqoO4XZF1XdcTlRDRCqkjCreXVza080mMCaIcwJRWezefuZ7JXvDdJv0ideVOj7FY2LmPFGpltwg74S45SwblIcn1yNFc2MuGRYZBpPPbXjFtNHr7xHbes3otJG_W5Y2vA2sKXKL71NaS3LEluS9BqMsX7L6HzCabBf-CVWRHGb4nslw9z-ZHCCFBeDrwYjBGhsfG0zDGJkS1N2rNLk_WY4vT8W8GKHTDYtyE2HfN4cwu_L0ZKAjUxgB_6ecT1RtmhqZt5zGg4CCfocuo2JbciRPq7sVa9lvmD_CjBZlEmDo7X5opx4FFkh9WZZRxjEP0xcX0xiG0haCz50rOkcnupoklZe02r94kaYZxT88VGPUC3_2lINJ9J04pMyQei-5-fuw2oLHIT3jGYcI0tMpWDWL6OTPLKWPBAXzt081tpH4OuWwJc-ukuyEDPP_1NlL3bVcEkgrtn6TmSzo5-AD883HLpr2I7vsubyMGHDvPqORWbZfV4dfxxy3dnOQFNxrhrWoTmPOBtP_QC3CJn9d_LObt3Ut5qkSikz-92dv9ogFFf9MvtCUEd125eihwiTLphXzf6aCD9PIp0KtFVqxHbRqR0eLlRRRP8bQk2grcSZ944sEn8vk79o0RkWuOkizkLoztjvkWKGRUx2f4DYDxiO7nS9VVwYJWa0MAB4YSemDYCJmWUV1AzutzUKkk2SxuFx0nu80NJPqhfneAbUZj9HsZSmfgepNb-ahcVCoUDzft7gfNunKkxNDgwekaI4tB6c5hSNjfS_zJmEMO7tuPkJXp5ORHYCUqackoQqC0fQSmUn2usDX6ePENW0t4JZrvIEwfqg7WNf435_idHtEuxbPshZX0UUyniTU29D3cFKe60xpnUCM-_XmVRwoVo9nVwzEDVfiyhOVyV3gmHj4T9l8KKhipkNmR31YYig3o8N-RMVvKDfMYNsLDOac9k4KUFzV8bMahtVliVGHLWL8tuFLkDP78NozEg0SED5EPAZWv2lzel1JOAalK6Hs99r-tMTTbxqCNNtHw5ukTzn-aQCEKuKHLI05z59GEiJK9HpAeFTOmx9MD1PhD0Yf-EeQym-47OF05fIZtbKNW04MbK4OWzNl9USw5CtSKZyIEwn7qSr2gTOrOEsVTxLVcKJHVhj3gaXwtuGBoLDSbpg_ZSZWnh5CHE3BLJqn9Bnsu_C8OkPK2kEOd8l4FAkREUP-OvS-CmWl81XB5jj8t6ypdAWhGjltAABuJijyroCC3bfpUoW28YE8ceUTV-b0BGeNMEy93c8liGrJZrPZfvOAiGG3saeS-eTqdBDAokA6wtwWI64HpNdaXgOMhBah_0LCdaIqQnS91qzxaBzw7KJeoReTaGwrnsDqGDkhyKvnv6UR1mMgIvcBEBiRiz7otjBPgI_-UIoYo0qUId_6CdRs8JTHPC5WNyr_bS4BVjfGu2WJhvEian3vG_aeUqmFAETCUc-GI5Mem8p3GTdxkDFHQbEdkkITlWJYX5b1Ke6dm7yjZfohZYRvFXVYkVJGGykxh6w6J6maZ6B773IMsuqpyi2Il25NgESZxgFEBVREnPMpzOI8OXwAeY6VKQ27U2mz8v6vSWmX8zdREctbyMjSVJnporHqXY7AsLmZIQYIAZVr8qalu2FA9IrXRxXAdhlfv-UPi4VMn1sbFB4_UJliQ6uB-Lb4XVDqOirwt0Wn2zt0RY_R4K-o8i-EZSdd-d0HkYfeg9vqOyK2-E4EJGqTCjEPRjDoaXjFu45LXVganMkvhCRBYWYeuCHuScudsQRZovq1qdWKEZcbWcqwILmq7dHXQ1x6SOkiIjh7N62iXdkliUKAer5wSsFm-JxQjykCCGGw-rFXDrgyy-CGPJ4Pk1EQ4qEsPs-4MjlKlPmzJ79Ss5sFuWg2aDRKs3O_S0LMkdvlgMppOYZD95mVIfOWWBZJqv42J38YTN97K-0NArrKw7DTxOjv-2lPmRRUg92_AXBi8uHTbiUOwWgjyu6jm3Q5vhoo-eFl1_V98o5syoQKhwFuTJBpPiNMhXOFlKal71Cc9u-58ZIJRk0AxrpBQVYre_0UDgdW0nuoLJlb_a84E3WBkVrdhdGMA-DqB8GQCji4BWarSktBkOB6yxzUOrOj7HaWxByLY8twf6eNJvpnyjD2P4C_TJqOT1jcI_pJElY9Rl-kCFJ7rZK-Ue7yDcWyEmqsK_pa973DtsF3ICPQxtl_mdiD2Lf9iv7C2akoGMfng_ptP1GFNxA3b-6lwCEnbNc0qbm71Y33a87Hc8nOC5xu3mzNn3wP8nuAXbXu7EB0gw4Trbivodr99f71ymKjVVPpiqOYO5Dhoqe3DoOJT7xmvKUu5N64cc5uF0g0q2Cf5DdUVNdAIRtjR7XAo3pe5jUuUFU8Dr6rxSDSje2IcysWFR9oY622niB9yngy9YW-heW4ryRVxCnreLXJrBQPcFOw8PsRLGeTdThblDjCQ27QVKkGiym9Nptp4NZk0f1AWCKdCRsaX5662JydUUsNmmJ8IQTsmO2Zc_-6KkuyB3679rzqC9x9gIcNZo0hCuk_cN-dNVxGBJvWTEXJ7JF5omS-MXd4spD9uQlYToN32inoqrj2pI5mhm6JYaFlAyQwWw6JXmkzpUSWlwUkVYEgLqHXpXaKqQm4-jWlnjMBIQgSJ3xLOvnfBGqADNCiIa-wrGTp1mWeIMF2hAD9pbTCX1rA8GWHm0eRSv_iyn65XivP3X10tc7OHQOb3bnElGCJokU-Ajh6As2SIoyMvWE0R4nG-OBlTNV3Bkod6QO7fQ8Ao6erSNJW5G-qKim1A0zD8i23zEfHbgFSxjUjHMdiq-WIi4BRy8yz4tal2R2xKZ53YH1O31-hEKY-lNkw4Or0wueZSTAc-pmtyrgRV6QGstyFED7pMJ6kNCEuoCUmcyMEZXSWg5rrmYjwUmMJxdBFT5WvJbMcGSZzWQvzZ5kduM82l-YeSmwW7RV-aoEi3fCm1gHLh8AbdKdL6DQ55lhT7fCxM6lqe65lySfq5dVZVHdyT71b6o7KEXcJEkrJmMet8Yjqrj1fst3QVT6YfwYEYk2cfMGTI5Vg0jU1Ie3zQje2EemLUeNx9oyc6wTq5dTQKYZJFQOKjZj3zHHgTLSRSvShlRVbxS0Bga6OesXrgUCUjzhM88t_AWULmlZyPYAsRn8IM0pQET92cesRHw_ZrFbWtLsGDKs0I0kYw-UY2gHVVa5_qkuwDl63-HyXV3FYHBZhHbCU_rZ6U","summary":[{"type":"summary_text","text":"**Assessing writer thread leak cause**"},{"type":"summary_text","text":"**Investigating possible outdated broker version**"}]},{"id":"fc_06e10c77aaab3d16016a59d4bdb2ac81938e21d9690c5499c7","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating ready hash loop\",\"pattern\":\"write_ready|current_exe_hash|from_millis\\\\(500\\\\)|500\",\"path\":\"crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_4jLCUmAOKl79Ldh17Ojf40Dh","name":"grep"},{"id":"fc_06e10c77aaab3d16016a59d4bdb2c081939bc5e8edbcebb38b","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating executable hash implementation\",\"pattern\":\"fn current_exe_hash|current_exe_hash\",\"path\":\"crates\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_yoczDJvKt3RWn4fbvoxPKSm9","name":"grep"},{"id":"fc_06e10c77aaab3d16016a59d4bdb2d08193afdaebbc61f2ad39","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating session ledger cap\",\"pattern\":\"64|MAX_.*SESSION|truncate|read_all\",\"path\":\"crates/spt-store/src/sessions.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_mk3R9Gct7sUzSjS0Ae93pw5U","name":"grep"}]},"duration":17542.366200000048,"ttft":2866.0410999953747,"contextSnapshot":{"promptTokens":112636,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4jLCUmAOKl79Ldh17Ojf40Dh|fc_06e10c77aaab3d16016a59d4bdb2ac81938e21d9690c5499c7","toolName":"grep","startedAt":"2026-07-17T07:07:45.254Z","args":{"path":"crates/spt-daemon/src"},"intent":"Locating ready hash loop"},"id":"ff0a91b0","parentId":"1a148662","timestamp":"2026-07-17T07:07:45.254Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yoczDJvKt3RWn4fbvoxPKSm9|fc_06e10c77aaab3d16016a59d4bdb2c081939bc5e8edbcebb38b","toolName":"grep","startedAt":"2026-07-17T07:07:45.254Z","args":{"path":"crates"},"intent":"Locating executable hash implementation"},"id":"870aed38","parentId":"ff0a91b0","timestamp":"2026-07-17T07:07:45.254Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_mk3R9Gct7sUzSjS0Ae93pw5U|fc_06e10c77aaab3d16016a59d4bdb2d08193afdaebbc61f2ad39","toolName":"grep","startedAt":"2026-07-17T07:07:45.254Z","args":{"path":"crates/spt-store/src/sessions.rs"},"intent":"Locating session ledger cap"},"id":"11b10aa5","parentId":"870aed38","timestamp":"2026-07-17T07:07:45.254Z"}
{"type":"message","id":"2f47e3e6","parentId":"11b10aa5","timestamp":"2026-07-17T07:07:45.269Z","message":{"role":"toolResult","toolCallId":"call_4jLCUmAOKl79Ldh17Ojf40Dh|fc_06e10c77aaab3d16016a59d4bdb2ac81938e21d9690c5499c7","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## brainproc.rs\n 62|/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n*63|const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n 64|\n 65|/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n 66|/// broker stamps at spawn time (ADR-0018 V2). One channel, three payloads: the\n...\n 220|    // semantic the live-agent adapter inherits.\n*221|    write_ready(generation);\n 222|\n 223|    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n 224|    // online/offline exclusivity — a reconcile loop that runs each offline\n...\n 250|    // reconcile in the background (ready-without-warm is preserved because\n*251|    // write_ready already fired above), then drain/debounce/coalesce the\n 252|    // invalidation queue. Same posture as the hosts above: the stop flag is\n 253|    // held for symmetry, the thread dies with the brain, and the next brain\n 254|    // rehydrates from the persisted index (warm start, fingerprint-gated).\n...\n 296|        let _ = brain.sessions();\n*297|        write_ready(generation);\n 298|    }\n 299|}\n 300|\n...\n 364|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n*365|fn write_ready(generation: u64) {\n 366|    let mut obj = serde_json::json!({ \"pid\": std::process::id(), \"generation\": generation });\n 367|    // Best-effort: omit `exe_hash` if the self-read fails (additive — a reader\n 368|    // tolerates its absence). Never fail the ready write over a diagnostic field.\n*369|    if let Some(hash) = current_exe_hash() {\n 370|        obj[\"exe_hash\"] = serde_json::Value::String(hash);\n 371|    }\n 372|    let _ = spt_store::atomic::atomic_write_string(&brain_ready_path(), &obj.to_string());\n...\n 380|/// D7-1 test (the resident-bytes diagnostic the enlyzeam regression lacked).\n*381|fn current_exe_hash() -> Option<String> {\n 382|    let exe = std::env::current_exe().ok()?;\n 383|    let bytes = std::fs::read(exe).ok()?;\n 384|    Some(crate::release::sha256_hex(&bytes))\n## broker.rs\n 4473|    ///    then RELEASE it;\n*4474|    /// 2. stop each resident translation child (`terminate()`, bounded 500ms) —\n 4475|    ///    frees the OS file lock on the install-dir binary;\n 4476|    /// 3. CRC-swap `install_dir` from the CLI's `staging_dir` ONCE (W3b\n 4477|    ///    never-strand: a mid-swap failure rolls the install dir back to OLD);\n## config.rs\n 518|        let path = dir.path().join(\"daemon.json\");\n*519|        std::fs::write(&path, r#\"{\"crash_grace_ms\": 500}\"#).unwrap();\n 520|\n 521|        let cfg = DaemonConfig::load_from(&path);\n*522|        assert_eq!(cfg.crash_grace, Duration::from_millis(500));\n 523|        assert_ne!(\n 524|            cfg.crash_grace, DEFAULT_CRASH_GRACE,\n 525|            \"must override the default\"\n...\n 576|        // key) loads as empty — forward/back-compat, never a parse failure.\n*577|        std::fs::write(&path, r#\"{\"pulse_period_ms\": 5000}\"#).unwrap();\n 578|        assert!(\n 579|            DaemonConfig::load_from(&path).startup_endpoints.is_empty(),\n 580|            \"a file without the key back-loads as empty, not an error\"\n## consent.rs\n 130|        seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*131|        seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent live\n 132|                                                               // More recently active than ling, but OFFLINE → excluded.\n 133|        seed_perch(&owlery, \"dunsen\", STATUS_OFFLINE, Some(9_000));\n 134|        // Live but never stamped → ranks as 0, never wins over a stamped peer.\n...\n 138|        assert_eq!(target.id, \"ling\");\n*139|        assert_eq!(target.last_active_ms, 500);\n 140|    }\n 141|\n 142|    // [unit->REQ-UPD-4] no live session ⇒ no target (the gate then defers; it\n## deadline.rs\n 250|        // fired-point step, never behind it.\n*251|        assert_eq!(a.advance_after_fire(1_500, 1_400), 1_600);\n 252|    }\n 253|\n 254|    #[test] // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n...\n 284|        // now < anchor must not underflow-panic; elapsed saturates to 0 → anchor.\n*285|        assert_eq!(a.next_fire(500), 1_000);\n 286|        assert_eq!(a.next_fire(0), 1_000);\n 287|    }\n 288|\n## dispatch.rs\n 190|/// Backoff base for a released claim (doubles per failed attempt).\n*191|const DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n 192|\n 193|/// The backoff before retry number `attempts` (1-based) runs.\n 194|fn retry_backoff(attempts: u32) -> Duration {\n## grants.rs\n 531|            // the prompt must land at the user's session, not the asker's.\n*532|            for (id, ms) in [(\"doyle\", 500u64), (\"ling\", 100)] {\n 533|                let p = perch::resolve_perch_path_in(&owlery, id, ParentHint::Infer);\n 534|                std::fs::create_dir_all(&p).unwrap();\n 535|                write_info(&p, &InfoJson::new(id, \"t\", 4242, \"sid\", \"live_agent\")).unwrap();\n## notif.rs\n 588|            seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*589|            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent\n 590|            seed_perch(&owlery, \"dunsen\", STATUS_OFFLINE, Some(9_000)); // dead — never a target\n 591|\n 592|            let s = store(home);\n...\n 651|            let doyle = seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*652|            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent but hidden\n 653|\n 654|            let mut policy = home_policy();\n 655|            policy.vis.set_override(\"ling\", \"home\", Some(true));\n...\n 793|            let owlery = perch::owlery_dir();\n*794|            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 795|            let doyle = seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n 796|\n 797|            let s = store(home);\n...\n 861|            let owlery = perch::owlery_dir();\n*862|            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 863|            seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n 864|\n 865|            let s = store(home);\n...\n 920|            let owlery = perch::owlery_dir();\n*921|            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 922|\n 923|            let s = store(home);\n 924|            let mut e = epochs(home);\n...\n 991|            let owlery = perch::owlery_dir();\n*992|            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 993|\n 994|            let s = store(home);\n 995|            let mut e = epochs(home);\n## pairhost.rs\n 436|                    }\n*437|                    Err(_) => tokio::time::sleep(Duration::from_millis(500)).await,\n 438|                }\n 439|            }\n 440|        }\n...\n 476|        spt_net::net::pairing::ntp::force_refresh,\n*477|        Duration::from_millis(500),\n 478|    )\n 479|    .await\n 480|    .map_err(|e| {\n## projwriter.rs\n 50|/// overridable for int rigs (`SPT_PROJINDEX_POLL_MS`), never for production.\n*51|const POLL_MS: u64 = 500;\n 52|/// Debounce window after the first observed event — a burst (an adapter\n 53|/// stampede, a multi-endpoint rename) coalesces into ONE reconcile.\n 54|const DEBOUNCE_MS: u64 = 250;\n## psyrelay.rs\n 357|        with_home(|_| {\n*358|            let doyle = seed_live_perch(\"doyle\", 500);\n 359|            let mut h = NotifHarness::new();\n 360|            let got = drive_with(\n 361|                &mut h,\n## resting.rs\n 724|\n*725|        // Opt-in: dormant at t=1000, threshold 500.\n*726|        let knob = Some(500);\n 727|        assert_eq!(\n 728|            transition(\n 729|                Dormant,\n...\n 740|                Some(1_000),\n*741|                AutoSuspendTick { now_ms: 1_500 },\n 742|                knob\n 743|            ),\n 744|            Some(Suspended),\n...\n 1066|\n*1067|        // Node enables 500ms; dormant since 1000; tick at 2000 ⇒ suspended.\n 1068|        let report = apply_event(\n 1069|            d.path(),\n 1070|            RestEvent::AutoSuspendTick { now_ms: 2_000 },\n*1071|            Some(500),\n 1072|            2_000,\n 1073|            || Ok(()),\n 1074|            || {},\n...\n 1098|            RestEvent::AutoSuspendTick { now_ms: u64::MAX },\n*1099|            Some(500),\n 1100|            2_000,\n 1101|            || Ok(()),\n 1102|            || {},\n## shellwake.rs\n 622|            backoff_base_ms: 100,\n*623|            backoff_cap_ms: 1_500,\n 624|            give_up_after: 6,\n 625|        };\n 626|        assert_eq!(backoff_ms(&p, 0), 0);\n...\n 629|        assert_eq!(backoff_ms(&p, 4), 800);\n*630|        assert_eq!(backoff_ms(&p, 5), 1_500, \"saturates at the cap\");\n 631|        assert_eq!(\n 632|            backoff_ms(&p, 60),\n*633|            1_500,\n 634|            \"huge failure counts can't overflow\"\n 635|        );\n 636|    }\n## sync.rs\n 702|                timeout: Duration::from_secs(30),\n*703|                now_ms: 5000,\n 704|                window_ms: 0,\n 705|            },\n 706|        )\n## translation.rs\n 36|/// a translation filter has nothing to flush on EOF.\n*37|const REAP_GRACE: Duration = Duration::from_millis(500);\n 38|/// Poll interval while waiting out [`REAP_GRACE`].\n 39|const REAP_POLL: Duration = Duration::from_millis(20);\n 40|\n\n## pump/\n### health.rs\n 304|        assert_eq!(h.last_registry_admit_ms, Some(2_000), \"a fresh admit advances\");\n*305|        h.note_registry_admit(Some(500));\n 306|        assert_eq!(h.last_registry_admit_ms, Some(2_000), \"an older stamp never regresses\");\n 307|    }\n 308|\n### mod.rs\n 1288|        assert!(due(Some(earlier), false, now, Duration::from_millis(10)));\n*1289|        assert!(!due(Some(earlier), false, now, Duration::from_millis(500)));\n 1290|    }\n 1291|\n 1292|    // [unit->REQ-CONV-2] [unit->REQ-INST-3] the wake edge (a worker's"}],"details":{"scopePath":"crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":40,"fileCount":17,"files":["crates/spt-daemon/src/brainproc.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/config.rs","crates/spt-daemon/src/consent.rs","crates/spt-daemon/src/deadline.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/grants.rs","crates/spt-daemon/src/notif.rs","crates/spt-daemon/src/pairhost.rs","crates/spt-daemon/src/projwriter.rs","crates/spt-daemon/src/psyrelay.rs","crates/spt-daemon/src/pump/health.rs","crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/resting.rs","crates/spt-daemon/src/shellwake.rs","crates/spt-daemon/src/sync.rs","crates/spt-daemon/src/translation.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brainproc.rs","count":7},{"path":"crates/spt-daemon/src/broker.rs","count":1},{"path":"crates/spt-daemon/src/config.rs","count":3},{"path":"crates/spt-daemon/src/consent.rs","count":2},{"path":"crates/spt-daemon/src/deadline.rs","count":2},{"path":"crates/spt-daemon/src/dispatch.rs","count":1},{"path":"crates/spt-daemon/src/grants.rs","count":1},{"path":"crates/spt-daemon/src/notif.rs","count":6},{"path":"crates/spt-daemon/src/pairhost.rs","count":2},{"path":"crates/spt-daemon/src/projwriter.rs","count":1},{"path":"crates/spt-daemon/src/psyrelay.rs","count":1},{"path":"crates/spt-daemon/src/pump/health.rs","count":1},{"path":"crates/spt-daemon/src/pump/mod.rs","count":1},{"path":"crates/spt-daemon/src/resting.rs","count":6},{"path":"crates/spt-daemon/src/shellwake.rs","count":3},{"path":"crates/spt-daemon/src/sync.rs","count":1},{"path":"crates/spt-daemon/src/translation.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## brainproc.rs\n  62│/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n *63│const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n  64│\n  65│/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n  66│/// broker stamps at spawn time (ADR-0018 V2). One channel, three payloads: the\n    │...\n 220│    // semantic the live-agent adapter inherits.\n*221│    write_ready(generation);\n 222│\n 223│    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n 224│    // online/offline exclusivity — a reconcile loop that runs each offline\n    │...\n 250│    // reconcile in the background (ready-without-warm is preserved because\n*251│    // write_ready already fired above), then drain/debounce/coalesce the\n 252│    // invalidation queue. Same posture as the hosts above: the stop flag is\n 253│    // held for symmetry, the thread dies with the brain, and the next brain\n 254│    // rehydrates from the persisted index (warm start, fingerprint-gated).\n    │...\n 296│        let _ = brain.sessions();\n*297│        write_ready(generation);\n 298│    }\n 299│}\n 300│\n    │...\n 364│// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n*365│fn write_ready(generation: u64) {\n 366│    let mut obj = serde_json::json!({ \"pid\": std::process::id(), \"generation\": generation });\n 367│    // Best-effort: omit `exe_hash` if the self-read fails (additive — a reader\n 368│    // tolerates its absence). Never fail the ready write over a diagnostic field.\n*369│    if let Some(hash) = current_exe_hash() {\n 370│        obj[\"exe_hash\"] = serde_json::Value::String(hash);\n 371│    }\n 372│    let _ = spt_store::atomic::atomic_write_string(&brain_ready_path(), &obj.to_string());\n    │...\n 380│/// D7-1 test (the resident-bytes diagnostic the enlyzeam regression lacked).\n*381│fn current_exe_hash() -> Option<String> {\n 382│    let exe = std::env::current_exe().ok()?;\n 383│    let bytes = std::fs::read(exe).ok()?;\n 384│    Some(crate::release::sha256_hex(&bytes))\n## broker.rs\n 4473│    ///    then RELEASE it;\n*4474│    /// 2. stop each resident translation child (`terminate()`, bounded 500ms) —\n 4475│    ///    frees the OS file lock on the install-dir binary;\n 4476│    /// 3. CRC-swap `install_dir` from the CLI's `staging_dir` ONCE (W3b\n 4477│    ///    never-strand: a mid-swap failure rolls the install dir back to OLD);\n## config.rs\n 518│        let path = dir.path().join(\"daemon.json\");\n*519│        std::fs::write(&path, r#\"{\"crash_grace_ms\": 500}\"#).unwrap();\n 520│\n 521│        let cfg = DaemonConfig::load_from(&path);\n*522│        assert_eq!(cfg.crash_grace, Duration::from_millis(500));\n 523│        assert_ne!(\n 524│            cfg.crash_grace, DEFAULT_CRASH_GRACE,\n 525│            \"must override the default\"\n    │...\n 576│        // key) loads as empty — forward/back-compat, never a parse failure.\n*577│        std::fs::write(&path, r#\"{\"pulse_period_ms\": 5000}\"#).unwrap();\n 578│        assert!(\n 579│            DaemonConfig::load_from(&path).startup_endpoints.is_empty(),\n 580│            \"a file without the key back-loads as empty, not an error\"\n## consent.rs\n 130│        seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*131│        seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent live\n 132│                                                               // More recently active than ling, but OFFLINE → excluded.\n 133│        seed_perch(&owlery, \"dunsen\", STATUS_OFFLINE, Some(9_000));\n 134│        // Live but never stamped → ranks as 0, never wins over a stamped peer.\n    │...\n 138│        assert_eq!(target.id, \"ling\");\n*139│        assert_eq!(target.last_active_ms, 500);\n 140│    }\n 141│\n 142│    // [unit->REQ-UPD-4] no live session ⇒ no target (the gate then defers; it\n## deadline.rs\n 250│        // fired-point step, never behind it.\n*251│        assert_eq!(a.advance_after_fire(1_500, 1_400), 1_600);\n 252│    }\n 253│\n 254│    #[test] // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    │...\n 284│        // now < anchor must not underflow-panic; elapsed saturates to 0 → anchor.\n*285│        assert_eq!(a.next_fire(500), 1_000);\n 286│        assert_eq!(a.next_fire(0), 1_000);\n 287│    }\n 288│\n## dispatch.rs\n 190│/// Backoff base for a released claim (doubles per failed attempt).\n*191│const DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n 192│\n 193│/// The backoff before retry number `attempts` (1-based) runs.\n 194│fn retry_backoff(attempts: u32) -> Duration {\n## grants.rs\n 531│            // the prompt must land at the user's session, not the asker's.\n*532│            for (id, ms) in [(\"doyle\", 500u64), (\"ling\", 100)] {\n 533│                let p = perch::resolve_perch_path_in(&owlery, id, ParentHint::Infer);\n 534│                std::fs::create_dir_all(&p).unwrap();\n 535│                write_info(&p, &InfoJson::new(id, \"t\", 4242, \"sid\", \"live_agent\")).unwrap();\n## notif.rs\n 588│            seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*589│            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent\n 590│            seed_perch(&owlery, \"dunsen\", STATUS_OFFLINE, Some(9_000)); // dead — never a target\n 591│\n 592│            let s = store(home);\n    │...\n 651│            let doyle = seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n*652│            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500)); // most recent but hidden\n 653│\n 654│            let mut policy = home_policy();\n 655│            policy.vis.set_override(\"ling\", \"home\", Some(true));\n    │...\n 793│            let owlery = perch::owlery_dir();\n*794│            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 795│            let doyle = seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n 796│\n 797│            let s = store(home);\n    │...\n 861│            let owlery = perch::owlery_dir();\n*862│            seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 863│            seed_perch(&owlery, \"doyle\", STATUS_ONLINE, Some(100));\n 864│\n 865│            let s = store(home);\n    │...\n 920│            let owlery = perch::owlery_dir();\n*921│            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 922│\n 923│            let s = store(home);\n 924│            let mut e = epochs(home);\n    │...\n 991│            let owlery = perch::owlery_dir();\n*992│            let ling = seed_perch(&owlery, \"ling\", STATUS_ONLINE, Some(500));\n 993│\n 994│            let s = store(home);\n 995│            let mut e = epochs(home);\n## pairhost.rs\n 436│                    }\n*437│                    Err(_) => tokio::time::sleep(Duration::from_millis(500)).await,\n 438│                }\n 439│            }\n 440│        }\n    │...\n 476│        spt_net::net::pairing::ntp::force_refresh,\n*477│        Duration::from_millis(500),\n 478│    )\n 479│    .await\n 480│    .map_err(|e| {\n## projwriter.rs\n 50│/// overridable for int rigs (`SPT_PROJINDEX_POLL_MS`), never for production.\n*51│const POLL_MS: u64 = 500;\n 52│/// Debounce window after the first observed event — a burst (an adapter\n 53│/// stampede, a multi-endpoint rename) coalesces into ONE reconcile.\n 54│const DEBOUNCE_MS: u64 = 250;\n## psyrelay.rs\n 357│        with_home(|_| {\n*358│            let doyle = seed_live_perch(\"doyle\", 500);\n 359│            let mut h = NotifHarness::new();\n 360│            let got = drive_with(\n 361│                &mut h,\n## resting.rs\n  724│\n *725│        // Opt-in: dormant at t=1000, threshold 500.\n *726│        let knob = Some(500);\n  727│        assert_eq!(\n  728│            transition(\n  729│                Dormant,\n     │...\n  740│                Some(1_000),\n *741│                AutoSuspendTick { now_ms: 1_500 },\n  742│                knob\n  743│            ),\n  744│            Some(Suspended),\n     │...\n 1066│\n*1067│        // Node enables 500ms; dormant since 1000; tick at 2000 ⇒ suspended.\n 1068│        let report = apply_event(\n 1069│            d.path(),\n 1070│            RestEvent::AutoSuspendTick { now_ms: 2_000 },\n*1071│            Some(500),\n 1072│            2_000,\n 1073│            || Ok(()),\n 1074│            || {},\n     │...\n 1098│            RestEvent::AutoSuspendTick { now_ms: u64::MAX },\n*1099│            Some(500),\n 1100│            2_000,\n 1101│            || Ok(()),\n 1102│            || {},\n## shellwake.rs\n 622│            backoff_base_ms: 100,\n*623│            backoff_cap_ms: 1_500,\n 624│            give_up_after: 6,\n 625│        };\n 626│        assert_eq!(backoff_ms(&p, 0), 0);\n    │...\n 629│        assert_eq!(backoff_ms(&p, 4), 800);\n*630│        assert_eq!(backoff_ms(&p, 5), 1_500, \"saturates at the cap\");\n 631│        assert_eq!(\n 632│            backoff_ms(&p, 60),\n*633│            1_500,\n 634│            \"huge failure counts can't overflow\"\n 635│        );\n 636│    }\n## sync.rs\n 702│                timeout: Duration::from_secs(30),\n*703│                now_ms: 5000,\n 704│                window_ms: 0,\n 705│            },\n 706│        )\n## translation.rs\n 36│/// a translation filter has nothing to flush on EOF.\n*37│const REAP_GRACE: Duration = Duration::from_millis(500);\n 38│/// Poll interval while waiting out [`REAP_GRACE`].\n 39│const REAP_POLL: Duration = Duration::from_millis(20);\n 40│\n\n## pump/\n### health.rs\n 304│        assert_eq!(h.last_registry_admit_ms, Some(2_000), \"a fresh admit advances\");\n*305│        h.note_registry_admit(Some(500));\n 306│        assert_eq!(h.last_registry_admit_ms, Some(2_000), \"an older stamp never regresses\");\n 307│    }\n 308│\n### mod.rs\n 1288│        assert!(due(Some(earlier), false, now, Duration::from_millis(10)));\n*1289│        assert!(!due(Some(earlier), false, now, Duration::from_millis(500)));\n 1290│    }\n 1291│\n 1292│    // [unit->REQ-CONV-2] [unit->REQ-INST-3] the wake edge (a worker's"},"isError":false,"timestamp":1784272065269}}
{"type":"message","id":"81186c93","parentId":"2f47e3e6","timestamp":"2026-07-17T07:07:45.270Z","message":{"role":"toolResult","toolCallId":"call_yoczDJvKt3RWn4fbvoxPKSm9|fc_06e10c77aaab3d16016a59d4bdb2c081939bc5e8edbcebb38b","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## brainproc.rs\n 368|    // tolerates its absence). Never fail the ready write over a diagnostic field.\n*369|    if let Some(hash) = current_exe_hash() {\n 370|        obj[\"exe_hash\"] = serde_json::Value::String(hash);\n 371|    }\n 372|    let _ = spt_store::atomic::atomic_write_string(&brain_ready_path(), &obj.to_string());\n...\n 380|/// D7-1 test (the resident-bytes diagnostic the enlyzeam regression lacked).\n*381|fn current_exe_hash() -> Option<String> {\n 382|    let exe = std::env::current_exe().ok()?;\n 383|    let bytes = std::fs::read(exe).ok()?;\n 384|    Some(crate::release::sha256_hex(&bytes))"}],"details":{"scopePath":"crates","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/brainproc.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brainproc.rs","count":2}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## brainproc.rs\n 368│    // tolerates its absence). Never fail the ready write over a diagnostic field.\n*369│    if let Some(hash) = current_exe_hash() {\n 370│        obj[\"exe_hash\"] = serde_json::Value::String(hash);\n 371│    }\n 372│    let _ = spt_store::atomic::atomic_write_string(&brain_ready_path(), &obj.to_string());\n    │...\n 380│/// D7-1 test (the resident-bytes diagnostic the enlyzeam regression lacked).\n*381│fn current_exe_hash() -> Option<String> {\n 382│    let exe = std::env::current_exe().ok()?;\n 383│    let bytes = std::fs::read(exe).ok()?;\n 384│    Some(crate::release::sha256_hex(&bytes))"},"isError":false,"timestamp":1784272065269}}
{"type":"message","id":"d1452bf5","parentId":"81186c93","timestamp":"2026-07-17T07:07:45.271Z","message":{"role":"toolResult","toolCallId":"call_mk3R9Gct7sUzSjS0Ae93pw5U|fc_06e10c77aaab3d16016a59d4bdb2d08193afdaebbc61f2ad39","toolName":"grep","content":[{"type":"text","text":" 30|/// append so the file can never grow without bound (risk #2).\n*31|pub const MAX_LEDGER: usize = 64;\n 32|\n 33|/// Why a session became active.\n 34|#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n...\n 75|    /// digest cursor anchors to (REQ-DIGEST-CURSOR). The ledger is bounded\n*76|    /// ([`MAX_LEDGER`]): a `read_all` index renumbers survivors when the oldest row\n 77|    /// prunes, which would re-number a consumer's cursor under it. A persisted\n 78|    /// ordinal is stamped once at [`append`] and never moves, so a pruned survivor\n 79|    /// keeps its number. **Additive serde-default**: a pre-migration row missing\n...\n 83|    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n*84|    pub ordinal: Option<u64>,\n 85|    /// The adapter[:profile] the session ran under (D-2, REQ-SESSION-ADAPTER-RECORDED).\n 86|    /// A resume-from-history restores THIS harness — the one that authored the\n 87|    /// transcript — not merely the endpoint's *current* stamp (which a B-2\n...\n 98|/// line is skipped, the rest still load (the degrade-don't-crash store stance).\n*99|pub fn read_all(perch: &Path) -> Vec<SessionEntry> {\n 100|    let Ok(s) = std::fs::read_to_string(perch.join(LEDGER_FILE)) else {\n 101|        return Vec::new();\n 102|    };\n...\n 113|    }\n*114|    let all = read_all(perch);\n 115|    let start = all.len().saturating_sub(k);\n 116|    all[start..].to_vec()\n 117|}\n...\n 121|/// its persisted ordinal; a pre-migration `None` row falls back to its **absolute**\n*122|/// `read_all` index (not the slice index) so it stays comparable across sessions.\n 123|/// The absolute index of returned entry `i` is `(all.len() - returned.len()) + i`.\n 124|// [impl->REQ-DIGEST-CURSOR]\n 125|pub fn last_k_with_ordinals(perch: &Path, k: usize) -> Vec<SessionEntry> {\n...\n 128|    }\n*129|    let all = read_all(perch);\n 130|    let start = all.len().saturating_sub(k);\n 131|    all[start..]\n 132|        .iter()\n...\n 135|            let mut e = e.clone();\n*136|            // Some from disk; else the absolute read_all index as the fallback.\n*137|            e.ordinal = Some(e.ordinal.unwrap_or((start + i) as u64));\n 138|            e\n 139|        })\n 140|        .collect()\n...\n 150|pub fn append(perch: &Path, entry: &SessionEntry) -> io::Result<bool> {\n*151|    let mut entries = read_all(perch);\n 152|    if entries.last().map(|e| e.session_id.as_str()) == Some(entry.session_id.as_str()) {\n 153|        return Ok(false); // same session re-bound — not a new boundary\n 154|    }\n 155|    // BACKFILL: stamp any pre-migration (`ordinal == None`) row with its concrete\n*156|    // read_all index, so post-migration the ledger is fully ordinal-stamped and no\n 157|    // survivor renumbers on a later prune (REQ-DIGEST-CURSOR).\n 158|    for (i, e) in entries.iter_mut().enumerate() {\n 159|        if e.ordinal.is_none() {\n*160|            e.ordinal = Some(i as u64);\n 161|        }\n 162|    }\n 163|    // The new row's ordinal is one past the highest existing (monotonic, persisted).\n...\n 206|        assert!(append(&perch, &entry(\"s2\", SessionTrigger::Clear)).unwrap());\n*207|        let all = read_all(&perch);\n 208|        assert_eq!(all.len(), 2);\n 209|        assert_eq!(all[0].session_id, \"s1\");\n 210|        assert_eq!(all[0].trigger, SessionTrigger::Boot);\n...\n 225|        );\n*226|        assert_eq!(read_all(&perch).len(), 1);\n 227|        assert!(append(&perch, &entry(\"s2\", SessionTrigger::Clear)).unwrap());\n*228|        assert_eq!(read_all(&perch).len(), 2);\n 229|    }\n 230|\n 231|    // [unit->REQ-TERM-6] last_k returns the newest K rows; the ledger is bounded so\n...\n 239|        }\n*240|        assert_eq!(read_all(&perch).len(), MAX_LEDGER, \"ledger is bounded\");\n 241|        let last3 = last_k(&perch, 3);\n 242|        assert_eq!(last3.len(), 3);\n 243|        let n = MAX_LEDGER + 10;\n...\n 264|        assert!(append(&perch, &with_cwd).unwrap());\n*265|        let all = read_all(&perch);\n 266|        assert_eq!(all.len(), 1);\n 267|        assert_eq!(all[0].session_id, \"s-cwd\");\n 268|        assert_eq!(\n...\n 291|        .unwrap();\n*292|        let all = read_all(&perch);\n 293|        assert_eq!(all.len(), 2, \"both an old (no-cwd) and new row load\");\n 294|        assert_eq!(all[0].session_id, \"old\");\n 295|        assert!(\n...\n 340|        .unwrap();\n*341|        let all = read_all(&perch);\n 342|        assert_eq!(all.len(), 2, \"both an old (no-adapter) and new row load\");\n 343|        assert!(\n 344|            all[0].adapter.is_none(),\n...\n 384|        .unwrap();\n*385|        let all = read_all(&perch);\n 386|        assert_eq!(all.len(), 1);\n 387|        assert_eq!(all[0].adapter.as_deref(), Some(\"claude-spt\"));\n 388|        assert!(all[0].ordinal.is_some(), \"append still stamps the ordinal\");\n...\n 400|        let rows = last_k_with_ordinals(&perch, 4);\n*401|        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n 402|        assert_eq!(ords, vec![0, 1, 2, 3], \"ordinals are stamped monotonically\");\n 403|    }\n 404|\n...\n 415|        }\n*416|        assert_eq!(read_all(&perch).len(), MAX_LEDGER, \"ledger is bounded\");\n 417|        let rows = last_k_with_ordinals(&perch, MAX_LEDGER);\n 418|        // The oldest survivor was the (n - MAX_LEDGER)th appended → ordinal == that.\n 419|        let oldest = rows.first().unwrap();\n...\n 421|            oldest.ordinal,\n*422|            Some((n - MAX_LEDGER) as u64),\n 423|            \"the oldest survivor keeps its STAMPED ordinal (no renumber on prune)\"\n 424|        );\n 425|        let newest = rows.last().unwrap();\n*426|        assert_eq!(newest.ordinal, Some((n - 1) as u64), \"newest keeps its stamp\");\n 427|        // The whole survivor window is contiguous and monotonic.\n*428|        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n*429|        let expected: Vec<u64> = ((n - MAX_LEDGER) as u64..n as u64).collect();\n 430|        assert_eq!(ords, expected, \"ordinals survive contiguous, never renumbered\");\n 431|    }\n 432|\n 433|    // [unit->REQ-DIGEST-CURSOR] a pre-migration ledger (rows with no `ordinal` key)\n*434|    // backfills each existing row to its read_all index on the first append, then the\n 435|    // new row continues monotonically past the highest.\n 436|    #[test]\n 437|    fn pre_migration_rows_backfill_to_index_on_append() {\n...\n 448|        // Loaded with ordinal == None until an append migrates them.\n*449|        assert!(read_all(&perch).iter().all(|e| e.ordinal.is_none()));\n 450|        append(&perch, &entry(\"new2\", SessionTrigger::Clear)).unwrap();\n 451|        let rows = last_k_with_ordinals(&perch, 3);\n*452|        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n 453|        assert_eq!(\n 454|            ords,\n 455|            vec![0, 1, 2],\n...\n 458|        // The backfill is persisted (re-reading shows concrete ordinals).\n*459|        assert!(read_all(&perch).iter().all(|e| e.ordinal.is_some()));\n 460|    }\n 461|\n 462|    // [unit->REQ-TERM-6] a blank/garbage line is skipped, the valid rows still load.\n...\n 472|        .unwrap();\n*473|        let all = read_all(&perch);\n 474|        assert_eq!(all.len(), 1);\n 475|        assert_eq!(all[0].session_id, \"good\");\n 476|    }"}],"details":{"scopePath":"crates/spt-store/src/sessions.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-store\\src\\sessions.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":31,"fileCount":1,"files":["crates/spt-store/src/sessions.rs"],"fileMatches":[{"path":"crates/spt-store/src/sessions.rs","count":31}],"truncated":false,"displayContent":"  30│/// append so the file can never grow without bound (risk #2).\n *31│pub const MAX_LEDGER: usize = 64;\n  32│\n  33│/// Why a session became active.\n  34│#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n    │...\n  75│    /// digest cursor anchors to (REQ-DIGEST-CURSOR). The ledger is bounded\n *76│    /// ([`MAX_LEDGER`]): a `read_all` index renumbers survivors when the oldest row\n  77│    /// prunes, which would re-number a consumer's cursor under it. A persisted\n  78│    /// ordinal is stamped once at [`append`] and never moves, so a pruned survivor\n  79│    /// keeps its number. **Additive serde-default**: a pre-migration row missing\n    │...\n  83│    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n *84│    pub ordinal: Option<u64>,\n  85│    /// The adapter[:profile] the session ran under (D-2, REQ-SESSION-ADAPTER-RECORDED).\n  86│    /// A resume-from-history restores THIS harness — the one that authored the\n  87│    /// transcript — not merely the endpoint's *current* stamp (which a B-2\n    │...\n  98│/// line is skipped, the rest still load (the degrade-don't-crash store stance).\n *99│pub fn read_all(perch: &Path) -> Vec<SessionEntry> {\n 100│    let Ok(s) = std::fs::read_to_string(perch.join(LEDGER_FILE)) else {\n 101│        return Vec::new();\n 102│    };\n    │...\n 113│    }\n*114│    let all = read_all(perch);\n 115│    let start = all.len().saturating_sub(k);\n 116│    all[start..].to_vec()\n 117│}\n    │...\n 121│/// its persisted ordinal; a pre-migration `None` row falls back to its **absolute**\n*122│/// `read_all` index (not the slice index) so it stays comparable across sessions.\n 123│/// The absolute index of returned entry `i` is `(all.len() - returned.len()) + i`.\n 124│// [impl->REQ-DIGEST-CURSOR]\n 125│pub fn last_k_with_ordinals(perch: &Path, k: usize) -> Vec<SessionEntry> {\n    │...\n 128│    }\n*129│    let all = read_all(perch);\n 130│    let start = all.len().saturating_sub(k);\n 131│    all[start..]\n 132│        .iter()\n    │...\n 135│            let mut e = e.clone();\n*136│            // Some from disk; else the absolute read_all index as the fallback.\n*137│            e.ordinal = Some(e.ordinal.unwrap_or((start + i) as u64));\n 138│            e\n 139│        })\n 140│        .collect()\n    │...\n 150│pub fn append(perch: &Path, entry: &SessionEntry) -> io::Result<bool> {\n*151│    let mut entries = read_all(perch);\n 152│    if entries.last().map(|e| e.session_id.as_str()) == Some(entry.session_id.as_str()) {\n 153│        return Ok(false); // same session re-bound — not a new boundary\n 154│    }\n 155│    // BACKFILL: stamp any pre-migration (`ordinal == None`) row with its concrete\n*156│    // read_all index, so post-migration the ledger is fully ordinal-stamped and no\n 157│    // survivor renumbers on a later prune (REQ-DIGEST-CURSOR).\n 158│    for (i, e) in entries.iter_mut().enumerate() {\n 159│        if e.ordinal.is_none() {\n*160│            e.ordinal = Some(i as u64);\n 161│        }\n 162│    }\n 163│    // The new row's ordinal is one past the highest existing (monotonic, persisted).\n    │...\n 206│        assert!(append(&perch, &entry(\"s2\", SessionTrigger::Clear)).unwrap());\n*207│        let all = read_all(&perch);\n 208│        assert_eq!(all.len(), 2);\n 209│        assert_eq!(all[0].session_id, \"s1\");\n 210│        assert_eq!(all[0].trigger, SessionTrigger::Boot);\n    │...\n 225│        );\n*226│        assert_eq!(read_all(&perch).len(), 1);\n 227│        assert!(append(&perch, &entry(\"s2\", SessionTrigger::Clear)).unwrap());\n*228│        assert_eq!(read_all(&perch).len(), 2);\n 229│    }\n 230│\n 231│    // [unit->REQ-TERM-6] last_k returns the newest K rows; the ledger is bounded so\n    │...\n 239│        }\n*240│        assert_eq!(read_all(&perch).len(), MAX_LEDGER, \"ledger is bounded\");\n 241│        let last3 = last_k(&perch, 3);\n 242│        assert_eq!(last3.len(), 3);\n 243│        let n = MAX_LEDGER + 10;\n    │...\n 264│        assert!(append(&perch, &with_cwd).unwrap());\n*265│        let all = read_all(&perch);\n 266│        assert_eq!(all.len(), 1);\n 267│        assert_eq!(all[0].session_id, \"s-cwd\");\n 268│        assert_eq!(\n    │...\n 291│        .unwrap();\n*292│        let all = read_all(&perch);\n 293│        assert_eq!(all.len(), 2, \"both an old (no-cwd) and new row load\");\n 294│        assert_eq!(all[0].session_id, \"old\");\n 295│        assert!(\n    │...\n 340│        .unwrap();\n*341│        let all = read_all(&perch);\n 342│        assert_eq!(all.len(), 2, \"both an old (no-adapter) and new row load\");\n 343│        assert!(\n 344│            all[0].adapter.is_none(),\n    │...\n 384│        .unwrap();\n*385│        let all = read_all(&perch);\n 386│        assert_eq!(all.len(), 1);\n 387│        assert_eq!(all[0].adapter.as_deref(), Some(\"claude-spt\"));\n 388│        assert!(all[0].ordinal.is_some(), \"append still stamps the ordinal\");\n    │...\n 400│        let rows = last_k_with_ordinals(&perch, 4);\n*401│        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n 402│        assert_eq!(ords, vec![0, 1, 2, 3], \"ordinals are stamped monotonically\");\n 403│    }\n 404│\n    │...\n 415│        }\n*416│        assert_eq!(read_all(&perch).len(), MAX_LEDGER, \"ledger is bounded\");\n 417│        let rows = last_k_with_ordinals(&perch, MAX_LEDGER);\n 418│        // The oldest survivor was the (n - MAX_LEDGER)th appended → ordinal == that.\n 419│        let oldest = rows.first().unwrap();\n    │...\n 421│            oldest.ordinal,\n*422│            Some((n - MAX_LEDGER) as u64),\n 423│            \"the oldest survivor keeps its STAMPED ordinal (no renumber on prune)\"\n 424│        );\n 425│        let newest = rows.last().unwrap();\n*426│        assert_eq!(newest.ordinal, Some((n - 1) as u64), \"newest keeps its stamp\");\n 427│        // The whole survivor window is contiguous and monotonic.\n*428│        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n*429│        let expected: Vec<u64> = ((n - MAX_LEDGER) as u64..n as u64).collect();\n 430│        assert_eq!(ords, expected, \"ordinals survive contiguous, never renumbered\");\n 431│    }\n 432│\n 433│    // [unit->REQ-DIGEST-CURSOR] a pre-migration ledger (rows with no `ordinal` key)\n*434│    // backfills each existing row to its read_all index on the first append, then the\n 435│    // new row continues monotonically past the highest.\n 436│    #[test]\n 437│    fn pre_migration_rows_backfill_to_index_on_append() {\n    │...\n 448│        // Loaded with ordinal == None until an append migrates them.\n*449│        assert!(read_all(&perch).iter().all(|e| e.ordinal.is_none()));\n 450│        append(&perch, &entry(\"new2\", SessionTrigger::Clear)).unwrap();\n 451│        let rows = last_k_with_ordinals(&perch, 3);\n*452│        let ords: Vec<u64> = rows.iter().map(|e| e.ordinal.unwrap()).collect();\n 453│        assert_eq!(\n 454│            ords,\n 455│            vec![0, 1, 2],\n    │...\n 458│        // The backfill is persisted (re-reading shows concrete ordinals).\n*459│        assert!(read_all(&perch).iter().all(|e| e.ordinal.is_some()));\n 460│    }\n 461│\n 462│    // [unit->REQ-TERM-6] a blank/garbage line is skipped, the valid rows still load.\n    │...\n 472│        .unwrap();\n*473│        let all = read_all(&perch);\n 474│        assert_eq!(all.len(), 1);\n 475│        assert_eq!(all[0].session_id, \"good\");\n 476│    }"},"isError":false,"timestamp":1784272065271}}
{"type":"message","id":"acda82f0","parentId":"d1452bf5","timestamp":"2026-07-17T07:07:54.707Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Estimating executable and data sizes**\n\n**Planning detailed thread inspection**"},{"type":"toolCall","id":"call_JPhYINqft5tEV7oKIRaPA6xB|fc_03c662885d559ab5016a59d4c74bf8819496fa5729e4e154b4","name":"read","arguments":{"i":"Reading seat writer lifetime","path":"crates/spt-daemon/src/nethost.rs","selector":"155-235"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":10491,"output":245,"cacheRead":108032,"cacheWrite":0,"totalTokens":118768,"reasoningTokens":203,"cost":{"input":0.052455,"output":0.00735,"cacheRead":0.054015999999999995,"cacheWrite":0,"total":0.113821}},"stopReason":"toolUse","timestamp":1784272065314,"responseId":"resp_03c662885d559ab5016a59d4c1af5481948ddc81d9713c84d9","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_03c662885d559ab5016a59d4c3232081948a4e0ca0aa64ac72","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdTHIDtsWv114CITVQM_Zgwvql5XnXUKj47N_unRMRxyzUJ-PKW7b9g4oUGo69IMNBY3n6TpQFtnMRx_C9WHfEMFbKht8ytJcOkBQYGmBgCU1MY1llq2NKEUbbv2AtP8UYmNX43o54rfUhYHxathe87ZuDh8pa-AxHHC052LuNdIH-rMTxIQlFeh_yY8X_7JCLuUEKnh1M9Mmo5-83jmi1O8ZIcuTSlalwQKZrQqanwHIzjRAnS1_ADhjw6v6YCOemxw-_6bSWIRwv8Fg_F2QaY46Y5l3iMVDhJYK9cys_vTs9FHEDgEK8dT2vdttZAA14Z0ozaJYuDAjbmDaXwUIgMHm3c8Kp912KZdvXqRzfnwGGSXCnMcPi2Yievhu0ohztkCZpWuv4Gvd2hRpsnQcjlZ9hooPVv5LhzZj9b2SOBOTmqnVnx26UIW0JxcF8SE8u8OP6AQ5Q8kMUjt7sVE2uYXu9XxgWJ-y7fML2fiN64rFMETM2ywahJS9anOMsl8KhWujd_bHRY25xbKtpfkPS3E-y6VObjsGnv4AZRmiMjQD6kyxMhNqESLsqiRqpa4Xk7vbJ1mtb-GLs0i1SxHliNGJVY0ept4zQiREFjFC3VjsPVKPJDCCdX--eAnZ0U348diJn7Y228YJEEAvBr64DKrmAF5uYc62bKOq9i3D1RFfyWuL-RWq_56AJfx_TO0rUnZd5HElC941N8qpCvfWFmnawTqv_asRQDApWo2zTHOOrYzfjqdUeSsG7Uu49aVjQd0dsbSCXaCGFqlCIqPmHXp2otI4UL9F7ouDOVhCvdTOFMw66eMjtV3Xn5MIiQ3UJ4bdHHXWe4OA_Y1ogl7sh9g1FcthN69gSqHELp9lIhQY-TwV5QBnqtXqoRYURAAxLiIxbJbBVooA651RrcFYUT8OugWln2680-LXkKRtAz2X8-evM4GXo8gwYfVCY_wcfyBeRKyOAuovmF8lIctaI6KcCgC3Jvgxk4ezyRWdS9Uya6gW26O2Eo5RJnjEsim-Mf0Kg81vk4fWLfz_UtqIN8H-NIeQiiGlIFtb7w5teNoCsA-yWcnK4eZjZ61EgKlSKwUX_QzWUzVt-J2_AaeQGwxPXwO_04DvNTAR4aHuQQb6-e5uiUgcYvs5W2v3SqndYk6DQKueYgI6aniM7TN_y-CnqVgv0XAZeQKChqhebXjExCw1dB6DFiKMcihSyCYcgOTnjlY5GK58-xpvOXL56bfvogELYy-D7icaWpucRIpM94oH4Hp74Cd9aTm5vuoNSg1BZHtvGgFLmZdzuuS_Btud2UCg2J1P7pjuC_aR9uQnymYhVl7J1i2QvNDGyVigSL7GOzaa9pDNEFj0KGXExSBuPgQjujwXR4TXymslnH9dRv8iH6Wv2S-04IWL1Mn5mMNOPyuFQAp1Kg5Jz7FQL1jQF8R-ZU1yLx5wz7Yhf_DbP8Y1P6MEWAEIuTpDnVgmBU2YaEGb8d43NP0PaIepO8pvPIFRUAwu9Vah4TiAzojdrA8AneQWCkUSmFgOa3nHPiz50c7DjiKCNLX329L7x544I36HIhz0sMLDhTDvxdT2POG_UGbVVcdv8_w9tlbHInLkm-T-GyH0ZeHhe38ARO51awyzW1nvzRjzy1XX2Q9z8wx29q0o-D176Wnwm7ASALBN2hgyq9RoOl5YbFMd7Zv8D9zj0pABzL-JnThmj1IznAg5ARuoMhTiNOFV_0zSkQbwWgiixhR1rTgUeLfn8IMC-1IAOrAJ7C_Q6U0Och5tGKwDQmtyEPihH_H67rsooi4qJmKXWb01cyo7YkWtNZxwtbpixHBXP_x1dsVS7CrSJmYWc_kvHRVvuXUdh-GovBTPhgvgRFY0t0Xsg05f--gtBpyp8njqooudBbMg-Do3NiVqBVhdMGpuI0S9pn9X9yIaLT7ZWKLoQH-hyWvbwBL31aUx-zY_vKYIogq0WD0tk8gGzUp7xa2bnVCElABJe6tuXUo4witHgmkHLHFm8mWnwIpKvt8JSM9OikKBsZOvhnu5iNjyhME7we6AXrGxhB5","summary":[{"type":"summary_text","text":"**Estimating executable and data sizes**"},{"type":"summary_text","text":"**Planning detailed thread inspection**"}]},{"id":"fc_03c662885d559ab5016a59d4c74bf8819496fa5729e4e154b4","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading seat writer lifetime\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"155-235\"}","call_id":"call_JPhYINqft5tEV7oKIRaPA6xB","name":"read"}]},"duration":9392.804800003767,"ttft":1837.8451000005007,"contextSnapshot":{"promptTokens":118523,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_JPhYINqft5tEV7oKIRaPA6xB|fc_03c662885d559ab5016a59d4c74bf8819496fa5729e4e154b4","toolName":"read","startedAt":"2026-07-17T07:07:54.707Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading seat writer lifetime"},"id":"1fffec11","parentId":"acda82f0","timestamp":"2026-07-17T07:07:54.707Z"}
{"type":"message","id":"34487281","parentId":"1fffec11","timestamp":"2026-07-17T07:07:54.726Z","message":{"role":"toolResult","toolCallId":"call_JPhYINqft5tEV7oKIRaPA6xB|fc_03c662885d559ab5016a59d4c74bf8819496fa5729e4e154b4","toolName":"read","content":[{"type":"text","text":"struct SubscriberSeat {\n    sub: SharedSend,\n    tx: std::sync::mpsc::SyncSender<Envelope>,\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\n    /// and a replacement may install (the no-reinstall-until-gone rule,\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\n    done: Arc<std::sync::atomic::AtomicBool>,\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\n}\n\nimpl SubscriberSeat {\n    /// Install a seat: spawn its writer thread and hand back the handle.\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\n    /// touching the live queue, so replay-then-live seq order holds by\n    /// construction even while producers enqueue concurrently. (Gate follow-\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\n    /// the mutex — a concurrent append could slot a live frame into the\n    /// middle of the replay and break the brain's strict gap check: the xfer\n    /// re-serve \"got seq 134 want 6\" CI red.)\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\n    /// a traffic-quiet stream where no producer enqueue would ever run\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\n    /// left the lease alive until the next append; the T4 forwarder starved).\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\n    /// displaced — is harmless by construction: every install mints a fresh\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n    fn install(\n        sub: SharedSend,\n        replay: Vec<Envelope>,\n        lease: Option<Arc<StreamLease>>,\n    ) -> SubscriberSeat {\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\n        let writer_sub = Arc::clone(&sub);\n        let writer_done = Arc::clone(&done);\n        let writer_poisoned = Arc::clone(&poisoned);\n        std::thread::spawn(move || {\n            let poison = |halt_reason: &str| {\n                writer_poisoned.store(true, Ordering::Release);\n                if let Some(l) = &lease {\n                    l.cancel();\n                }\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\n            };\n            // Replay first — halt at the first failed write (Amendment\n            // fix 2), never iterate past a poisoned conn.\n            for frame in replay {\n                if writer_sub.write(&frame).is_err() {\n                    poison(\"replay write failed\");\n                    writer_done.store(true, Ordering::Release);\n                    return;\n                }\n            }\n            // Then live frames, same halt discipline.\n            while let Ok(frame) = rx.recv() {\n                if writer_sub.write(&frame).is_err() {\n                    poison(\"live write failed\");\n                    break;\n                }\n            }\n            writer_done.store(true, Ordering::Release);\n        });\n        SubscriberSeat {\n            sub,\n            tx,\n            done,\n            poisoned,\n        }\n    }\n\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\n    /// push). `Err` means the seat must be removed.\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\n        if self.poisoned.load(Ordering::Acquire) {\n            return Err(SeatFail::Poisoned);\n        }\n        match self.tx.try_send(frame) {\n            Ok(()) => Ok(()),\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\n…\n        }\n    }\n…\n}\n\n[Showing lines 154-238 of 2865. Use :239 to continue]"}],"details":{"truncation":{"content":"struct SubscriberSeat {\r\n    sub: SharedSend,\r\n    tx: std::sync::mpsc::SyncSender<Envelope>,\r\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\r\n    /// and a replacement may install (the no-reinstall-until-gone rule,\r\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\r\n    done: Arc<std::sync::atomic::AtomicBool>,\r\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\r\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\r\n}\r\n\r\nimpl SubscriberSeat {\r\n    /// Install a seat: spawn its writer thread and hand back the handle.\r\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\r\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\r\n    /// touching the live queue, so replay-then-live seq order holds by\r\n    /// construction even while producers enqueue concurrently. (Gate follow-\r\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\r\n    /// the mutex — a concurrent append could slot a live frame into the\r\n    /// middle of the replay and break the brain's strict gap check: the xfer\r\n    /// re-serve \"got seq 134 want 6\" CI red.)\r\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\r\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\r\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\r\n    /// a traffic-quiet stream where no producer enqueue would ever run\r\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\r\n    /// left the lease alive until the next append; the T4 forwarder starved).\r\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\r\n    /// displaced — is harmless by construction: every install mints a fresh\r\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn install(\r\n        sub: SharedSend,\r\n        replay: Vec<Envelope>,\r\n        lease: Option<Arc<StreamLease>>,\r\n    ) -> SubscriberSeat {\r\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\r\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let writer_sub = Arc::clone(&sub);\r\n        let writer_done = Arc::clone(&done);\r\n        let writer_poisoned = Arc::clone(&poisoned);\r\n        std::thread::spawn(move || {\r\n            let poison = |halt_reason: &str| {\r\n                writer_poisoned.store(true, Ordering::Release);\r\n                if let Some(l) = &lease {\r\n                    l.cancel();\r\n                }\r\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\r\n            };\r\n            // Replay first — halt at the first failed write (Amendment\r\n            // fix 2), never iterate past a poisoned conn.\r\n            for frame in replay {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"replay write failed\");\r\n                    writer_done.store(true, Ordering::Release);\r\n                    return;\r\n                }\r\n            }\r\n            // Then live frames, same halt discipline.\r\n            while let Ok(frame) = rx.recv() {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"live write failed\");\r\n                    break;\r\n                }\r\n            }\r\n            writer_done.store(true, Ordering::Release);\r\n        });\r\n        SubscriberSeat {\r\n            sub,\r\n            tx,\r\n            done,\r\n            poisoned,\r\n        }\r\n    }\r\n\r\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\r\n    /// push). `Err` means the seat must be removed.\r\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\r\n        if self.poisoned.load(Ordering::Acquire) {\r\n            return Err(SeatFail::Poisoned);\r\n        }\r\n        match self.tx.try_send(frame) {\r\n            Ok(()) => Ok(()),\r\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\r","truncated":true,"truncatedBy":"lines","totalLines":2712,"totalBytes":4109,"outputLines":85,"outputBytes":4109,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"struct SubscriberSeat {\r\n    sub: SharedSend,\r\n    tx: std::sync::mpsc::SyncSender<Envelope>,\r\n    /// Writer thread exited (failure or clean drain) — the seat is fully gone\r\n    /// and a replacement may install (the no-reinstall-until-gone rule,\r\n    /// REQ-DISPATCH-FALLBACK-CIRCUIT).\r\n    done: Arc<std::sync::atomic::AtomicBool>,\r\n    /// The writer died on a WRITE FAILURE (poison), not a clean close.\r\n    poisoned: Arc<std::sync::atomic::AtomicBool>,\r\n}\r\n\r\nimpl SubscriberSeat {\r\n    /// Install a seat: spawn its writer thread and hand back the handle.\r\n    /// `replay` is the attach snapshot, moved in UNDER the log mutex (a\r\n    /// memory move, never conn I/O): the writer drains it COMPLETELY before\r\n    /// touching the live queue, so replay-then-live seq order holds by\r\n    /// construction even while producers enqueue concurrently. (Gate follow-\r\n    /// up: the first fix-3 shape enqueued the replay from the caller OUTSIDE\r\n    /// the mutex — a concurrent append could slot a live frame into the\r\n    /// middle of the replay and break the brain's strict gap check: the xfer\r\n    /// re-serve \"got seq 134 want 6\" CI red.)\r\n    /// `lease` (StreamLog seats; `None` for presence) is the stream's serve\r\n    /// lease AT INSTALL: the writer cancels it the moment a write fails, so\r\n    /// the paired serve/wire worker's next forward refuses PROMPTLY — even on\r\n    /// a traffic-quiet stream where no producer enqueue would ever run\r\n    /// `drop_seat` (a mid-replay poison with no follow-on appends previously\r\n    /// left the lease alive until the next append; the T4 forwarder starved).\r\n    /// A LATE cancel from a superseded writer — poisoned OR healthily\r\n    /// displaced — is harmless by construction: every install mints a fresh\r\n    /// lease Arc, so an old writer cancels only its own generation's Arc.\r\n    // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n    fn install(\r\n        sub: SharedSend,\r\n        replay: Vec<Envelope>,\r\n        lease: Option<Arc<StreamLease>>,\r\n    ) -> SubscriberSeat {\r\n        let (tx, rx) = std::sync::mpsc::sync_channel::<Envelope>(SUBSCRIBER_QUEUE_CAP);\r\n        let done = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let poisoned = Arc::new(std::sync::atomic::AtomicBool::new(false));\r\n        let writer_sub = Arc::clone(&sub);\r\n        let writer_done = Arc::clone(&done);\r\n        let writer_poisoned = Arc::clone(&poisoned);\r\n        std::thread::spawn(move || {\r\n            let poison = |halt_reason: &str| {\r\n                writer_poisoned.store(true, Ordering::Release);\r\n                if let Some(l) = &lease {\r\n                    l.cancel();\r\n                }\r\n                writer_sub.lifecycle_event(\"stream-sub-writer-poison\", halt_reason);\r\n            };\r\n            // Replay first — halt at the first failed write (Amendment\r\n            // fix 2), never iterate past a poisoned conn.\r\n            for frame in replay {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"replay write failed\");\r\n                    writer_done.store(true, Ordering::Release);\r\n                    return;\r\n                }\r\n            }\r\n            // Then live frames, same halt discipline.\r\n            while let Ok(frame) = rx.recv() {\r\n                if writer_sub.write(&frame).is_err() {\r\n                    poison(\"live write failed\");\r\n                    break;\r\n                }\r\n            }\r\n            writer_done.store(true, Ordering::Release);\r\n        });\r\n        SubscriberSeat {\r\n            sub,\r\n            tx,\r\n            done,\r\n            poisoned,\r\n        }\r\n    }\r\n\r\n    /// Non-blocking enqueue (the producer sites: append, finish, presence\r\n    /// push). `Err` means the seat must be removed.\r\n    fn enqueue(&self, frame: Envelope) -> Result<(), SeatFail> {\r\n        if self.poisoned.load(Ordering::Acquire) {\r\n            return Err(SeatFail::Poisoned);\r\n        }\r\n        match self.tx.try_send(frame) {\r\n            Ok(()) => Ok(()),\r\n            Err(std::sync::mpsc::TrySendError::Full(_)) => Err(SeatFail::Overflow),\r\n…\n        }\n    }\n…\n}","startLine":154,"lineNumbers":[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,null,240,241,null,252]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":4109,"outputLines":85,"outputBytes":4109,"shownRange":{"start":154,"end":238},"nextOffset":239}}},"isError":false,"timestamp":1784272074726}}
{"type":"message","id":"43ac2727","parentId":"34487281","timestamp":"2026-07-17T07:08:36.772Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying seat retirement and handler cleanup**\n\n**Assessing connection lifecycle and cleanup discrepancies**\n\n**Clarifying connection ID versus stream counts**"},{"type":"thinking","thinking":"**Analyzing seat lifecycle and leaks**\n\n**Verifying seat cleanup race conditions**\n\n**Identifying transient seat retention under churn**"},{"type":"thinking","thinking":"**Identifying thread leakage in completion-tail handling**\n\n**Planning falsifiable tests for retirement behavior**\n\n**Designing test hooks for stream count assertions**"},{"type":"thinking","thinking":"**Planning thread retirement fixes**\n\n**Tracing blocked seat thread behavior**"},{"type":"toolCall","id":"call_rtk0hzlZkMyKUgFU6cU8gxNb|fc_0a8ef892ce91ffed016a59d4f3b4a88194b9da66cf04e17b90","name":"read","arguments":{"i":"Reading retry constants","path":"crates/spt-daemon/src/dispatch.rs","selector":"180-230"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5562,"output":1993,"cacheRead":114176,"cacheWrite":0,"totalTokens":121731,"reasoningTokens":1953,"cost":{"input":0.02781,"output":0.05979,"cacheRead":0.057088,"cacheWrite":0,"total":0.144688}},"stopReason":"toolUse","timestamp":1784272074736,"responseId":"resp_0a8ef892ce91ffed016a59d4cb2f2c81948294da1e363c20d4","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a8ef892ce91ffed016a59d4cd6c8081949fc051c9b70cb82d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdTYpekw_NwT4Z6QZDFMzseKqw_Yy-5MlLHT2Tqy_xIYP2PMZs7w-fmdgvPDttzo5ucoty2cjp_Mq2CB5keltvRs8Oj8KQe5V-r4yBBXot-pAKWvP8qA0OSLhPEbM5CSTGloYYopRy5r5AzUOKopA5IxRQRpEChotGN05b8HLCA0n0bf35Ij8U2G9T8cFfEd3T2NqexX1AN6VlovH9dAvbh1q3xr1NbL6xzc4hpaOIrL6rv3YyaWxbMSwjSvy55UMGAM_TlOeDeZ9zw4O0gDCqMMeNiqi3ougodqJnIlWRZOqaI3QOlQf_QwXjVgYr4_eLETdbJkTvd2DiSxfX3pZoM59YeyABgtOm-dZUWkMY0RG_ro7BOtYoR8Vrf_6z-LdnkhOOHuOMh3MjriSaQJ1miPc4o2-97R7KJUUfr-cXqboXzeQhI21tSEywWYy4rBVmLulKJvGwfZbechI1QqhhpzKTJstp--oYCutDAyCIGLXkLClcdckk99pmeEMsPAyixs8YBQisTtvcbnQK7n1MznE8TGCXPQUYMn41YTLZGKFrNPdY53RXBOe8DPlWMn0q4koK_ljrESOKCSuz1wHVSuSQVee_Jf-7hOseJO8wgCn_CrgRXm4Zjyx5Rs0FbELoGpl39NMrFGgZgOcgudzqOa3MANP3xkA5uXFBuoBE3j-im09eFworO26avL6XaPS5TgkK3RHxIWJegvR-csMfkTWh26_8o4cHVLSClw9zQA4j_UoU1oAzosibvn-D6nVXldsFeY9wHyE4WI06yFY8HVcTvPUlLZiXC9pftbqUBH-pWZgjJlz_3-7rjfIi_PRjbg5pBFTAdGUhgiwxVwxiPBcsSFhrqxLGRhA5I_p_GHkywzmwGQAwxmQKgAV1yJjUTj4aGDf4eKg1UcimpUHtYmYN8TCIQHJDb-Yngym76qHs5LQFVGFr1whV3s90XPuqeRK8shet_4yTpWNgQ7Rf9kNln4gjjOcQ17gzaguClqa0mUvYFancQImMTTtJfnRVg3aSKjcsWpGUOlcW2ZJ4KDq6_pp6bntrnbzJArD5BoSo62qHT6pE7K-y9mkMtzY6w1qcxCoCfy6CuA4l7_R5T4VCVutbZHzsJ7X6XGyTSL5hjqgiwkPHaSTMwEAZX36qYf3y5vfxoviCaFwJT5wLhf6AVWLf484HjYVgvQ7khf4cdxTArXjo9oH8avDNrkl4zw4kNAhSZ4pqWr5LstpKx-GdEg2ZeGTGG2h0GrX67gupMVAaKKGRFejD4eEugJsl1LCyOMRe7Y3WOWab8gns14KMeiybpE9ov0QEunrhhnq70afI0SvKufBnzdB8s_g6Ze-glu8Ltt3Rp5fd--RrvneKu9CUjqHBWfuxxIy58wPUFZKlRDpzhxMs1zgstgaNojEYrYFgHYiOg31L61MeJfnxf2jKvdcWncWqKQlkQi2kOiFTdUFcGQl5rBn8DsDKSt5eMd9p-Pvor8Nd3uKYAaPgrg1RVNe5y_zH6mgp2syUp-QaZXk6fCJm4ikXxgdD-vVkH4qSWIVZG62XMO6wKbkx1Xks9s4SGIfsQfEJ4QKeTQ9-eAQ3rfHlrMEV4grmHbW8BEMHQs-w4Ipndcegc8VtcoK82agseJZMFYtUXL2Kg3tSpZ4PIh9rWAeprajWsAQvjKHp8jAbwdL29EKqvYBkvZCu2BuUCa4Z4dZX4Gn9rSnta2L2YRCJudAngFUeOdiCVcSwvw2dsZWDPI7R3jdf0nb2ypT4UA7WC0phGOwfzzRDrrikhi07alAgMskeLVadhll4KqYe7wLc64Dn-bLeVlLWsnvvztR7tVKwyssS8YKboDRjUTYCYhYsC2F8sWL9hpuoDmnObJCLT2JtwPklw3-XO9Aga6c4mbvahTrwBjenmG5l9YzYxBq_aDPun0U1Ix-o8vOd2NX9sLNNwFcOIg4UC52Elm6uHA2vxnH_5exsE7syIAYjizkAGF1E3BVM3-QMgJbC5OA2hPVF1Uhh9ySX_ZwTSisx_HBLupPEdkcBGlHMCJIRBkWyak9M3UZc-jmLJWAlgJAF5QTI_-ltDSCwIY3cHGE9e4b1J7uNXp-cc22Q8eY5YaWO4K3QpgD8y2rmx8p8TidC4RDlfFGvEEuktsc1FIpj6WztPwbnxTRaB9BaxpYr5VN37RzdvfxuqVK-Dn7Z8Zu_7l3E6ACb43J2667P0h9Ujvj0PGoa1AVWNnkT56AsiRet6MR7L1HqiR4CT8a8nGYY7ZIHAGC9QtxYMNNg79hQsk_ZF6buDAER676RXBb5BuJuW9PiqiQ7UhgO2n8NlMR9SnxHNBU-Sq8IFXp4qioikPJBmBovoch55_3S8wJ08pmF0l1bmhIx_fySbfRT0OzXIeh4o1EIQrquQ2PcOcahuS2LQKJo2fBJKuvoFqEVnQoioianTspSTgZUV9iR8oFqRPJI9lp8S9juslqFwpR5ezm8Skfcr0n7-sIgb9Wma-2dQFkoQybFJw20lprnu7_J152NPC5OulP0sf98O2zUX9J_NRQhKyiFTbM_MehXOJ0YYcY41WqCQY2jy4xNpvCkniBa8OJYaJoLVafG05P5t5OpOsa8QmlhYRNzjfgU9je_z9E85XeukccZ3S_WvXN3JpK0C8huAhDtoucz9cNDH1wCw91v7YfcgCsO2zb6hL_WWs3hBrNdSbtEm7MeyhlQi-uwyWINHZideq-W6beIcBm9-adlie4v0xJg2MHwFqMrDVgdw_KIJ1BYuYXPDwJZQU5I09RlFgQYJcowhG64MPSeZ1y4EfLpPHE2iM-QYjKbVMY76bGZ8cBAzzl5YhfWZbShnO8y7RqXzEjzwR8CRQYccog5jZjWCIZjK9Zs_OHzvV3OpFMvQRPJjMgZnh6HTnZFnSD1ym8xzP20hW32rvfg62c8B_iGsOEZxRAK_OZfuS2TSP6HleWpMeEqCNnYtlRQMHtebZNG1tMkvLJfDtgLUU14vR1ei3-hRVg9EPW18RQ97jv45l6etIhUUu0L6uJK3Qn6FbbzKzqb1gGGA-7ApF5_iMv5eVZDp_nyZfPJZBN5oJikGOpR6stjCnZg6qzJHOMJM3-DPLmuaNMn4HRWlG_LsDMpaV1ffprENGY3rA5KBcUtjLHGfEwIoNDX4yiibyUtF9nmputSboM56mMLgT4bSpXGzcyBYqhpYCwi5UvOstNsko1Mv8L4UWDS4wLEqJzsX7htkqQG60-bDQoXrWMeeksD4wxdbDb4Qq24TOC0pxMFt8CfMXUWoOcs5fZKV2IiGQN0Cb-BUAWxTO7rZL-S86mToOspr9E0u4cipHBPreATGqM4GQNtqB8rNFcaj1ufhbC351cP4Vweg_AfN5MPd92g9MgqJVCK420U6OOpQe_na8hRkUg3ZuOg6FvdKnUp9Vp4lc-rahjYqn-18gawVIVBPkyhL4mT_ewddWgXCEL9JNUp34L_Lcp60QH-WelE29k2hDhvOKtTvhFJrzEEdKIbXm2zYbYeEWZ-h-6DiK21hHq2YiufsXf1YQUZue-yrdEt7YwbGlbwOdwTw5cmuUG1yozdkX6T8tzwNaALfUdpO4munkbN0Tju4KSmcbjycsgCZnl8me-WHcElWZIcjabyfQh1oU4zyEeo_wumiehlbeUQZzw9QAIGPbSauGPz9UQ2UwGkwLiEQE3FQZHP3AcyifQa3WwuhOf7Ph_iEYeq7F6RKEtk0kaqcF0x90ZU7jAJjrk66aS_UoIzAc65oYwa310eud_TUwCbF2MqSWXsIUD-OsnIHGvAXfQie75rlRFIfF4GF9FczPg9nFenBGRN7QCPeZX_84Tp8mqQxLXHvpDiweGEqK6Guk9BVKMERuQE_7bIA6spWA6W5nzf5Tf_TIW53JuFRuyU-fh19Z3pXtFQrzUoVjwfYZay9q4RZt8VJWV43IqQJJrE5uNj71RAmvj45qiE206mDdHm8X-w7GLbHAsb5YJuW19O9ewHrZP2FBUMrxcfIoawJx5CehJCw9LxNcUIRzW_MAIRRsKcfIb0c6mHzEfIiOgCMV9G8-kVeR1SA8wQLQMxj3zjoXX7zVEmfhpP1qQKSyDW68AuTG7h-wDXluOFeEShXYN1SAAsV69l1MxK2xh8szyBo9ki990XQMneDr1t1gHr_1","summary":[{"type":"summary_text","text":"**Verifying seat retirement and handler cleanup**"},{"type":"summary_text","text":"**Assessing connection lifecycle and cleanup discrepancies**"},{"type":"summary_text","text":"**Clarifying connection ID versus stream counts**"}]},{"id":"rs_0a8ef892ce91ffed016a59d4d8b2208194948b20c42d8e5e21","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdTibfjn0mSrrUrDe3lIcP8afNUTvg_xRd9x7-KoOh5jyfBIfZwXfD3PWhEOuhd3m7HJ8peEtCO6r1BUHKo46Lg8befEu-nrAzvqgJ0Y2YVSFOsB3VzHSDItq3VduS4uw54sf5R_t7L-AAs6WVdjLie27_z-dF1vnhsEXOwK63pP5oxl245s3VlI1N4xzhwFtGCxC5OqnB5pNQO8KpN_89fShuP-iB8A0Cv8XlKqd_cHJ7mH85Pb3kzxxJwpvOPT_Ql-rqAHoq-XgqZSq9ER-Kze69C57pDRAKyd5yD2_4_k0wX7y1m1sC5A_G0c4jhrOhTOxtgGivvnYqvVUmzix_PQZWqtb4z1hP-o7PgaRpFUe07CcUa5FjLIVDKkP73R39rh59KPPWSLRWEA8JCJmJVGAzJnjZadqPInNe7_mbiBtDXdq-UFlf6MntSy6pN-mPhZL1Bm9VqLNeVOoXUYSJg6l9UiYSBGe4PCnPN6E8Inj307_vifLVCFD9HL-ZRi4cHi0pUWH-fbspRZjMhvjYa73ozAYXSj71WlMdWFHciaPvo0aCV2Jb-X_yZg1agUKs0JxBtS346T7Y8FmNilUySXKB1OncPd3Es1mhB6InGzC2-NlB1rJrDeshJCkieA52eAqN_BxLKNkPBnzKHfFDSheTTSunuhdDxgxaW_wLTsYoF_9XKq5rPW01l73OOsUS7gBt-O3bANGQTSvEVCtUYIZn2MtKIz7FQkYqFJ3yaHPKGV8KLZmFUOzD5UceMrpueuNRNIOrf1jgNjeAZug0M_zVx_0OytaZ5kVsKHQeHZ8q7tc8Zs7Q20be1vMEkvvjq-ZLdpR4sbmSHyaBa2fFvivoDllAaKLDlTDFYEIxYbkoilkKx7_JsrztdVlwbrtV4oJhJGaFxx_XgXdkx74zOD-ly8NCxteLLPPzO-3qZ762Bqhqj7FUamU_rn1AvUqRuk1Z4HjFxOyxQQcj8W8PpdOZzDnk3HwUZ40fwqNmt9ix1AWK2jnT7q7ST2ygAuHsS_moPeMABGgb2Lglyy7JQC3Wrf6aHD0XmRGTrS5KBkQrpkVGBFOIufGATxstoo8MEH-3IZaBNhdIipKPdo6paixBcBAk82vGQSaRYmBvxIlX9CEpg3agenWbOsOu1JupVOcEE-ZPxDy_RZNWWwNd5nWhO3MZASaqWriGxCcWUfMFiB8zvC6qiKLM6M7W7NxtEyrCyn6MWEilzLqN_zJAKXoaj9RyDx3Oq2YIA_x-DJHxbyX_7XFaAEXxbE8WSrZXeEPFVT1mVd7Z1eYda31zWy4TPYitYywFDIMR3iI2A3663fJ_to7oMIY9ekc2KUY3nOYMHYsajp6bqpmoO5s8pNj0sENjpLL2A2J1-SeC9eb12edxTNsCEXOkzPJ-x8Mo9-pLxCLvGA_5p-QtYJAKvfLbBJnenW6Xd__HSLi08lRvGea6BVp_MuUPCJHdHrpsJ6px2FcSLlBuDJGtIQELH0s9F6eNRKruur16Rlw-j_TITbIZiV3e7DnGj3389qF_L7UiWhAvDB4-dk67bVe52Pl7tA6TwR3YY3qbZcOW31vr_qTlGn-UnH9o2GMCUWtOlXb1vJ6HST4oW3wBajrX4B0LgjN6byXRd1pWSX9UpAT0j-Tn7t2ipH4s8zjDPSamp_l6RtPVu76-6J4GzPfrtCg1js-0x-ImLnPQobkhyZ00alk9obLFbsib2_kbM0GvUSSR7PZKuJi9-6zTBkposMGplsAXt_a4xm9r2wnoxC9MoD2YlHdpyNIk-MrTc20Ud8vaK8RJHS8-NwBfDhm4q5lptFrUyu-Fhq1voVZcExl-h25HSd3tcoJDH7SeXO-5Tyfocl7jtBEAt3Ibb_PkdaenTOWyTwUz1q9G6MsvP2rv_rfZjl_Abcroxn4tvkhSdpgVcWBdt5-s9U_4w8gJvqJjU4uCcytwcb9a6x7v5u8navhUpieCTkSORO6TPKfbCrvEV0QypWvHOubJyAfKvSPxaf_qaroUcJwDf8EYjOZ9n3mxuVaeuiyx5jLdN3PnYzlxDiMghKRES4Q1RMxZSzy1l9eXKqgQD9tk-B7e_qHIgMPnHQZwWp42YmH4ORNU88e_1IrMFyeWzI6hkHw8hKlhhLys534-hx9mGRqeEcAbxvYhEM2qF8doDMWlyk_43Nlu9dM0MbpAwvUa9jHF1Jxvqy8c-OIw6s24I5-45MotvGiChaeNUCnKhn19cuhYzRsRTkx56Z8RXWgE87pxPPKzpQwnruOPlTX4pDzrHqK2ro75Vyw69QMyF3aiSQkIzV7siucD5O6FlgPGVHavwJuZRqBzL7kME_aoq-rNIKo-uq45ylJPycgvCGv8S4r1iUm4EK4KkuHgfhR44b2pGV8x77c5mRBO71Vs9kPmOtqmwhskdKRM7fL4Um-gjMXxMX5qVkbT_nadD-smjIep9qItNVn9mR48taAcV_dQwDjV3u4JgQ96St5xISi9JQMu0eT9wrCXvAXbgw5Wipyw2vUq0J64rSJ8EW-x7yCyS40iSw3Vygd0EWNXVTzWC1jygUtkD9FytyqQuN-d06yl2Gg-F2IskKPNFa91perK7iA9C0qqRWyn5QDVjhAYf1ef4aK_cnRY5aArEO3nCUx4KAPPo6HUdSKpObUQGiLqigUpyVhQJTg9bxbmnQdcHzPQpN2N_h0gwsVmYWVk8wGuZ8FwZnMdzhfAVQ8b-YdNJDyduu3WDT4Y8RG5AesllILhU4HwrcxrqHj922FVDviQKUIztAWvyF8AwVKhYccsCJ1od8SmarQNx_tuCixPpSeuvZK9I5Q-IQ9yJw-454G22qkW5qTpiP7ehFFGO5H_gaEskK09ATs8OX6vio-OvkwzvQOhyMwniacWxdVN1C1OxcQoWRxgW5YHqJmab0KaBHbB0o__WT112jth-vqj5_ccXv78SiWMjFMI20Sp96lAmz1oRTjayFRd8AxOZmReGpCFS-1V_DRGKMS_cEP9fxS16qInd_fO-anVycy8PpbCiFoIn_6cZ7mKs80CWLBhOZyg34rW8QENsvYzTYfV6fIIfJmUwPFjQYxi3t7K2wgNLfJk04ooakoFEKHBEOJF5gb6pZi24XIsDLjbUJnY-mGyC3k6s3Jd4bVekbKA1nL9g-TxZCAIt8AqNftQbOjebvuz7Gk7zAp18JKQ7gRvnNm3YACj4iwqieB3yxkYtriCM0cLj2RJcT40ZvqsUDpJJiDZV2qbnA5VdAl-ChRE6onf2s9qhY6Be4yIOh9gy9_LGxHZ0fQIFu-jwB3B8ix5ylci7w8ssA7_bH_Rc00IXhz9t9y1N_la78XeZi4z1ztFKBfUgVpDUjcni0LJXT9njKTygUfHeG2CmDM0k7ikBEHxDiCbExCFf6-6xYIyhqGcK36qpJYpqC8MT7xBlEfQRDJc6bb18kXJSp8GMEisucNClLeKlJacFuGhlOiqWGh_XgpuKhmIazUNpwXdXFDtfZFnVs7-DPxsmCRTH1xNv5zCqgsTFa1oPJf2fyBp6KklHMAc8dapjVw20ELK4mzmiIrP_IiM3UwEVu3tZbPg7UIY7SnUVgqrIPXuiQfsbfWpRNWl76-xFwRWbCmBnwTEpsmhEcfZhPTF4FiRJEaJd6kYPDTjwdWEzAIT8Tif0Y82mynJ3yKJhOnyVjd4N8cARZWOgkoiVyXE4Zxs7HncjTN-5ZpbtbQjXD6_MmRWx3XNwJofr35DbqsOpRJZqk0gTldPAAO-ifUeFXbItMgGROimv22U1Jqr8F3h9tG1LffjQr5Vir8gsgULzYm_oueYFYS04BqLYFy2ROAthdJz_da66FbnDtH5X1ld3fTlIh0pu-2VgTxMEaNv4AL82r5AoelAYVAkxBYzOxcYhSajtUuj0KRjBue_2dHC2rYvTJjvNYs_nbwYho6qDku4VRbMlwa0miUbjrcOe29cYXeC8jXLjzzltp2d-28eYQ0ILB8KgP1DK2V1L-2DuK1via41vz4P_gkpgDrmZHkwxDyT8hr-KTA5XmNhOxD7jDTCCEn9XQm4utFZ_agVt5luxHOK0yXrMni-LjlDqlqYwQjvskzyg0KzaevmPUfFSC0K9s3hyu9GtOaKUC7-zLo2rt90gLL-4jgEceno4VJpVXRj96HhapwYP0cDe94BPFRWRAodP2siQ6or4gXYTkx-SGHM-PDyr0qQdPEV7mo_C4Kr3jXxz4a_HVygEESlCaULGNxUERNiYbjGRBbWp-Tly2WV1g3cJ6GFtObdyvkkI3ghpzIHM7SeqjZIdCK-t_R1jjPIuCvfHX1DuCuDbs7oG6331cpa_fC6e8XwPPk6PbdtyoUteIE1riwEKXTEKpL3KYblPukKZU9s0GsOI2PdrMtL5AGjqimovnwrFx8d3g3Vkwz9Ya","summary":[{"type":"summary_text","text":"**Analyzing seat lifecycle and leaks**"},{"type":"summary_text","text":"**Verifying seat cleanup race conditions**"},{"type":"summary_text","text":"**Identifying transient seat retention under churn**"}]},{"id":"rs_0a8ef892ce91ffed016a59d4e25c34819490e0ffb6f77f45d2","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdTtn4hHUxeADZyFKS6-L47_GcUm_rdpiAHddJy_dqVJeMiR7rJR1yYHeUVH6-TRSBbAwlJqO5z5yYCuivWw9fCbTe1DBA3YtIMLfxA-SYOjVbouHHEuj78fPGJscFI8o8xBPC7gkQ7313bRcVii8cp1CbIS7bgSoTArNFOr9tfj90lkyeH3RL909XePpsHkam7YJ5c2z7TofNs2fL4vKxCugw_xOdkNai8znIHkZIoAdr9qps7CO5kJ-GTZ42BnnusouYNrpO4oZHgyLhEcawJbx9z9rIYpzBbPj7x5n4wSrMHx8HHjzK7wspvWzDoIi6VgIqyCVAwTZI-Tm7DMgBya5IHS7PsNMd-g4eTjVWrnHEJU1_7Hw8P-Xnvm98iSRMiTVQF8QeCOmCKwUOjIlIRqOoM4M7bBLm01Xuf8DRT5kVgKs714pULsSD_9uEHfsClSsx4lxsj9_o54jyLlxd8JkiACQOek91m5Lv3Ck2XGpa4hzICnHtI1_aRB810LXcrDaLM7OI8EcQy3YBkXBMDyrdZveemOv1fsKygRDzdaa2ZLoM5aBamNjZVTwrKavgO_PTabHxfWogTY5_jQ-TVvXhhl9bifrUoBnX3F4ypdTBCrfehTJpE3H-ardL5l7rxJ-tsShhBP7HKnBjrLxA9_wLIw3rfLwUp5sGheOaD2yq5SLZAEfnqIh2ZcI6zmO5DDa4CfbRM56ie22uZt3WlTwEB1VoPpKcp8v00LNDBHkqOinJcoPtIwPmKPeN0EOuyFoIKgNPuXzKn_pGslDRnf4Dm1jpALz1QOntlHye0dGJG2cp-fYuNUgnt0KPRV4mZ7QXxY9C9SkpZR5X-Mf_me7rCz8stzz0NGZaWr7UgEDWlmM9y_Zs1FrEDYyvcGZwnMtv8Z2ty3dLVURK5GWO4NuZtOWx34Waoas_ln1ZV2X-dQ2pGr5OZGlBwXMRrtHXOqsxnGyQPL4nkI8NEmYQGjYbGZyamHZoi8TxtFaT-JDFTxEuC7fd2WU9GueFXeb70WnDG_EfB60Cc1K-XhGwQ0zYuRXsIMIuZCZnI5O0pINI24oyO8mFLQPFE60pP7BpNiC5idbak3xwCs05Aiiy9DP5j1HiZ--dEbfKGpe8LpiRrbyevnp4NpS_ypbO-VtFsHsQvBH19L9xeo4ntfM3DE7gjG0eSgCb2Zz4Fg91CReNTGV41ZGuPNPRrT7TyX4wK3SjvXMndJW8_9piXTdmU9scmrr2FtteYMout5SLoK1QHx_AlryPjMTq9NKftQS3F2UIHbpPtLhf0l7udSsfICnOW8eXfpGrOkqFOpfdtshbT6aeus81tPehBlIM87s9of0RTI7sPNN72Gh7XUkt6MwcCwgav5xWKJD4j8xzDSxDcuwnOJfFZFhTZ31CgYSKRVJ25LvwBK8PpHoZaXBtq4BT4bxnCpJvJpxNSHo3Ij7JPuOxnLcRbQ-jcacg47k8mIuLhJKFEW5YyhDfXTNJoIoeYUOoAVvgJlvECGwRmmYrXIUMEKLYFjyuBOluVlA8h7qGm-PKuBIc92j_M51ukOWl1FDsX8k-xrHHV629XVGOhS5WzCyCwpVJzC3PhbBUHsVHX475iG6GFwzLl6x_BMD2lC4MwpMHCDfmg2wuv-wJXkGqI3arT22VVMVk5l18RD3ee26wQ8iiObEMtk0xDh49jepHjUdJ1GTlZGb0l75CxQHFZgakBBR2h0wA6alRMVsGTYCq6-hoBaLb1I-eGv6zaK9GEMUDR8cudOrm2TxOr49lueEG9ptWrGg_vXmhFV92BmX2isfx_3P4SveGemf5MrhUE9gsWXh-CLCBumvftmMBsK7GawrflfoDYlGW06CILsGMTq7zkm6hpdhbakAXc3EDPuesZqeKl4T2OzJxsku3XeWYQin40kzT5gO-eGuCGZJUZ_rr-TVSzB1N923UMXavYoGq9rJXAsS4DegyTizIGHIXcJkvElhhnlNPNSBP2PVAr9jpPJR2W7PYzgZJG_eCE6H49-dAo_wMP0ckp_XtsDl7LaClYpVAncGqfmId_5SOe4OEGMMepIj8Gh4Msxlo-WGAzlwH74u_4niW2gWGBxhv9nRAquEkPGmn94tL7h5MGQSP8YkqKh_SzODmMj2lAkX2h-FR_bDsNsfG65sSaja5JV05o2KAsJyxmVORIyN-LaTvAlEBeR-P9s74WSIYP3veQ4JxLq40MsUvL1CnCuy_dJWR8qkJxUBI1fqVSRGYGAWDXW3UG7bdzfI7Mpc6IYcVSk8lF8o7OG69xL2SOfcQFZYbsyJOL4SDRh5XxXt8zsyycd44eItgZYFeZRYf_tIeIQRurRFQq8Xh5hSnSiEwy4aA0WLNzmPdndtzCw28M0qkQsBOulo2PllotCYZTZMkkNI3Fx_EbGSlT9eUhWfQvwmEvbquI1KEsaEU9MAMuaPraptl2e0yhdTjNaOwhDfiRigJwKNAVM-eh7FAUfIIz_VLp9nKPnp0DSniMT_asJkk0yh1apk3BtDUvSVeZcu3P6yBUx28sGLeivouoLfF2W7HAZi_XcQoxMmyfuJ6y06V-LhiisPi46ZwnXqMD2MDe5z2YVE2mYjQKeltUsdx2BoWMDjjQpinrAxpbKauoM9jXj6rxbe5zxM7xyOLoji5EVo8SIyqxCzHRcIzRwQ4w-ahoplBzattmCnokQOOUcwf6amXdqib8A2K_RacvWpnRw3LGjikcgXo22SARyflYIh7unFqTxxX48GOH-YmgzuKRpouo6UCUJJLZm2r9R7igBNR_hwqik09wfmKVweW0f2pZ2ffThr-fjzA_3VYHHDNKu4qvl_gUec0TgBobOuNnY0ppGZPI5KN8vpQaYzr4p8rbbB-levc_UdM97vEFmY5-ZndVdCAwKYDyUBso0xng7wPf_RZXmCdPaukSJEBa6JsYpeX3X02vI16okjAkRWm2cJw9dlr8UfKz_6CsFR5CyotiBXJSSHC7L7HcVqhXv1GWuwRd_-nY0IB0potGYW1xTeFaM7TXFK5VHCiwfGJLERFrsq-36sbK2CFWbEitXiGOT81CsnmksGVW79S3ct46J1WPsCI3qFi9RpySLyyuTE2R5juo6tcHMwrzEg581Hultz1ZY-_u5JWlBNAFu2obauUGh1TBmNPrKWRjGYB_B1ocDJNTbIrLpuMNokTpXaaOCRAEJ2JyQZ5vS5MnUhPgFFONCgH7g4iRtwzjrCesqO0gwQChtdY2NpS1T1OnOmH2AERjGR8LWco4uqTYU-dw72OU-sV5hZL4gmMF8ZpHhTSGo25jThi2C7yJNHDSGQhuLYz-H2Pp4dso51dY6aNXRAc9KSm4pviZm5qTEiRDO1Y3hGPebpykfNIp495NsFl_8GXVdcUHkBX8oeqpLg4HkoBWo3i4qkiXw47g_cZyblEM0gcnqLFFqb2oVJOYzsuZap0WnMXgR6OZNOSoBo_RR6krKG043t1P7wFD_QAchJVnej4eHjldtw5uUxU1j09Zl2Gh4j0tm8VLsPEcsogGH_4ShJDLwoeTLGrv6oKpeR54wbiKXucCedUObpT_hvLn6x5N3rFQadqtWuus4_YnBNvnt0LTV2Bo9wqz7H5CMAvPveA6BirueUTteHc4lYxKV0o5Aq369sII-tdRBsjak4DWFpl2UvAdPDwxI2FfWalDxJ_0de5a9Yxyj7OAiQijTGUBFdyLHNOo-D4mse23pNalOCaAExboakAJnrqcMpHLVrwDB_4SaouQKuA4BkzmIZFYhHqIivO2rw3cVUShE_EMukD_910z5Dr8SPz5-PkAylIG54ey4NE_2FRVMoH4gZqEE15fi-Dto1ns1ZcXoTSosAmxwH8b8ap7sf8hex0bpqst4IK9ur0nOiwH8ymbkjVvCx873donHI9cyiq1K8J3oL6iQExHdC02q8GnZ765UblNyP80dZ39kJGmJ4pFT8jC35bmb7ylXQ25n6xnR2aVUQqxc7Pdd5qm8znLRdIMZtSU0Z2Zgh6tx8WOmxK9HHg0rICtZF-TBXqXB4f3fkkG2Ez-gD_FnqmkcAx1GFyDgWFi-fv7-WzMMRLEkSyd_fQ560fuKVR3b66zep6jANMeA2qWSUAolzLuLInFXIomXDVtZLrDKZOgIieahC45obt08oYyML8CRgXkwkhyoLFwdeVqWk2J3-8OTBmuQuxVeid-ustXV21r4dA0Oj9Fw9TesDLBV2XWWYkBDWFG52gThQUNuSg==","summary":[{"type":"summary_text","text":"**Identifying thread leakage in completion-tail handling**"},{"type":"summary_text","text":"**Planning falsifiable tests for retirement behavior**"},{"type":"summary_text","text":"**Designing test hooks for stream count assertions**"}]},{"id":"rs_0a8ef892ce91ffed016a59d4edaf1c81948a6a510bee9de2b8","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdTzR0gdj_CIm8zwkBbHTgKolhjfSNXRYKq8PWzNgRwyPkwpgnqyGqLwVbzVjELUAvgpJSRpouj-jRIWIYXhyX1CmnFY90v5MNx0TBITYq8PFNKmmiy24hU7MgOrrX__Pfl_Y5ue0zdmG98XgcIUq-ClHuMq48D0UuE2vrUUFkaqv0_Z1ySZgjaT6xsvyD0fo590sQ3TS6H_Y5Pje1pNdZ6Ctf8n3_nc_t2YZAIZIF09yt9DbdRj1gjRyPyo9xdzG3e0SDdLaxCvV3chOczH26KA6H9O6OT1GnXXYFE4DICpakygFJOvpoM4-NvpPuH_8B4qeRfIA-9ahXk5xyTGdvzgCYXmselk0lOACm2PoScgMv4ekjHiuPdbZCkUt9qVssPTTa_61u0GS_C-SVBB0YnAqoox5u6eWlERfUSMDktrPfG5Z4_FrTPCQCo6kvBGAWhgC47bmGRHXBk57hZPiRIi1wr36CW_acXUYr9aMTl0jTKYVqU6PckmqxgnLyOO6uRVeD64qyAKrnMRVcdf-KRfuotlYOdLxl6WQtEGlZnvlHWXJuxWZrj2YiLwB7IIL5HHubzN4jKOoW-bn00DvagRuCe7CNs6CvBF8YEGjc5Ez2LOCLU_Adh0Mk5tOcqxxH0R_yfqdRW8j9sndoWJLVuxyaXZIdbeh1qbAhwt2avxfF-hO2nXnWgzfP2Jrf4WFcE4FpvDNHID7cUVgrHTdWKIbEf3RuoMp_nWbUamCwkQ4P8I5YEOlSu-7VRRje2aoXbcjrSIG_KTbtzRwO3mqoXE9vG9x0juZsZVH3UbTFrso946SXrrsjE6NoEqD4CZCgJ_zKrSrPqOuCWXPaiAYt7ybqX5ekkgUbrG61J6I9TrWoylEAXzSePILbacN9cPOI792leQSCQNp7MMeYlvVFte4fIWfVFthV4UvlEuSaoWXXoynJApBM3qd0BhNxXyYiNGumyS7othyCuFPqCPIIBA0y5M-dHMNJz2QapmQ1zge31AO9qcJAZcgGVQX3YRPMD2U_O8PFejBZfE4YtmB6KPWc4FZoAGNg-zlzaNnIwEe9Jpd29E9T2ZO9BXRQYxhfpUzxpqRPmcGeX5gIoawevInNg3XiGA57YKIheHkxHJSim4SKvLcTbQYoC1QwOnNGRqZeUe0D2AcuA9vN0jBC50iSJKf4SGuV7nwDx4WPtgv2kMg532lKOqA9jojytZbp8TS5tp5OnX6SHE8YdQdud3AlUoAiaoMY9x-6ZGVS_egfiQUKLM_XS9J4t1NWmYs3qNmDrPIevyd-x6KFFiD3baf4gs8OyxSx_Dg2cWJK008uVjveD_GTVg24uV78U3PqGuKefO5N9cc_YLcP-gBeykHWalZobXxxZ5ClUl4DKq22m0SjcFfkldk2GzCWgIIkXNxGaWtKb_mWuEj1JPCx48_kPRnNmhWXeHSS2Yz0hYcySIIQ4AMWfcawQmH6KDqZv-p8MHvfVYy_e6uBhA2_pvtuehmTqijfl1vyo2HDc5y0hDmrWPe-eeRUlWuC4n_4kThCcTCkUfOMFGUjXvfFH9vnyYkXUmubdjbrEXuy4L1yro887T8n5JMSu6B1II3a9Zn2TXZWduqOTAC1RUNhd69sDf--Q7L5u2pvbwd-MXFRVYUo2Kg6-ewNahkwhHh5CKKDgG8tktySZfK9yH6aOVL3zsVxxIt5I714319aMFP_Nsx2h9wynw589iewScZ7peZhF-NvjR1QyAne2T3rkJ3E85bh3guJhTbEcz6ThirwJmfCfy2gr-KI0eaM6-HdpV3_hYES7wQuaoRvWLihx9j3IB8i7fqyYQBErbhBMzyH3q74HOhhQ9gQ30_FsxUW6GwNY6F0bDI9PeEHGzwb-ElHljGlPB0xpHG5oVXQ3ESwgM4BHi9RZwYu8ciJvRasOHYCxkxSTzQw0hZlxpYY2y64ErqmWxYhXkQKn76GqOfKx8x2lssTe1SNpZpK7oLzYbL_DTa2rGFsQ0g92VVztoyRlMKmDmwViC_HTUQOy0d-ZAZ0IVUmGaGPPgftilTHGDSXxuvDytym4KB0HsS335nOxMvP_cdLCEgxPkB3WY3E7KO8j6-7OWmX2yHMfRXPwm-GuMa7BS4gKS-j9p6PL_SAc8fhzZBF0QXmfFHnfcWOxDE6IoptMl7x4MVT119-Blr_dhINUFFRDIEAXyxGwPDTQap2cweLXvZH5YPeZw-2R7qCStxPkVZgAuWK_fKhD1OCVEUjVu71lx8150Ofn3ePhTF-WLoEYtoIqlcsGgkWObbwLaZ-lB_TtkaxhuKcHbbcSHG831_LW0LX1Zc8uYYtUbYPq0nsLOMg9eVfjDggTAVnJ8UUT_jiQNeUjDlVI9ftAvztf8WRmGHPOky2V32bD39hw52mW8zc1E4Ye30D93h0M357U3UzKCMH3YYp_7lEGXMzYdbGcQ-DbyrReXkp5B8_4PVc2AJZCxBupr1n_uqbmWbf9R-PjwOzyxzy_rJXZhXlabOe-q-S2DdA1i-aaJCDgRamIBebcyoKNUWASZLDDDPmve0mQMOevEd8mdka4hKj5eZCFticB5yOfsuVG_ojeHKR-OY8VWwqgAu7ylJvWXHnHnysUjQpvYrCplLO9M43Qf8Qx7Zw88it7-4dSmUwfizEP4GbmRiawOXJ4uP9BcDPVlQ39cW9KdiFwj9TDFx3cpYUp_12cbLnZX56Uk0-lp_cJSreEU_q700QionN0qJaw-PbJXSbNn7CFZyoqVBbykWObFO0s7ibrmEICkQQqfYrLda_uODj-p0eIrbggg3_bMaVhu4p40c6tECNrLzjMN0JaaJZhNYS72SqKU21LhG5JPSvTndMsfJXpZUHe0TCvIWE16EvktqVTRnevXXbR6ouztdksiyZh8BgtQWyYPYVj0E6qMBjDFbWP4BwAvG6y0V7YciJrC8m7s7OT3VeRoI9MFl0Hr65JEmOEaNw6mER4qp01VYS_D26fei1cWDu9fUlP4YadfsczxnwUAYLixF4DDmAHdfdqzA-8nyF6lEU0Jl7UiAxfvlI3Gw-ZG-BsxgM3JCpKrhVqUazm2FUjxulvWhUyIOjb_KL4DoX0zRAXUQOT2aRNHE5hgJyzYzMcaggCa2tk8NDHUK9HDas8qIKdGPWveSVuGNassOSdN6RenMzdHuTgN7WLxsMDC6uWdb5Qs9xZxbUSQ","summary":[{"type":"summary_text","text":"**Planning thread retirement fixes**"},{"type":"summary_text","text":"**Tracing blocked seat thread behavior**"}]},{"id":"fc_0a8ef892ce91ffed016a59d4f3b4a88194b9da66cf04e17b90","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading retry constants\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"180-230\"}","call_id":"call_rtk0hzlZkMyKUgFU6cU8gxNb","name":"read"}]},"duration":42035.78479999304,"ttft":2694.967500001192,"contextSnapshot":{"promptTokens":119738,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_rtk0hzlZkMyKUgFU6cU8gxNb|fc_0a8ef892ce91ffed016a59d4f3b4a88194b9da66cf04e17b90","toolName":"read","startedAt":"2026-07-17T07:08:36.773Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading retry constants"},"id":"daf8b83e","parentId":"43ac2727","timestamp":"2026-07-17T07:08:36.773Z"}
{"type":"message","id":"b2670015","parentId":"daf8b83e","timestamp":"2026-07-17T07:08:36.788Z","message":{"role":"toolResult","toolCallId":"call_rtk0hzlZkMyKUgFU6cU8gxNb|fc_0a8ef892ce91ffed016a59d4f3b4a88194b9da66cf04e17b90","toolName":"read","content":[{"type":"text","text":"enum ClaimState {\n…\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a\n\n[Showing lines 179-233 of 1558. Use :234 to continue]"}],"details":{"truncation":{"content":"    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a","truncated":true,"truncatedBy":"lines","totalLines":1380,"totalBytes":2511,"outputLines":55,"outputBytes":2511,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"enum ClaimState {\n…\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// Why a worker failed — the retry CLASSIFICATION (ADR-0038 Amendment fix 5,\n/// REQ-DISPATCH-FALLBACK-CIRCUIT). The two classes retry differently:\n/// a pre-setup transient keeps the bounded ×3 budget (the D3 semantics);\n/// a seat-blocked failure (poisoned replay / prior subscriber still draining)\n/// trips the GLOBAL circuit breaker and requeues WITHOUT burning budget —\n/// the stream must recover once the wedged seat's writer dies (≤ the 15s\n/// bounded-write deadline), and three fast retries would burn to Terminal\n/// before that (the v0.33 abandonment this explicitly must not regress to).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum FailClass {\n    /// Ordinary transient (broker IPC hiccup, opener not pinned yet, …).\n    Transient,\n    /// The stream's subscriber seat is poisoned or still draining — retrying\n    /// immediately would manufacture another replay victim.\n    SeatBlocked,\n}\n\n/// Classify a worker failure message (the seat-blocked shapes are the two\n/// strings the nethost subscriber discipline emits).\n// [impl->REQ-DISPATCH-FALLBACK-CIRCUIT]\nfn classify_failure(msg: &str) -> FailClass {\n    if msg.contains(\"subscriber busy\")\n        || msg.contains(\"replay halted\")\n        || msg.contains(\"lease canceled\")\n    {\n        FailClass::SeatBlocked\n    } else {\n        FailClass::Transient\n    }\n}\n\n/// Whether an opener-query error is the OLD broker's unknown-verb answer —\n/// the ONLY error class that may fall back to the ring peek (ADR-0038\n/// Amendment fix 1). `send_error` on an unknown kind replies exactly\n/// `unknown command kind \"…\"` (broker.rs forward-compat arm), a stable N-1\n/// wire fact. Everything else (transport timeout, EOF, poison) is a","startLine":176,"lineNumbers":[176,null,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1558,"totalBytes":2511,"outputLines":55,"outputBytes":2511,"shownRange":{"start":179,"end":233},"nextOffset":234}}},"isError":false,"timestamp":1784272116787}}
{"type":"custom_message","customType":"irc:incoming","content":"<irc>\nIncoming IRC message from agent `BrokerCpuRca`:\n\nCorrection/precision: conn137 is almost certainly the long-lived **peer-pump** `Brain::cold_start_pump`, not dispatcher worker. Pump reuses one broker IPC carrier for many locally-initiated request/response streams (sync/update/registry/etc); callers subscribe each opened response stream, but there is no net-stream-unsubscribe and the `Brain.net_cursors` + broker handler `my_stream_subs: Vec` only clear on physical conn EOF. Each finished stream seat remains `Some`, writer blocks forever on `rx.recv`; `retire_stream` (dispatcher inbound only) also explicitly keeps subscriber alive. Thus seats are permanent for lifetime of pump conn, not temporary 15s: 15s applies only failed writes; finished+idle seat never writes/fails. conn137’s first subscriptions at mono 72s and continuing through >6,100s fit that pump lifetime.\n\nAn agent sent this while you were waiting or working. Any active interruptible wait was stopped early so you can read it now.\n\nIf a response is expected, reply with the `irc` tool (`op: \"send\"`, `to: \"BrokerCpuRca\"`) — you may finish your current step first. Nobody replies on your behalf.\n</irc>","display":true,"details":{"id":"1533073dc39c5f4e","from":"BrokerCpuRca","message":"Correction/precision: conn137 is almost certainly the long-lived **peer-pump** `Brain::cold_start_pump`, not dispatcher worker. Pump reuses one broker IPC carrier for many locally-initiated request/response streams (sync/update/registry/etc); callers subscribe each opened response stream, but there is no net-stream-unsubscribe and the `Brain.net_cursors` + broker handler `my_stream_subs: Vec` only clear on physical conn EOF. Each finished stream seat remains `Some`, writer blocks forever on `rx.recv`; `retire_stream` (dispatcher inbound only) also explicitly keeps subscriber alive. Thus seats are permanent for lifetime of pump conn, not temporary 15s: 15s applies only failed writes; finished+idle seat never writes/fails. conn137’s first subscriptions at mono 72s and continuing through >6,100s fit that pump lifetime."},"attribution":"agent","id":"d285eef0","parentId":"b2670015","timestamp":"2026-07-17T07:08:36.825Z"}
{"type":"message","id":"f9211323","parentId":"d285eef0","timestamp":"2026-07-17T07:08:52.501Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing pump subscription behavior**\n\n**Confirming stream retention and resource usage**"},{"type":"toolCall","id":"call_DpVbj5c5EMjlyNqXlZWKbic4|fc_02cfeae63d5600b3016a59d5020ac48193ba698f4e4b9bb443","name":"grep","arguments":{"i":"Tracing pump response subscriptions","pattern":"net_stream_subscribe|request_sync|fetch_update|net_cursors","path":"crates/spt-daemon/src/sync.rs; crates/spt-daemon/src/propagate.rs; crates/spt-daemon/src/pump; crates/spt-daemon/src/brain.rs","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_6ZvfGkjd0mPiljDdO5gnrCe9|fc_02cfeae63d5600b3016a59d5020ae081939135b90fd42d635d","name":"read","arguments":{"i":"Reading sync request lifetime","path":"crates/spt-daemon/src/sync.rs","selector":"90-180"}},{"type":"toolCall","id":"call_fC1zSbNdQwSteEMew9Qyjuc5|fc_02cfeae63d5600b3016a59d5020af0819398d03190bc5ab5a0","name":"read","arguments":{"i":"Reading update request lifetime","path":"crates/spt-daemon/src/propagate.rs","selector":"90-175"}},{"type":"toolCall","id":"call_quYKgqHD8OkNNIxztwriTp68|fc_02cfeae63d5600b3016a59d5020b04819391566e2f3b9b5a15","name":"read","arguments":{"i":"Reading pump brain construction","path":"crates/spt-daemon/src/pump/mod.rs","selector":"520-590"}},{"type":"toolCall","id":"call_8wznDeYtk2gt1IDJ3vupUsoQ|fc_02cfeae63d5600b3016a59d5020b1081939dd75f89fbfd9b9b","name":"read","arguments":{"i":"Reading brain cursor insertion","path":"crates/spt-daemon/src/brain.rs","selector":"760-835,1360-1405"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":7428,"output":566,"cacheRead":115200,"cacheWrite":0,"totalTokens":123194,"reasoningTokens":302,"cost":{"input":0.037140000000000006,"output":0.016980000000000002,"cacheRead":0.0576,"cacheWrite":0,"total":0.11172000000000001}},"stopReason":"toolUse","timestamp":1784272116845,"responseId":"resp_02cfeae63d5600b3016a59d4f587d48193be907d80ff076942","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_02cfeae63d5600b3016a59d4f76f2881938a0fc8d7dcfea882","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdUCPbWE5xWjDL0D7WrPXt7AJgwjgxbWvMSip_LAqnZ8JRtM52KHapZ5tBtE1w0JbKbXJ4vTqv9cJ9f43v8gUWSk_TwNy_VUAzptdjQbcXYRBBmsyWmrMXVFLTC5tke-eMhyx9aggpyNuBjQRNt0d3jDgkANmGsuSIoN9kTuPQp4s3xQqTvmN_p5xmBsYfjvqkMWmmTuxvVWPdWoYwEYs5sfbia8hTtEJh8ma-TL8vZ_S081Ct2oeV2jhvkrttuXTJABYxKeOSgIRFbG8_6k2vuk_cS4v7riKaJo5qnu3g3FVAZVzb9ueeOdJmhs_eJ8AhHHK7UJU1W-6dHbmcspWyywL_SHd1JGq2lWXzUPDNfwMOT581p_Jg4rqx-AJ4MbFCLaZE32P9JIKdYu1EeohKcJRm_BSAiV6d85iJywzWLKvGxuYyvHAgI5Z_17UsrQXJe-RYqE2frwemL5hTtEh2mirhpQ14y-eRr7tTJvaGwfwCum5CsTR0zgykmy3r9TtKRLG1iK_7fxMkTUTIhEqg4rijQSAXXeFFhsd0-1ytLSi7kg4_a7PjnG3TjSGiZsyj40XE8HNtep3i9qxgkAuGLC9leziQDukHCu_XSqutyfOrNy3m0p3okYlBxS19J3qOxtYtTD_tlp4dDCHRGHJRpP2gppcDrjgsn6oPlkMPeaBPdVYJvWsIevcCHhQ_EKpWj6XvyWLfrTFClV3iq8UoctSMxi90hNce2dyhOUNCSwI_A76wKjMcC4pDadn-OsDDUjcfJwzL7ziDGFUA2ayySD_0QwwYR8eqesBZPuRV-_rxONDLRx1wDf_svNbQY-9sLBSdnvDEt_XuzvPuSX2B5RvFuzbg5ePGkdJX54UNpogZzMg58mYfa7urdmbkWpZkkSW19iOMcyQvqVRX27f23p9CO5tFwBw1Y7fqPnzH-kl4D0fGJW0SWkgrpYH_9Eu0-mnOyJ_J-sxYLk1NloDM9YCR0oltycRadchPEDVrhs0oDgGzRj9XSfJZWtHCymdPua4CEQgWOfgYf8he8bDbr-rlVyUvt5oF0zoCJwG-QEwCwMT4kswKfH7RVDre4ruWTAOnIfEc2fb4At_YCvxi4ZiiCP7gndzyXONBs2LZfWbSC7gQWNJOKuqLurAFathEIfr0-lONF_dX1qXrUKIzKWcyjljzk8cUvNlJWXuiq2jRRizLq_1fEyl4chjJHbK4GRh_AO2qizOofIKV2v56hZZbXa2c7BIN4GpupTDtSGgzJtzKhTocFiEwiNEnj8K7nJbdRmtc3wwe5evudb7jFYIJYHkQrdFLausBmtfoSkAiw9sUi8OrYlx6Tt564_NYp5kDz_-E2V-Jzx7G-Luf0EvdI8a8KQp-ZFXmKm0GsFB97C7zJfp2UiA4dXxfliLBvFTH_k1fbno3fE8sQAADrfJNCJ7TN4JVaOhCT2RyEjnTCWQ10WcSFn8-WMWQw-0fgK5ewRr6ZiJ-MeAHhnIxPygtpnbSXSoVG46O3pAXh6oWkyny4GMTMwRL2er8eOSuB4OWQgMmDlBTN8zchFZNcu0LeEXFckHMLVrVNbluDNXiJ2pZ8RuniWJDOmVPgoC2N_swiCQHJ28f_wb967lomsWFe29jDL2AWH-cQbR2eZn24D1cUAsIGZLNPyanA1XRY3gLrp0oqaHH-4STN13jD152OWF5svuTQmqHrB7GJWR-RtrYTzvsVvDLqFHFGNifOKPs7ahcRt1wP0_EiOeyClz-P3hrUIhVI9M4iJAgCjjrw5ad0dz9IXvvGcuvHVjMq1CfPAW0ZbxgNj3AeMRfnKZ3mNelgMv9kTmxlQqqkplW2ilex1im76neSPrkoTbolbT9ugvXdIXrdOAW3PbyWIxjQ6jpQuY3XdVSXLxlBA0wqaRRC91iKOuLEW3MxautzFwh3TZP2TloKrXdtDCk81AmubguDMhJbvhr1l1KqsgDy_CtV-i-2iy3Z5Jk8F8fF-zJvHENWgux3f6NDPnDxVDejMtRo7DjlbCkxbcIPatEfrKYqvQcZYbZ__gggR-fE1JiuefQj6PFsAh28vJM9CzqqeDBVRJhn_pmZwd7gBFSPJf9U7gV0WgiR7NxzQNRRyrSCcdlqmdWGGYy02cGArBLbC7YaaEjn8VwzD_9ziA6iyPZ_64Jo4c11p3JQ12dYb4ss-tIpzO7ykRSFWng78K9Kj4FlNupXKJy2IsilI0ZaLn17Z4PnhJ5Q8oCWsXrJIMMDTrL3EQy8tgOyw7-ycaGnxIOsmI_2tBsiVtMGJXexKHH1KfUdO5jXyYxRamLttHz3zASmvrLUzd0eCVscrq01oeuP_VN_sh1zigeW-bPEr_DMc-09K_sNfax5Ai5JnH0wI2PvUW-b0g1Mp3PvCETnsZHd3qObTgOXqIafKRh2XY3krPfO2ey0YvtNO89GDLvlCYOZy3KDjT0v0Ovfm_D-A_DYuuAI2TXaieOflqWRdc8st4lZszP1nI6SsXLpXRDL8dt4wfXqoObtBUd3SGkd2SJDT7BW--pvpyI1eUBZx3VNUVaeHuz-09Izck5jCv57_FBho51m2DYE1TnErGcvB-OedJTiGMuhoAfwD8fy5h1UikSqMYRGsV3sKdFMa_XGabnflIA1ovqonRNpb_gHcPpBIFzGTKk6PlucN_62j5csJeI4IMz3FJQF0VU3KgMixgiaQ10ZF7GZAwA-A84ji7CA0ZM1qynDSLAFLeNCRUQeLhBoXAMY0ULZr3ORS-wqMgokTty0dEMTs4huEDEgAIH98a55kiNAh8at-2Dc=","summary":[{"type":"summary_text","text":"**Tracing pump subscription behavior**"},{"type":"summary_text","text":"**Confirming stream retention and resource usage**"}]},{"id":"fc_02cfeae63d5600b3016a59d5020ac48193ba698f4e4b9bb443","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing pump response subscriptions\",\"pattern\":\"net_stream_subscribe|request_sync|fetch_update|net_cursors\",\"path\":\"crates/spt-daemon/src/sync.rs; crates/spt-daemon/src/propagate.rs; crates/spt-daemon/src/pump; crates/spt-daemon/src/brain.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_DpVbj5c5EMjlyNqXlZWKbic4","name":"grep"},{"id":"fc_02cfeae63d5600b3016a59d5020ae081939135b90fd42d635d","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading sync request lifetime\",\"path\":\"crates/spt-daemon/src/sync.rs\",\"selector\":\"90-180\"}","call_id":"call_6ZvfGkjd0mPiljDdO5gnrCe9","name":"read"},{"id":"fc_02cfeae63d5600b3016a59d5020af0819398d03190bc5ab5a0","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading update request lifetime\",\"path\":\"crates/spt-daemon/src/propagate.rs\",\"selector\":\"90-175\"}","call_id":"call_fC1zSbNdQwSteEMew9Qyjuc5","name":"read"},{"id":"fc_02cfeae63d5600b3016a59d5020b04819391566e2f3b9b5a15","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump brain construction\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"520-590\"}","call_id":"call_quYKgqHD8OkNNIxztwriTp68","name":"read"},{"id":"fc_02cfeae63d5600b3016a59d5020b1081939dd75f89fbfd9b9b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain cursor insertion\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"760-835,1360-1405\"}","call_id":"call_8wznDeYtk2gt1IDJ3vupUsoQ","name":"read"}]},"duration":15654.093799993396,"ttft":2611.603100001812,"contextSnapshot":{"promptTokens":122628,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_DpVbj5c5EMjlyNqXlZWKbic4|fc_02cfeae63d5600b3016a59d5020ac48193ba698f4e4b9bb443","toolName":"grep","startedAt":"2026-07-17T07:08:52.501Z","args":{"path":"crates/spt-daemon/src/sync.rs; crates/spt-daemon/src/propagate.rs; crates/spt-daemon/src/pump; crates/spt-daemon/src/brain.rs"},"intent":"Tracing pump response subscriptions"},"id":"f683fe0a","parentId":"f9211323","timestamp":"2026-07-17T07:08:52.501Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6ZvfGkjd0mPiljDdO5gnrCe9|fc_02cfeae63d5600b3016a59d5020ae081939135b90fd42d635d","toolName":"read","startedAt":"2026-07-17T07:08:52.501Z","args":{"path":"crates/spt-daemon/src/sync.rs"},"intent":"Reading sync request lifetime"},"id":"852ab9e9","parentId":"f683fe0a","timestamp":"2026-07-17T07:08:52.501Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fC1zSbNdQwSteEMew9Qyjuc5|fc_02cfeae63d5600b3016a59d5020af0819398d03190bc5ab5a0","toolName":"read","startedAt":"2026-07-17T07:08:52.501Z","args":{"path":"crates/spt-daemon/src/propagate.rs"},"intent":"Reading update request lifetime"},"id":"9126347c","parentId":"852ab9e9","timestamp":"2026-07-17T07:08:52.501Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_quYKgqHD8OkNNIxztwriTp68|fc_02cfeae63d5600b3016a59d5020b04819391566e2f3b9b5a15","toolName":"read","startedAt":"2026-07-17T07:08:52.501Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Reading pump brain construction"},"id":"a7983ef2","parentId":"9126347c","timestamp":"2026-07-17T07:08:52.501Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_8wznDeYtk2gt1IDJ3vupUsoQ|fc_02cfeae63d5600b3016a59d5020b1081939dd75f89fbfd9b9b","toolName":"read","startedAt":"2026-07-17T07:08:52.501Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading brain cursor insertion"},"id":"69805aee","parentId":"a7983ef2","timestamp":"2026-07-17T07:08:52.501Z"}
{"type":"message","id":"b10f2f71","parentId":"69805aee","timestamp":"2026-07-17T07:08:52.519Z","message":{"role":"toolResult","toolCallId":"call_8wznDeYtk2gt1IDJ3vupUsoQ|fc_02cfeae63d5600b3016a59d5020b1081939dd75f89fbfd9b9b","toolName":"read","content":[{"type":"text","text":"                KIND_APPLIED => {\n…\n                    return Ok(BrokerEvent::Applied {\n…\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…\n    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetDialed(d) => return Ok(d),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Open a bidi stream on a held connection (D4b). With `op_id` the open is\n    /// exactly-once (a replay is answered with the original stream). NOTE all\n    /// net op ids — dials, opens, sends — share ONE durable namespace; mint\n    /// them from a single durable counter.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_open_stream(\n        &mut self,\n        conn_id: u64,\n        op: Option<MintedOp>,\n    ) -> io::Result<NetStreamOpened> {\n        self.send(\n            KIND_NET_STREAM_OPEN,\n            serde_json::to_value(NetStreamOpenReq {\n                conn_id,\n                op_id: op.map(|o| o.seq),\n                minter: op.map(|o| o.minter),\n            })\n            .expect(\"NetStreamOpenReq serializes\"),\n        )?;\n        let deadline = self.call_deadline();\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetStreamOpened(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Write bytes to a held stream over the broker (D4b). With `op_id` the\n    /// write is exactly-once and this blocks for the `net-sent` ack (returned);\n    /// without one it is a direct fire-and-forget write (`None` returned).\n    /// `finish` closes the send side after the write.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_stream_send(\n        &mut self,\n        stream_id: u64,\n        bytes: &[u8],\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n    }"}],"details":{"displayContent":{"text":"                KIND_APPLIED => {\n…\n                    return Ok(BrokerEvent::Applied {\n…\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…\n    pub fn net_dial_loopback(&mut self) -> io::Result<NetDialed> {\n…\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetDialed(d) => return Ok(d),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Open a bidi stream on a held connection (D4b). With `op_id` the open is\n    /// exactly-once (a replay is answered with the original stream). NOTE all\n    /// net op ids — dials, opens, sends — share ONE durable namespace; mint\n    /// them from a single durable counter.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_open_stream(\n        &mut self,\n        conn_id: u64,\n        op: Option<MintedOp>,\n    ) -> io::Result<NetStreamOpened> {\n        self.send(\n            KIND_NET_STREAM_OPEN,\n            serde_json::to_value(NetStreamOpenReq {\n                conn_id,\n                op_id: op.map(|o| o.seq),\n                minter: op.map(|o| o.minter),\n            })\n            .expect(\"NetStreamOpenReq serializes\"),\n        )?;\n        let deadline = self.call_deadline();\n        loop {\n            match self.read_event_until(deadline)? {\n                BrokerEvent::NetStreamOpened(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Write bytes to a held stream over the broker (D4b). With `op_id` the\n    /// write is exactly-once and this blocks for the `net-sent` ack (returned);\n    /// without one it is a direct fire-and-forget write (`None` returned).\n    /// `finish` closes the send side after the write.\n    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    pub fn net_stream_send(\n        &mut self,\n        stream_id: u64,\n        bytes: &[u8],\n…\n    ) -> io::Result<Option<NetSent>> {\n…\n    }","startLine":755,"lineNumbers":[755,null,758,null,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,null,1356,null,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,null,1408,null,1432]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"}}},"isError":false,"timestamp":1784272132518}}
{"type":"message","id":"3e020d46","parentId":"b10f2f71","timestamp":"2026-07-17T07:08:52.528Z","message":{"role":"toolResult","toolCallId":"call_fC1zSbNdQwSteEMew9Qyjuc5|fc_02cfeae63d5600b3016a59d5020af0819398d03190bc5ab5a0","toolName":"read","content":[{"type":"text","text":"impl OfferedUpdate {\n…\n    fn signature_hex(&self) -> String {\n        match self {\n            OfferedUpdate::Single(s) => s.signature_hex.clone(),\n            OfferedUpdate::Set(s) => s.signature_hex.clone(),\n        }\n    }\n}\n\n/// Serve one update pull from this node's staged release, blocking until the\n/// pull completes, the requester detaches, or the request fails.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against the `roster`\n/// **here**, fail-closed: a non-member peer gets the up-to-date shape and\n/// learns nothing (not even whether a release is staged).\n// [impl->REQ-UPD-1]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-MESH-5]\npub fn serve_update(\n    brain: &mut Brain,\n    stream_id: u64,\n    origin_node: &str,\n    net_from_seq: u64,\n    cache: &ReleaseCache,\n    roster: &RosterStore,\n) -> io::Result<UpdateServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    let mut decoder = UpdDecoder::new();\n    // Set once an Offer goes out; the Fetch must echo the same pull id.\n    let mut offered: Option<String> = None;\n\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        UpdRecord::StatusQuery { upd_id } => {\n                            // Status-only convergence query (M8 decision 19,\n                            // REQ-UPD-6): same trust gate as Query — an\n                            // untrusted origin gets the all-None shape and\n                            // learns nothing (not channel, not versions).\n                            // [impl->REQ-UPD-6]\n                            let trusted = roster.is_member_any(origin_node);\n                            let status = if trusted {\n                                let policy = VerifyPolicy::load_from(\n                                    &spt_store::perch::identity_dir().join(\"release-keys.json\"),\n                                    0,\n                                    0,\n                                );\n                                UpdRecord::Status {\n                                    upd_id,\n                                    channel: Some(policy.pinned_channel),\n                                    staged_version: cache.staged_version(),\n                                    applied_version: cache.applied_version(),\n                                    last_outcome: cache.last_outcome(),\n                                }\n                            } else {\n                                UpdRecord::Status {\n                                    upd_id,\n                                    channel: None,\n                                    staged_version: None,\n                                    applied_version: None,\n                                    last_outcome: None,\n                                }\n                            };\n                            brain.net_stream_send(stream_id, &status.encode_line(), None, true)?;\n                            return Ok(if trusted {\n                                UpdateServeOutcome::StatusServed\n                            } else {\n                                UpdateServeOutcome::Refused\n                            });\n                        }\n                        UpdRecord::Query {\n                            upd_id,\n                            current_version,\n                            channel,\n                        } => {\n                            // The gate: only a roster member in ≥1 subnet is\n                            // answered; everyone else gets the up-to-date\n                            // shape (fail-closed, indistinguishable from\n                            // having nothing).\n                            let trusted = roster.is_member_any(origin_node);\n                            let staged = if trusted { cache.staged_update() } else { None };\n                            // Offer only a release that is actually newer on\n                            // the requester's channel (an honest responder\n                            // never proposes a rollback — the requester's own\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}\n\n[Showing lines 89-178 of 780. Use :179 to continue]"}],"details":{"truncation":{"content":"        match self {\r\n            OfferedUpdate::Single(s) => s.signature_hex.clone(),\r\n            OfferedUpdate::Set(s) => s.signature_hex.clone(),\r\n        }\r\n    }\r\n}\r\n\r\n/// Serve one update pull from this node's staged release, blocking until the\r\n/// pull completes, the requester detaches, or the request fails.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against the `roster`\r\n/// **here**, fail-closed: a non-member peer gets the up-to-date shape and\r\n/// learns nothing (not even whether a release is staged).\r\n// [impl->REQ-UPD-1]\r\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n// [impl->REQ-MESH-5]\r\npub fn serve_update(\r\n    brain: &mut Brain,\r\n    stream_id: u64,\r\n    origin_node: &str,\r\n    net_from_seq: u64,\r\n    cache: &ReleaseCache,\r\n    roster: &RosterStore,\r\n) -> io::Result<UpdateServeOutcome> {\r\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\r\n    let mut decoder = UpdDecoder::new();\r\n    // Set once an Offer goes out; the Fetch must echo the same pull id.\r\n    let mut offered: Option<String> = None;\r\n\r\n    loop {\r\n        match brain.read_event()? {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        UpdRecord::StatusQuery { upd_id } => {\r\n                            // Status-only convergence query (M8 decision 19,\r\n                            // REQ-UPD-6): same trust gate as Query — an\r\n                            // untrusted origin gets the all-None shape and\r\n                            // learns nothing (not channel, not versions).\r\n                            // [impl->REQ-UPD-6]\r\n                            let trusted = roster.is_member_any(origin_node);\r\n                            let status = if trusted {\r\n                                let policy = VerifyPolicy::load_from(\r\n                                    &spt_store::perch::identity_dir().join(\"release-keys.json\"),\r\n                                    0,\r\n                                    0,\r\n                                );\r\n                                UpdRecord::Status {\r\n                                    upd_id,\r\n                                    channel: Some(policy.pinned_channel),\r\n                                    staged_version: cache.staged_version(),\r\n                                    applied_version: cache.applied_version(),\r\n                                    last_outcome: cache.last_outcome(),\r\n                                }\r\n                            } else {\r\n                                UpdRecord::Status {\r\n                                    upd_id,\r\n                                    channel: None,\r\n                                    staged_version: None,\r\n                                    applied_version: None,\r\n                                    last_outcome: None,\r\n                                }\r\n                            };\r\n                            brain.net_stream_send(stream_id, &status.encode_line(), None, true)?;\r\n                            return Ok(if trusted {\r\n                                UpdateServeOutcome::StatusServed\r\n                            } else {\r\n                                UpdateServeOutcome::Refused\r\n                            });\r\n                        }\r\n                        UpdRecord::Query {\r\n                            upd_id,\r\n                            current_version,\r\n                            channel,\r\n                        } => {\r\n                            // The gate: only a roster member in ≥1 subnet is\r\n                            // answered; everyone else gets the up-to-date\r\n                            // shape (fail-closed, indistinguishable from\r\n                            // having nothing).\r\n                            let trusted = roster.is_member_any(origin_node);\r\n                            let staged = if trusted { cache.staged_update() } else { None };\r\n                            // Offer only a release that is actually newer on\r\n                            // the requester's channel (an honest responder\r\n                            // never proposes a rollback — the requester's own\r","truncated":true,"truncatedBy":"lines","totalLines":692,"totalBytes":4465,"outputLines":90,"outputBytes":4465,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl OfferedUpdate {\n…\n    fn signature_hex(&self) -> String {\n        match self {\r\n            OfferedUpdate::Single(s) => s.signature_hex.clone(),\r\n            OfferedUpdate::Set(s) => s.signature_hex.clone(),\r\n        }\r\n    }\r\n}\r\n\r\n/// Serve one update pull from this node's staged release, blocking until the\r\n/// pull completes, the requester detaches, or the request fails.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). The origin is gated against the `roster`\r\n/// **here**, fail-closed: a non-member peer gets the up-to-date shape and\r\n/// learns nothing (not even whether a release is staged).\r\n// [impl->REQ-UPD-1]\r\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n// [impl->REQ-MESH-5]\r\npub fn serve_update(\r\n    brain: &mut Brain,\r\n    stream_id: u64,\r\n    origin_node: &str,\r\n    net_from_seq: u64,\r\n    cache: &ReleaseCache,\r\n    roster: &RosterStore,\r\n) -> io::Result<UpdateServeOutcome> {\r\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\r\n    let mut decoder = UpdDecoder::new();\r\n    // Set once an Offer goes out; the Fetch must echo the same pull id.\r\n    let mut offered: Option<String> = None;\r\n\r\n    loop {\r\n        match brain.read_event()? {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        UpdRecord::StatusQuery { upd_id } => {\r\n                            // Status-only convergence query (M8 decision 19,\r\n                            // REQ-UPD-6): same trust gate as Query — an\r\n                            // untrusted origin gets the all-None shape and\r\n                            // learns nothing (not channel, not versions).\r\n                            // [impl->REQ-UPD-6]\r\n                            let trusted = roster.is_member_any(origin_node);\r\n                            let status = if trusted {\r\n                                let policy = VerifyPolicy::load_from(\r\n                                    &spt_store::perch::identity_dir().join(\"release-keys.json\"),\r\n                                    0,\r\n                                    0,\r\n                                );\r\n                                UpdRecord::Status {\r\n                                    upd_id,\r\n                                    channel: Some(policy.pinned_channel),\r\n                                    staged_version: cache.staged_version(),\r\n                                    applied_version: cache.applied_version(),\r\n                                    last_outcome: cache.last_outcome(),\r\n                                }\r\n                            } else {\r\n                                UpdRecord::Status {\r\n                                    upd_id,\r\n                                    channel: None,\r\n                                    staged_version: None,\r\n                                    applied_version: None,\r\n                                    last_outcome: None,\r\n                                }\r\n                            };\r\n                            brain.net_stream_send(stream_id, &status.encode_line(), None, true)?;\r\n                            return Ok(if trusted {\r\n                                UpdateServeOutcome::StatusServed\r\n                            } else {\r\n                                UpdateServeOutcome::Refused\r\n                            });\r\n                        }\r\n                        UpdRecord::Query {\r\n                            upd_id,\r\n                            current_version,\r\n                            channel,\r\n                        } => {\r\n                            // The gate: only a roster member in ≥1 subnet is\r\n                            // answered; everyone else gets the up-to-date\r\n                            // shape (fail-closed, indistinguishable from\r\n                            // having nothing).\r\n                            let trusted = roster.is_member_any(origin_node);\r\n                            let staged = if trusted { cache.staged_update() } else { None };\r\n                            // Offer only a release that is actually newer on\r\n                            // the requester's channel (an honest responder\r\n                            // never proposes a rollback — the requester's own\r\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}","startLine":80,"lineNumbers":[80,null,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,null,204,null,284,285,286,null,293,294,295]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\propagate.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":780,"totalBytes":4465,"outputLines":90,"outputBytes":4465,"shownRange":{"start":89,"end":178},"nextOffset":179}}},"isError":false,"timestamp":1784272132527}}
{"type":"message","id":"1da1eabb","parentId":"3e020d46","timestamp":"2026-07-17T07:08:52.532Z","message":{"role":"toolResult","toolCallId":"call_6ZvfGkjd0mPiljDdO5gnrCe9|fc_02cfeae63d5600b3016a59d5020ae081939135b90fd42d635d","toolName":"read","content":[{"type":"text","text":"impl SyncPolicy {\n…\n    pub fn load() -> SyncPolicy {\n…\n        SyncPolicy {\n            subnets,\n            roster: RosterStore::load(),\n            vis: VisibilityStore::load(),\n        }\n    }\n\n    /// Whether `branch` may sync with `origin_node` (handshake-proven). The\n    /// fail-closed decision table in the module docs. Used on **both** sides:\n    /// the responder's serve gate, and the requester's ref selection (don't\n    /// ask for what the peer shouldn't get — symmetric scoping).\n    // [impl->REQ-INST-5]\n    // [impl->REQ-INST-13]\n    // [impl->REQ-MESH-5]\n    pub fn allows(&self, branch: &str, origin_node: &str) -> bool {\n        if let Some(id) = branch.strip_prefix(\"a-\") {\n            self.subnets.iter().any(|(s, hide)| {\n                self.roster.is_member(s, origin_node) && self.vis.synced(id, s, *hide)\n            })\n        } else if branch.strip_prefix(\"p-\").is_some() {\n            self.subnets\n                .iter()\n                .any(|(s, _)| self.roster.is_member(s, origin_node))\n        } else {\n            false // machinery refs are never sync-served\n        }\n    }\n}\n\n/// Why a responder-side serve loop returned.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum SyncServeOutcome {\n    /// A bundle was served (`bytes` on the wire, carrying `refs`); `denied`\n    /// lists requested refs the gate refused.\n    Sent {\n        bytes: u64,\n        refs: Vec<String>,\n        denied: Vec<String>,\n    },\n    /// Every allowed ref was already up to date — nothing sent.\n    UpToDate { denied: Vec<String> },\n    /// The serve failed; an `Err` record was sent to the peer.\n    Failed(String),\n    /// The requester closed its send side before a request completed.\n    Detached,\n}\n\n/// What one pull applied, branch by branch.\n#[derive(Debug, Default)]\npub struct SyncPullReport {\n    /// Per-branch apply verdicts (accept/drop/conflict per file).\n    pub applied: Vec<ApplyReport>,\n    /// Refs the responder's gate refused (fail-closed on its side).\n    pub denied: Vec<String>,\n}\n\nimpl SyncPullReport {\n    /// Every `(branch, file)` surfaced as a concurrent conflict by this pull —\n    /// the reconcile wiring's input.\n    pub fn conflicts(&self) -> Vec<(String, String)> {\n        self.applied\n            .iter()\n            .flat_map(|r| {\n                r.conflicted\n                    .iter()\n                    .map(move |f| (r.branch.clone(), f.clone()))\n            })\n            .collect()\n    }\n}\n\n/// Sanitize a wire-supplied sync id into a filesystem-safe scratch-file stem.\nfn safe_stem(sync_id: &str) -> String {\n    sync_id\n        .chars()\n        .map(|c| {\n            if c.is_ascii_alphanumeric() || c == '-' || c == '_' {\n                c\n            } else {\n                '_'\n            }\n        })\n        .collect()\n}\n\n/// Serve one sync pull against this node's context store, blocking until the\n/// pull completes, the requester detaches, or the request fails.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against\n/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull\n/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient\n/// bundle file.\n// [impl->REQ-NET-3]\n\n[Showing lines 89-183 of 724. Use :184 to continue]"}],"details":{"truncation":{"content":"        SyncPolicy {\r\n            subnets,\r\n            roster: RosterStore::load(),\r\n            vis: VisibilityStore::load(),\r\n        }\r\n    }\r\n\r\n    /// Whether `branch` may sync with `origin_node` (handshake-proven). The\r\n    /// fail-closed decision table in the module docs. Used on **both** sides:\r\n    /// the responder's serve gate, and the requester's ref selection (don't\r\n    /// ask for what the peer shouldn't get — symmetric scoping).\r\n    // [impl->REQ-INST-5]\r\n    // [impl->REQ-INST-13]\r\n    // [impl->REQ-MESH-5]\r\n    pub fn allows(&self, branch: &str, origin_node: &str) -> bool {\r\n        if let Some(id) = branch.strip_prefix(\"a-\") {\r\n            self.subnets.iter().any(|(s, hide)| {\r\n                self.roster.is_member(s, origin_node) && self.vis.synced(id, s, *hide)\r\n            })\r\n        } else if branch.strip_prefix(\"p-\").is_some() {\r\n            self.subnets\r\n                .iter()\r\n                .any(|(s, _)| self.roster.is_member(s, origin_node))\r\n        } else {\r\n            false // machinery refs are never sync-served\r\n        }\r\n    }\r\n}\r\n\r\n/// Why a responder-side serve loop returned.\r\n#[derive(Debug, Clone, PartialEq, Eq)]\r\npub enum SyncServeOutcome {\r\n    /// A bundle was served (`bytes` on the wire, carrying `refs`); `denied`\r\n    /// lists requested refs the gate refused.\r\n    Sent {\r\n        bytes: u64,\r\n        refs: Vec<String>,\r\n        denied: Vec<String>,\r\n    },\r\n    /// Every allowed ref was already up to date — nothing sent.\r\n    UpToDate { denied: Vec<String> },\r\n    /// The serve failed; an `Err` record was sent to the peer.\r\n    Failed(String),\r\n    /// The requester closed its send side before a request completed.\r\n    Detached,\r\n}\r\n\r\n/// What one pull applied, branch by branch.\r\n#[derive(Debug, Default)]\r\npub struct SyncPullReport {\r\n    /// Per-branch apply verdicts (accept/drop/conflict per file).\r\n    pub applied: Vec<ApplyReport>,\r\n    /// Refs the responder's gate refused (fail-closed on its side).\r\n    pub denied: Vec<String>,\r\n}\r\n\r\nimpl SyncPullReport {\r\n    /// Every `(branch, file)` surfaced as a concurrent conflict by this pull —\r\n    /// the reconcile wiring's input.\r\n    pub fn conflicts(&self) -> Vec<(String, String)> {\r\n        self.applied\r\n            .iter()\r\n            .flat_map(|r| {\r\n                r.conflicted\r\n                    .iter()\r\n                    .map(move |f| (r.branch.clone(), f.clone()))\r\n            })\r\n            .collect()\r\n    }\r\n}\r\n\r\n/// Sanitize a wire-supplied sync id into a filesystem-safe scratch-file stem.\r\nfn safe_stem(sync_id: &str) -> String {\r\n    sync_id\r\n        .chars()\r\n        .map(|c| {\r\n            if c.is_ascii_alphanumeric() || c == '-' || c == '_' {\r\n                c\r\n            } else {\r\n                '_'\r\n            }\r\n        })\r\n        .collect()\r\n}\r\n\r\n/// Serve one sync pull against this node's context store, blocking until the\r\n/// pull completes, the requester detaches, or the request fails.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against\r\n/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull\r\n/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient\r\n/// bundle file.\r\n// [impl->REQ-NET-3]\r","truncated":true,"truncatedBy":"lines","totalLines":636,"totalBytes":3410,"outputLines":95,"outputBytes":3410,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl SyncPolicy {\n…\n    pub fn load() -> SyncPolicy {\n…\n        SyncPolicy {\r\n            subnets,\r\n            roster: RosterStore::load(),\r\n            vis: VisibilityStore::load(),\r\n        }\r\n    }\r\n\r\n    /// Whether `branch` may sync with `origin_node` (handshake-proven). The\r\n    /// fail-closed decision table in the module docs. Used on **both** sides:\r\n    /// the responder's serve gate, and the requester's ref selection (don't\r\n    /// ask for what the peer shouldn't get — symmetric scoping).\r\n    // [impl->REQ-INST-5]\r\n    // [impl->REQ-INST-13]\r\n    // [impl->REQ-MESH-5]\r\n    pub fn allows(&self, branch: &str, origin_node: &str) -> bool {\r\n        if let Some(id) = branch.strip_prefix(\"a-\") {\r\n            self.subnets.iter().any(|(s, hide)| {\r\n                self.roster.is_member(s, origin_node) && self.vis.synced(id, s, *hide)\r\n            })\r\n        } else if branch.strip_prefix(\"p-\").is_some() {\r\n            self.subnets\r\n                .iter()\r\n                .any(|(s, _)| self.roster.is_member(s, origin_node))\r\n        } else {\r\n            false // machinery refs are never sync-served\r\n        }\r\n    }\r\n}\r\n\r\n/// Why a responder-side serve loop returned.\r\n#[derive(Debug, Clone, PartialEq, Eq)]\r\npub enum SyncServeOutcome {\r\n    /// A bundle was served (`bytes` on the wire, carrying `refs`); `denied`\r\n    /// lists requested refs the gate refused.\r\n    Sent {\r\n        bytes: u64,\r\n        refs: Vec<String>,\r\n        denied: Vec<String>,\r\n    },\r\n    /// Every allowed ref was already up to date — nothing sent.\r\n    UpToDate { denied: Vec<String> },\r\n    /// The serve failed; an `Err` record was sent to the peer.\r\n    Failed(String),\r\n    /// The requester closed its send side before a request completed.\r\n    Detached,\r\n}\r\n\r\n/// What one pull applied, branch by branch.\r\n#[derive(Debug, Default)]\r\npub struct SyncPullReport {\r\n    /// Per-branch apply verdicts (accept/drop/conflict per file).\r\n    pub applied: Vec<ApplyReport>,\r\n    /// Refs the responder's gate refused (fail-closed on its side).\r\n    pub denied: Vec<String>,\r\n}\r\n\r\nimpl SyncPullReport {\r\n    /// Every `(branch, file)` surfaced as a concurrent conflict by this pull —\r\n    /// the reconcile wiring's input.\r\n    pub fn conflicts(&self) -> Vec<(String, String)> {\r\n        self.applied\r\n            .iter()\r\n            .flat_map(|r| {\r\n                r.conflicted\r\n                    .iter()\r\n                    .map(move |f| (r.branch.clone(), f.clone()))\r\n            })\r\n            .collect()\r\n    }\r\n}\r\n\r\n/// Sanitize a wire-supplied sync id into a filesystem-safe scratch-file stem.\r\nfn safe_stem(sync_id: &str) -> String {\r\n    sync_id\r\n        .chars()\r\n        .map(|c| {\r\n            if c.is_ascii_alphanumeric() || c == '-' || c == '_' {\r\n                c\r\n            } else {\r\n                '_'\r\n            }\r\n        })\r\n        .collect()\r\n}\r\n\r\n/// Serve one sync pull against this node's context store, blocking until the\r\n/// pull completes, the requester detaches, or the request fails.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against\r\n/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull\r\n/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient\r\n/// bundle file.\r\n// [impl->REQ-NET-3]\r","startLine":79,"lineNumbers":[79,null,83,null,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\sync.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":724,"totalBytes":3410,"outputLines":95,"outputBytes":3410,"shownRange":{"start":89,"end":183},"nextOffset":184}}},"isError":false,"timestamp":1784272132532}}
{"type":"message","id":"861e2403","parentId":"1da1eabb","timestamp":"2026-07-17T07:08:52.534Z","message":{"role":"toolResult","toolCallId":"call_DpVbj5c5EMjlyNqXlZWKbic4|fc_02cfeae63d5600b3016a59d5020ac48193ba698f4e4b9bb443","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## sync.rs\n 194|) -> io::Result<SyncServeOutcome> {\n*195|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 196|    let mut decoder = SyncDecoder::new();\n 197|\n 198|    loop {\n...\n 332|/// after rides fire-and-forget. A torn pull errors — recovery is a fresh\n*333|/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).\n 334|// [impl->REQ-NET-3]\n*335|pub fn request_sync(\n 336|    brain: &mut Brain,\n 337|    conn_id: u64,\n 338|    refs: &[String],\n...\n 344|    let stream_id = opened.stream_id;\n*345|    brain.net_stream_subscribe(stream_id, 0)?;\n 346|\n 347|    let sync_id = format!(\"pull-{}\", open_op.seq);\n 348|    let mut have_tips = BTreeMap::new();\n## propagate.rs\n 114|) -> io::Result<UpdateServeOutcome> {\n*115|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 116|    let mut decoder = UpdDecoder::new();\n 117|    // Set once an Offer goes out; the Fetch must echo the same pull id.\n 118|    let mut offered: Option<String> = None;\n...\n 355|    let stream_id = opened.stream_id;\n*356|    brain.net_stream_subscribe(stream_id, 0)?;\n 357|\n 358|    let upd_id = format!(\"upd-{}\", open_op.seq);\n 359|    let query = UpdRecord::Query {\n...\n 557|    let stream_id = opened.stream_id;\n*558|    brain.net_stream_subscribe(stream_id, 0)?;\n 559|\n 560|    let upd_id = format!(\"upds-{stream_id}\");\n 561|    let query = UpdRecord::StatusQuery {\n## brain.rs\n 271|    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n*272|    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n 273|    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n 274|    /// Durability across a handoff is the logic layer's job (a re-attaching\n 275|    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n*276|    net_cursors: std::collections::HashMap<u64, u64>,\n 277|    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n*278|    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n 279|    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n 280|    /// `None` until subscribed (or the first event arrives).\n 281|    presence_cursor: Option<u64>,\n...\n 328|            next_seq: 0,\n*329|            net_cursors: std::collections::HashMap::new(),\n 330|            presence_cursor: None,\n 331|            session_cursors: std::collections::HashMap::new(),\n 332|            io_timeout: None,\n...\n 355|            next_seq: 0,\n*356|            net_cursors: std::collections::HashMap::new(),\n 357|            presence_cursor: None,\n 358|            session_cursors: std::collections::HashMap::new(),\n 359|            io_timeout: Some(io_timeout),\n...\n 400|            next_seq: prior.next_seq,\n*401|            net_cursors: std::collections::HashMap::new(),\n 402|            presence_cursor: None,\n 403|            session_cursors,\n 404|            io_timeout: None,\n...\n 813|                    // reject a forward gap (a lost chunk must not pass silently).\n*814|                    let cursor = self.net_cursors.entry(ev.stream_id).or_insert(ev.seq);\n 815|                    if ev.seq < *cursor {\n 816|                        continue; // replay duplicate — dedup\n 817|                    }\n...\n 883|    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n*884|    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n 885|    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n 886|    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n 887|    /// leg loops treat it as noise) — the desync the old blanket-poison feared\n...\n 1589|    // [impl->REQ-DAEMON-2]\n*1590|    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n*1591|        self.net_cursors.insert(stream_id, from_seq);\n 1592|        self.send(\n 1593|            KIND_NET_STREAM_SUBSCRIBE,\n 1594|            serde_json::to_value(NetStreamSubscribeReq {\n...\n 1604|    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {\n*1605|        self.net_cursors.get(&stream_id).copied()\n 1606|    }\n 1607|\n 1608|    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,\n\n## pump/\n### mod.rs\n 21|//!   full semilattice spool per subnet; the receiver's join dedups.\n*22|//! - **sync pull** ([`sync`], [`crate::sync::request_sync`]): anti-entropy for\n 23|//!   the minds. Want-refs are the union of [`crate::sync::select_refs`]\n 24|//!   (locally tracked branches the policy allows toward the peer) and\n 25|//!   **registry-derived** `a-<id>` refs (endpoints known in the shared\n### sync.rs\n 7|//! never held locally), both gated by the same [`crate::sync::SyncPolicy`] the\n*8|//! responder enforces (symmetric scoping) — then pulls via [`request_sync`]\n 9|//! over the shell's open-op counter and reports conflicts through the\n 10|//! construction-captured [`PumpHooks`]. Construction-captured deps (§Frozen-4):\n 11|//! the registry [`Arc`] (snapshot bootstrap refs), the store/scratch/marker\n...\n 21|use crate::registryhost::RegistryHost;\n*22|use crate::sync::{request_sync, select_refs};\n 23|\n 24|use super::{PeerIo, PumpCadence, PumpHooks, PumpPaths, PumpWorker, RoundCtx};\n 25|\n...\n 106|            let open_op = io.open_op()?;\n*107|            let report = request_sync(&mut *io.brain, io.conn_id, &want, open_op, &cs, &self.scratch)?;\n 108|            (self.hooks.on_pull)(peer_hex, &report);\n 109|        }\n 110|        Ok(())"}],"details":{"scopePath":"crates/spt-daemon/src/sync.rs, crates/spt-daemon/src/propagate.rs, crates/spt-daemon/src/pump, crates/spt-daemon/src/brain.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":22,"fileCount":5,"files":["crates/spt-daemon/src/sync.rs","crates/spt-daemon/src/propagate.rs","crates/spt-daemon/src/pump/mod.rs","crates/spt-daemon/src/pump/sync.rs","crates/spt-daemon/src/brain.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/sync.rs","count":4},{"path":"crates/spt-daemon/src/propagate.rs","count":3},{"path":"crates/spt-daemon/src/pump/mod.rs","count":1},{"path":"crates/spt-daemon/src/pump/sync.rs","count":3},{"path":"crates/spt-daemon/src/brain.rs","count":11}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## sync.rs\n 194│) -> io::Result<SyncServeOutcome> {\n*195│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 196│    let mut decoder = SyncDecoder::new();\n 197│\n 198│    loop {\n    │...\n 332│/// after rides fire-and-forget. A torn pull errors — recovery is a fresh\n*333│/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).\n 334│// [impl->REQ-NET-3]\n*335│pub fn request_sync(\n 336│    brain: &mut Brain,\n 337│    conn_id: u64,\n 338│    refs: &[String],\n    │...\n 344│    let stream_id = opened.stream_id;\n*345│    brain.net_stream_subscribe(stream_id, 0)?;\n 346│\n 347│    let sync_id = format!(\"pull-{}\", open_op.seq);\n 348│    let mut have_tips = BTreeMap::new();\n## propagate.rs\n 114│) -> io::Result<UpdateServeOutcome> {\n*115│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 116│    let mut decoder = UpdDecoder::new();\n 117│    // Set once an Offer goes out; the Fetch must echo the same pull id.\n 118│    let mut offered: Option<String> = None;\n    │...\n 355│    let stream_id = opened.stream_id;\n*356│    brain.net_stream_subscribe(stream_id, 0)?;\n 357│\n 358│    let upd_id = format!(\"upd-{}\", open_op.seq);\n 359│    let query = UpdRecord::Query {\n    │...\n 557│    let stream_id = opened.stream_id;\n*558│    brain.net_stream_subscribe(stream_id, 0)?;\n 559│\n 560│    let upd_id = format!(\"upds-{stream_id}\");\n 561│    let query = UpdRecord::StatusQuery {\n## brain.rs\n  271│    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n *272│    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n  273│    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n  274│    /// Durability across a handoff is the logic layer's job (a re-attaching\n  275│    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n *276│    net_cursors: std::collections::HashMap<u64, u64>,\n  277│    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n *278│    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n  279│    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n  280│    /// `None` until subscribed (or the first event arrives).\n  281│    presence_cursor: Option<u64>,\n     │...\n  328│            next_seq: 0,\n *329│            net_cursors: std::collections::HashMap::new(),\n  330│            presence_cursor: None,\n  331│            session_cursors: std::collections::HashMap::new(),\n  332│            io_timeout: None,\n     │...\n  355│            next_seq: 0,\n *356│            net_cursors: std::collections::HashMap::new(),\n  357│            presence_cursor: None,\n  358│            session_cursors: std::collections::HashMap::new(),\n  359│            io_timeout: Some(io_timeout),\n     │...\n  400│            next_seq: prior.next_seq,\n *401│            net_cursors: std::collections::HashMap::new(),\n  402│            presence_cursor: None,\n  403│            session_cursors,\n  404│            io_timeout: None,\n     │...\n  813│                    // reject a forward gap (a lost chunk must not pass silently).\n *814│                    let cursor = self.net_cursors.entry(ev.stream_id).or_insert(ev.seq);\n  815│                    if ev.seq < *cursor {\n  816│                        continue; // replay duplicate — dedup\n  817│                    }\n     │...\n  883│    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n *884│    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n  885│    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n  886│    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n  887│    /// leg loops treat it as noise) — the desync the old blanket-poison feared\n     │...\n 1589│    // [impl->REQ-DAEMON-2]\n*1590│    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n*1591│        self.net_cursors.insert(stream_id, from_seq);\n 1592│        self.send(\n 1593│            KIND_NET_STREAM_SUBSCRIBE,\n 1594│            serde_json::to_value(NetStreamSubscribeReq {\n     │...\n 1604│    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {\n*1605│        self.net_cursors.get(&stream_id).copied()\n 1606│    }\n 1607│\n 1608│    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,\n\n## pump/\n### mod.rs\n 21│//!   full semilattice spool per subnet; the receiver's join dedups.\n*22│//! - **sync pull** ([`sync`], [`crate::sync::request_sync`]): anti-entropy for\n 23│//!   the minds. Want-refs are the union of [`crate::sync::select_refs`]\n 24│//!   (locally tracked branches the policy allows toward the peer) and\n 25│//!   **registry-derived** `a-<id>` refs (endpoints known in the shared\n### sync.rs\n   7│//! never held locally), both gated by the same [`crate::sync::SyncPolicy`] the\n  *8│//! responder enforces (symmetric scoping) — then pulls via [`request_sync`]\n   9│//! over the shell's open-op counter and reports conflicts through the\n  10│//! construction-captured [`PumpHooks`]. Construction-captured deps (§Frozen-4):\n  11│//! the registry [`Arc`] (snapshot bootstrap refs), the store/scratch/marker\n    │...\n  21│use crate::registryhost::RegistryHost;\n *22│use crate::sync::{request_sync, select_refs};\n  23│\n  24│use super::{PeerIo, PumpCadence, PumpHooks, PumpPaths, PumpWorker, RoundCtx};\n  25│\n    │...\n 106│            let open_op = io.open_op()?;\n*107│            let report = request_sync(&mut *io.brain, io.conn_id, &want, open_op, &cs, &self.scratch)?;\n 108│            (self.hooks.on_pull)(peer_hex, &report);\n 109│        }\n 110│        Ok(())"},"isError":false,"timestamp":1784272132534}}
{"type":"message","id":"7e9b8cda","parentId":"861e2403","timestamp":"2026-07-17T07:08:52.549Z","message":{"role":"toolResult","toolCallId":"call_quYKgqHD8OkNNIxztwriTp68|fc_02cfeae63d5600b3016a59d5020b04819391566e2f3b9b5a15","toolName":"read","content":[{"type":"text","text":"pub fn run_peer_pump(\n    broker_name: &str,\n    registry: Arc<RegistryHost>,\n    paths: &PumpPaths,\n    resolver: PeerResolver,\n    config: PumpConfig,\n    hooks: PumpHooks,\n    stop: &AtomicBool,\n) -> io::Result<()> {\n    let PumpConfig {\n        cadence,\n        full_auto_update,\n    } = config;\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    let mut brain = connect(broker_name)?;\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\n    // whose reply-reads consume every event kind off their carrier; sharing one\n    // carrier would make a pull-read swallow a presence frame (advancing the\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\n    // carriers keep the drain and the pulls from swallowing each other by\n    // construction. Tail-follow (no history replay, baseline on first live event)\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\n    let mut events = connect(broker_name)?;\n    events.net_presence_subscribe_tail()?;\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\n    // addresses reconcile into the dial cache BEFORE the first round —\n    // recovery is connection-independent (a node restarted mid-strand heals\n    // here, no operator surgery) — and rows violating `outer key ==\n    // address.id` are repaired from the roster or dropped loudly (the first\n    // post-upgrade load is the migration; the file is never bare-deleted —\n    // valid rows and the id-only fallback behind them stay untouched).\n    // Idempotent, so re-running on every supervised pump restart is free.\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\n    // so the last-good stamps survive a restart; the live counts re-prime as\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\n    let mut health = PumpHealth::load_from(&health_path);\n    health.note_disconnected(0); // no conns exist yet in this pump generation\n    let mut ops = EpochSource::load_from(&paths.ops);\n    // The live per-peer connection cache (updated by presence events) + the\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\n    let mut conns: HashMap<String, u64> = HashMap::new();\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\n    // The four cadenced legs behind the worker seam, in fan-out order\n    // (registry → notif → sync → update). The shell drives them generically;\n    // each leg's *what* lives in its module (§Frozen). Construction capture\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\n    // knob it needs — the sync worker additionally owns the conflict-surface\n    // hooks (ADR-0013).\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\n    let mut workers: [&mut dyn PumpWorker; 4] = [\n        &mut registry_worker,\n        &mut notif_worker,\n        &mut sync_worker,\n        &mut update_worker,\n    ];\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\n    // `workers`. `None` = never run yet → due immediately, so the first tick\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\n    let mut last: [Option<Instant>; 4] = [None; 4];\n    let mut last_hb: Option<Instant> = None;\n\n…\n}\n\n[Showing lines 519-593 of 1982. Use :594 to continue]"}],"details":{"truncation":{"content":"pub fn run_peer_pump(\r\n    broker_name: &str,\r\n    registry: Arc<RegistryHost>,\r\n    paths: &PumpPaths,\r\n    resolver: PeerResolver,\r\n    config: PumpConfig,\r\n    hooks: PumpHooks,\r\n    stop: &AtomicBool,\r\n) -> io::Result<()> {\r\n    let PumpConfig {\r\n        cadence,\r\n        full_auto_update,\r\n    } = config;\r\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\r\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\r\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\r\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\r\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    let mut brain = connect(broker_name)?;\r\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\r\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\r\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\r\n    // whose reply-reads consume every event kind off their carrier; sharing one\r\n    // carrier would make a pull-read swallow a presence frame (advancing the\r\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\r\n    // carriers keep the drain and the pulls from swallowing each other by\r\n    // construction. Tail-follow (no history replay, baseline on first live event)\r\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\r\n    let mut events = connect(broker_name)?;\r\n    events.net_presence_subscribe_tail()?;\r\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\r\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\r\n    // addresses reconcile into the dial cache BEFORE the first round —\r\n    // recovery is connection-independent (a node restarted mid-strand heals\r\n    // here, no operator surgery) — and rows violating `outer key ==\r\n    // address.id` are repaired from the roster or dropped loudly (the first\r\n    // post-upgrade load is the migration; the file is never bare-deleted —\r\n    // valid rows and the id-only fallback behind them stay untouched).\r\n    // Idempotent, so re-running on every supervised pump restart is free.\r\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\r\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\r\n    // so the last-good stamps survive a restart; the live counts re-prime as\r\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let mut health = PumpHealth::load_from(&health_path);\r\n    health.note_disconnected(0); // no conns exist yet in this pump generation\r\n    let mut ops = EpochSource::load_from(&paths.ops);\r\n    // The live per-peer connection cache (updated by presence events) + the\r\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\r\n    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":1464,"totalBytes":4486,"outputLines":75,"outputBytes":4486,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn run_peer_pump(\r\n    broker_name: &str,\r\n    registry: Arc<RegistryHost>,\r\n    paths: &PumpPaths,\r\n    resolver: PeerResolver,\r\n    config: PumpConfig,\r\n    hooks: PumpHooks,\r\n    stop: &AtomicBool,\r\n) -> io::Result<()> {\r\n    let PumpConfig {\r\n        cadence,\r\n        full_auto_update,\r\n    } = config;\r\n    // PUMP MODE: a brain whose carrier is SPLIT at construction (reader thread +\r\n    // channel) so every reply read is bounded by PUMP_PEER_IO_TIMEOUT — a\r\n    // black-holed peer can never wedge this single-threaded loop (the 2.2h\r\n    // hfenduleam stall). A timed-out read escalates to a supervised restart\r\n    // below, never a per-peer retry (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    let mut brain = connect(broker_name)?;\r\n    // The DEDICATED event-drain carrier (PUMP-TRUTH W2, REQ-PUMP-PEER-ISOLATION):\r\n    // a SECOND pump-mode brain that OWNS the presence subscription. It is split\r\n    // from `brain` deliberately — `brain` runs the per-peer `peer_step` pulls\r\n    // whose reply-reads consume every event kind off their carrier; sharing one\r\n    // carrier would make a pull-read swallow a presence frame (advancing the\r\n    // exactly-once cursor while dropping the outcome — the D4c break). Two\r\n    // carriers keep the drain and the pulls from swallowing each other by\r\n    // construction. Tail-follow (no history replay, baseline on first live event)\r\n    // so a long-lived broker's evicted ring can never gap-trip a false restart.\r\n    let mut events = connect(broker_name)?;\r\n    events.net_presence_subscribe_tail()?;\r\n    // Startup reconcile + invariant migration (ADR-0039 Decisions 3+4,\r\n    // REQ-PEER-ROUTE-CHAIN / REQ-PEERADDR-INVARIANT): validated roster\r\n    // addresses reconcile into the dial cache BEFORE the first round —\r\n    // recovery is connection-independent (a node restarted mid-strand heals\r\n    // here, no operator surgery) — and rows violating `outer key ==\r\n    // address.id` are repaired from the roster or dropped loudly (the first\r\n    // post-upgrade load is the migration; the file is never bare-deleted —\r\n    // valid rows and the id-only fallback behind them stay untouched).\r\n    // Idempotent, so re-running on every supervised pump restart is free.\r\n    startup_reconcile_peeraddrs(paths, registry.node_hex());\r\n    // Reachability health (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): loaded\r\n    // so the last-good stamps survive a restart; the live counts re-prime as\r\n    // rounds run. Persisted beside the heartbeat; the status CLI reads it.\r\n    let health_path = PumpHealth::path_beside(&paths.heartbeat);\r\n    let mut health = PumpHealth::load_from(&health_path);\r\n    health.note_disconnected(0); // no conns exist yet in this pump generation\r\n    let mut ops = EpochSource::load_from(&paths.ops);\r\n    // The live per-peer connection cache (updated by presence events) + the\r\n    // per-peer dial schedule (G5 backoff). Both RAM-only, re-primed on restart.\r\n    let mut conns: HashMap<String, u64> = HashMap::new();\r\n    let mut sched: HashMap<String, PeerSched> = HashMap::new();\r\n    // The four cadenced legs behind the worker seam, in fan-out order\r\n    // (registry → notif → sync → update). The shell drives them generically;\r\n    // each leg's *what* lives in its module (§Frozen). Construction capture\r\n    // (§Frozen-4): each worker owns the registry Arc / path slices / config\r\n    // knob it needs — the sync worker additionally owns the conflict-surface\r\n    // hooks (ADR-0013).\r\n    let mut registry_worker = RegistryWorker::new(Arc::clone(&registry), paths, &cadence);\r\n    let mut notif_worker = NotifWorker::new(paths, &cadence);\r\n    let mut sync_worker = SyncWorker::new(Arc::clone(&registry), paths, &cadence, hooks);\r\n    let mut update_worker = UpdateWorker::new(Arc::clone(&registry), paths, &cadence, full_auto_update);\r\n    let mut workers: [&mut dyn PumpWorker; 4] = [\r\n        &mut registry_worker,\r\n        &mut notif_worker,\r\n        &mut sync_worker,\r\n        &mut update_worker,\r\n    ];\r\n    // WorkerLasts (ADR-0018 §V4): RAM-only last-ran instants, parallel to\r\n    // `workers`. `None` = never run yet → due immediately, so the first tick\r\n    // primes every leg. Never `Instant - Duration` — the underflow panic on a\r\n    // sub-uptime monotonic clock (KH 5.9) lives nowhere in this path.\r\n    let mut last: [Option<Instant>; 4] = [None; 4];\r\n    let mut last_hb: Option<Instant> = None;\r\n\r\n…\n}","startLine":519,"lineNumbers":[519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,null,732]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\mod.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1982,"totalBytes":4486,"outputLines":75,"outputBytes":4486,"shownRange":{"start":519,"end":593},"nextOffset":594}}},"isError":false,"timestamp":1784272132549}}
{"type":"message","id":"1ac72f36","parentId":"7e9b8cda","timestamp":"2026-07-17T07:09:08.178Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying pump connection uses cold_start_pump**"},{"type":"toolCall","id":"call_uSM9RzyanzueQipDvc91JP3A|fc_09045e1e9e959fa8016a59d513830c81938855a529ae8839bf","name":"read","arguments":{"i":"Reading pump connection helper","path":"crates/spt-daemon/src/pump/mod.rs","selector":"1110-1165"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":10648,"output":63,"cacheRead":118272,"cacheWrite":0,"totalTokens":128983,"reasoningTokens":19,"cost":{"input":0.05324,"output":0.00189,"cacheRead":0.059135999999999994,"cacheWrite":0,"total":0.114266}},"stopReason":"toolUse","timestamp":1784272132602,"responseId":"resp_09045e1e9e959fa8016a59d50522a881938edbaaa8e25955eb","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_09045e1e9e959fa8016a59d5076e14819389808aa07aceeeb9","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdUT4neq06fCXzSMGBYTUUVl97d6zHUZd1cqfN2pEOZIFoZLTaNxmfQKtBRAgOrmcQ6Ma5hBVRwB0XbbXURNqBajft4twjRlG9jZqbmCefp7cpANabvlfcpGFwFaO8aZywuy0owpbO9efJJGsFV6cciaXYo_eas8rWx1Qmug2Ti3mq5wwGRQfm0bzy5E-lyi-OAe9Ui1zaKYV9xNUTuIkR7nxfLoYEYQo5GXjl8UJkStQYb8apjAqRYnop2V4HLS-KlvW2pxuZ7UxWVFnjjB9MLB2J1Gx2Yxwc2Nl6MbU5PK7uTTQB_KH39pVpT2awTP7wOWjYSiEVg0K8oOKwoJxx4mni8-vjZoePDa_tQcba_4aq98fNL3zXwpS87sozF29_0b2Xb4y2G0eUKvRYK29BfeDSvr9zgReS7zuhircrOHLYVKYCws7vV2jj6OMoKZ7BJKrcdpx9ZVhL_LcKGZGSCXQ1Ov8WZcRsMxIg452uKzXciHj5zf6zoZIboquzJbIZinCJ76uDnh08Sk8pyLLffi_BcMcKgeQzwO-9wwVdqtUkYGbNc951-mOMuAEteUX7-_x9DrrfN1Aon-w1XkdDbWFye0JyhVhPvuxebRlp9kxKY_BrTI_SSUGlmGeWjeno5Y24oAauWOQrrEjncmLwy_qYaQ2PYsX4Rqcs5pRQtrkXHBhffcrMQeKuAF-Llw7VBrYSpObWiloej66dOmfG1KRTLcKZbDPHmMseMmmDGVAGuK0MYyu7DKOEqw3aDlyOebPUnBTdbOCq3ZoQVA_WixohBFyJHtE8I3i9ELATp3CcfOl1Fa1Vvosa8XxJqA4D9KrInw_a1eRQzD8Hp6HIHZkY5xEsSFlTK6vlAUdxyBAMvy9JaoUq2OXJW1PWarh_HjI0gBpCDYg1W8uvfdhqB5BxNsGvJWu209iE9u6F96uCBKqPC5VNmjm2u3bptq4wVdkh6VUeaTVrxziKfyGRfLP90kvcuyTL4on7gCd9sYdws=","summary":[{"type":"summary_text","text":"**Verifying pump connection uses cold_start_pump**"}]},{"id":"fc_09045e1e9e959fa8016a59d513830c81938855a529ae8839bf","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading pump connection helper\",\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"selector\":\"1110-1165\"}","call_id":"call_uSM9RzyanzueQipDvc91JP3A","name":"read"}]},"duration":15574.244800001383,"ttft":2851.798199996352,"contextSnapshot":{"promptTokens":128920,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_uSM9RzyanzueQipDvc91JP3A|fc_09045e1e9e959fa8016a59d513830c81938855a529ae8839bf","toolName":"read","startedAt":"2026-07-17T07:09:08.178Z","args":{"path":"crates/spt-daemon/src/pump/mod.rs"},"intent":"Reading pump connection helper"},"id":"9690b660","parentId":"1ac72f36","timestamp":"2026-07-17T07:09:08.178Z"}
{"type":"message","id":"b6bef2f0","parentId":"9690b660","timestamp":"2026-07-17T07:09:08.198Z","message":{"role":"toolResult","toolCallId":"call_uSM9RzyanzueQipDvc91JP3A|fc_09045e1e9e959fa8016a59d513830c81938855a529ae8839bf","toolName":"read","content":[{"type":"text","text":"/// step; this cannot, so intra-incident ordering is trustworthy.\nfn mono_ms() -> u64 {\n    use std::sync::OnceLock;\n    static BASE: OnceLock<Instant> = OnceLock::new();\n    BASE.get_or_init(Instant::now).elapsed().as_millis() as u64\n}\n\n/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\n/// and finish it (the receiver's dispatcher classifies + applies). One\n/// stream per feed: streams are single-purpose by the demux contract.\nfn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\n    let opened = brain.net_open_stream(conn_id, None)?;\n    brain.net_stream_send(opened.stream_id, feed, None, true)?;\n    Ok(())\n}\n\n/// Connect a fresh brain IPC client in PUMP mode (retrying through broker\n/// startup): the carrier is split at construction with the per-call deadline\n/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the\n/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// Cadence gate (the pure pump kernel): a leg is due when its worker raised a\n/// `wake` edge (forced round NOW, regardless of cadence), OR a never-run leg\n/// (`last == None`, so the first tick primes peers), OR `cadence` has elapsed\n/// since it last ran. **Prime-all-on-restart:** `WorkerLasts` is RAM-only and\n/// re-initializes to `[None; 4]` every daemon start, so after a restart EVERY leg\n/// is `last == None` → due on the first tick — a fresh daemon pulls/advertises\n/// immediately, never waiting out a full cadence (so \"silent since restart\" is\n/// never the priming path; a genuinely quiet leg means no reachable peer / empty\n/// want-set, not an un-primed timer). Uses only forward `duration_since` — NEVER `Instant -\n/// Duration`, which underflow-panics on a monotonic clock younger than the\n/// offset (a freshly-booted Windows host, KNOWN-HAZARDS 5.9). The shell owns\n/// the `last` instants (`WorkerLasts`, RAM only) and the `wake` (the worker's\n/// `poll_wake` marker take) — this kernel stays a pure fn for the unit table.\n/// [impl->REQ-HAZARD-INSTANT-UNDERFLOW]\nfn due(last: Option<Instant>, wake: bool, now: Instant, cadence: Duration) -> bool {\n    wake || last.is_none_or(|t| now.duration_since(t) >= cadence)\n}\n\n/// Write the pump's last-tick heartbeat (epoch ms) — the liveness fact\n/// `spt daemon status` renders and the `subnet status` stall warning reads\n\n[Showing lines 1109-1168 of 1982. Use :1169 to continue]"}],"details":{"truncation":{"content":"/// step; this cannot, so intra-incident ordering is trustworthy.\r\nfn mono_ms() -> u64 {\r\n    use std::sync::OnceLock;\r\n    static BASE: OnceLock<Instant> = OnceLock::new();\r\n    BASE.get_or_init(Instant::now).elapsed().as_millis() as u64\r\n}\r\n\r\n/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\r\n/// and finish it (the receiver's dispatcher classifies + applies). One\r\n/// stream per feed: streams are single-purpose by the demux contract.\r\nfn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\r\n    let opened = brain.net_open_stream(conn_id, None)?;\r\n    brain.net_stream_send(opened.stream_id, feed, None, true)?;\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client in PUMP mode (retrying through broker\r\n/// startup): the carrier is split at construction with the per-call deadline\r\n/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the\r\n/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    std::time::SystemTime::now()\r\n        .duration_since(std::time::UNIX_EPOCH)\r\n        .map(|d| d.as_millis() as u64)\r\n        .unwrap_or(0)\r\n}\r\n\r\n/// Cadence gate (the pure pump kernel): a leg is due when its worker raised a\r\n/// `wake` edge (forced round NOW, regardless of cadence), OR a never-run leg\r\n/// (`last == None`, so the first tick primes peers), OR `cadence` has elapsed\r\n/// since it last ran. **Prime-all-on-restart:** `WorkerLasts` is RAM-only and\r\n/// re-initializes to `[None; 4]` every daemon start, so after a restart EVERY leg\r\n/// is `last == None` → due on the first tick — a fresh daemon pulls/advertises\r\n/// immediately, never waiting out a full cadence (so \"silent since restart\" is\r\n/// never the priming path; a genuinely quiet leg means no reachable peer / empty\r\n/// want-set, not an un-primed timer). Uses only forward `duration_since` — NEVER `Instant -\r\n/// Duration`, which underflow-panics on a monotonic clock younger than the\r\n/// offset (a freshly-booted Windows host, KNOWN-HAZARDS 5.9). The shell owns\r\n/// the `last` instants (`WorkerLasts`, RAM only) and the `wake` (the worker's\r\n/// `poll_wake` marker take) — this kernel stays a pure fn for the unit table.\r\n/// [impl->REQ-HAZARD-INSTANT-UNDERFLOW]\r\nfn due(last: Option<Instant>, wake: bool, now: Instant, cadence: Duration) -> bool {\r\n    wake || last.is_none_or(|t| now.duration_since(t) >= cadence)\r\n}\r\n\r\n/// Write the pump's last-tick heartbeat (epoch ms) — the liveness fact\r\n/// `spt daemon status` renders and the `subnet status` stall warning reads\r","truncated":true,"truncatedBy":"lines","totalLines":874,"totalBytes":2984,"outputLines":60,"outputBytes":2984,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// step; this cannot, so intra-incident ordering is trustworthy.\r\nfn mono_ms() -> u64 {\r\n    use std::sync::OnceLock;\r\n    static BASE: OnceLock<Instant> = OnceLock::new();\r\n    BASE.get_or_init(Instant::now).elapsed().as_millis() as u64\r\n}\r\n\r\n/// Open a fresh stream on `conn_id`, write one whole feed fire-and-forget,\r\n/// and finish it (the receiver's dispatcher classifies + applies). One\r\n/// stream per feed: streams are single-purpose by the demux contract.\r\nfn push_feed(brain: &mut Brain, conn_id: u64, feed: &[u8]) -> io::Result<()> {\r\n    let opened = brain.net_open_stream(conn_id, None)?;\r\n    brain.net_stream_send(opened.stream_id, feed, None, true)?;\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client in PUMP mode (retrying through broker\r\n/// startup): the carrier is split at construction with the per-call deadline\r\n/// armed ([`Brain::cold_start_pump`]), so the pump's reads are bounded from the\r\n/// first tick (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n// [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start_pump(broker_name, now_ms(), PUMP_PEER_IO_TIMEOUT) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    std::time::SystemTime::now()\r\n        .duration_since(std::time::UNIX_EPOCH)\r\n        .map(|d| d.as_millis() as u64)\r\n        .unwrap_or(0)\r\n}\r\n\r\n/// Cadence gate (the pure pump kernel): a leg is due when its worker raised a\r\n/// `wake` edge (forced round NOW, regardless of cadence), OR a never-run leg\r\n/// (`last == None`, so the first tick primes peers), OR `cadence` has elapsed\r\n/// since it last ran. **Prime-all-on-restart:** `WorkerLasts` is RAM-only and\r\n/// re-initializes to `[None; 4]` every daemon start, so after a restart EVERY leg\r\n/// is `last == None` → due on the first tick — a fresh daemon pulls/advertises\r\n/// immediately, never waiting out a full cadence (so \"silent since restart\" is\r\n/// never the priming path; a genuinely quiet leg means no reachable peer / empty\r\n/// want-set, not an un-primed timer). Uses only forward `duration_since` — NEVER `Instant -\r\n/// Duration`, which underflow-panics on a monotonic clock younger than the\r\n/// offset (a freshly-booted Windows host, KNOWN-HAZARDS 5.9). The shell owns\r\n/// the `last` instants (`WorkerLasts`, RAM only) and the `wake` (the worker's\r\n/// `poll_wake` marker take) — this kernel stays a pure fn for the unit table.\r\n/// [impl->REQ-HAZARD-INSTANT-UNDERFLOW]\r\nfn due(last: Option<Instant>, wake: bool, now: Instant, cadence: Duration) -> bool {\r\n    wake || last.is_none_or(|t| now.duration_since(t) >= cadence)\r\n}\r\n\r\n/// Write the pump's last-tick heartbeat (epoch ms) — the liveness fact\r\n/// `spt daemon status` renders and the `subnet status` stall warning reads\r","startLine":1109,"lineNumbers":[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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\pump\\mod.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1982,"totalBytes":2984,"outputLines":60,"outputBytes":2984,"shownRange":{"start":1109,"end":1168},"nextOffset":1169}}},"isError":false,"timestamp":1784272148197}}
{"type":"message","id":"33eb3e44","parentId":"b6bef2f0","timestamp":"2026-07-17T07:09:15.567Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Adding request_sync EOF return handling**"},{"type":"toolCall","id":"call_JKPmylKYkyW7zwkvhElCOCmQ|fc_0a2b06952984995b016a59d51a7c0481958ff9c4b0ddb0e06c","name":"read","arguments":{"i":"Reading requester stream completion","path":"crates/spt-daemon/src/sync.rs","selector":"325-445"}},{"type":"toolCall","id":"call_hCktTYAUdlz0bCa5Rer6PXXf|fc_0a2b06952984995b016a59d51a7c188195bbe1c91d764f11aa","name":"read","arguments":{"i":"Reading update requester completion","path":"crates/spt-daemon/src/propagate.rs","selector":"335-455,540-625"}},{"type":"toolCall","id":"call_ry72QdGS79xzlv5gYmtjCq5z|fc_0a2b06952984995b016a59d51a7c308195ab4dc2d951076ab0","name":"read","arguments":{"i":"Reading subscribe command and cleanup","path":"crates/spt-daemon/src/brain.rs","selector":"1575-1615"}},{"type":"toolCall","id":"call_MBfFL9Ybu7bEIzMjuZyUhopR|fc_0a2b06952984995b016a59d51a7c388195984b50cf3da2abd8","name":"read","arguments":{"i":"Reading stream detach implementation","path":"crates/spt-daemon/src/nethost.rs","selector":"1900-1940"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5390,"output":186,"cacheRead":124416,"cacheWrite":0,"totalTokens":129992,"reasoningTokens":13,"cost":{"input":0.02695,"output":0.00558,"cacheRead":0.062208,"cacheWrite":0,"total":0.094738}},"stopReason":"toolUse","timestamp":1784272148247,"responseId":"resp_0a2b06952984995b016a59d514ec908195a057e209b6d85cea","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0a2b06952984995b016a59d516aeb4819586c539ced3d0b152","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdUaS7y9qWNFQGhXb_tU6fB3JoV-ejQdaQAPbH7ijMEYb2yUQ0Ml5oGo76qHWd60tTmKWGty2V8S4RRup8b3z3e_5quuoRnGLRmTtAwjAjwSypKADDm7N4ET0weV-yxEjRAdEnZcmb4CjrkGAIhseea4LnAgcwcF9VkRtb6NMLGwAWDj4vngQwR6mmyd3laULnsEH8enm3LLKqCMCKGHqWofRas141WI6lItyE_h9ZGyk7xKlnExSpAgVVWic7D9nELhum4jYwwoHhHsX3aMuWUwrrNZTBNsbiG3leNk9Z4MDqQyaqwa1nb_U1XjQgiieRULNcgcxEPABOKN4IvlQVOAs9DXsSXDp4tdN3YePDyT72hQ-HHU2u_IdQz_nxtUWrhNs-Z1lZpsNK2nf4SYVdoyBroNFM9VRZ8i1h0WfToQDospjVdMF0p6HJ8sAojI7IWhy8kQbPVweXD7YCwaeADnZx8Atua2xK7R7Xfn3DVZNsEKpJ4K843TRj-DXemDpUCBfzU3f0UhIBxeUGp35-TSnai4rWePKyXJJj85tO-PYqCKgpd0Hb9xMxQ26Bx8mJVUT8NS1ZGLNLBiC86mXA73ulp6pj1iM6ZJ0obO-2vyth2T38U-lQaoT17b_eXjLeedoXHrRAanPn2dbtvVZ6SeyalU0ozeM84Y9oO5h1llxQD4Ew9XhtFtQfOf11RJyTazCZ06p8e46G9UgoDdLvPNdNx8e32_CBV_Jtiv6IhlebL0OsuTKgGzgNd7h6Y07GScytQNEbVJHt9lyu7oWW-tL0UUXcIqfV_d_L5Q7GfVeWqNE2GG2EkDOQb5UI6Y9mB0JRB5RTATLotcRzTLdbS7h9b5Ihtne4a59dkbn-n2jvQHzOzZsa61h0yFTOJfSk7tj3JT5xvOwl4sVPUTK1YGUB3icyzCsewBHRzgKdFj1vyR5KgH_UdN4o8t8QznSyEDp5DZ7KTDhD9i056erkhnV4XVChPYmimd9nezXDA4y_E=","summary":[{"type":"summary_text","text":"**Adding request_sync EOF return handling**"}]},{"id":"fc_0a2b06952984995b016a59d51a7c0481958ff9c4b0ddb0e06c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading requester stream completion\",\"path\":\"crates/spt-daemon/src/sync.rs\",\"selector\":\"325-445\"}","call_id":"call_JKPmylKYkyW7zwkvhElCOCmQ","name":"read"},{"id":"fc_0a2b06952984995b016a59d51a7c188195bbe1c91d764f11aa","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading update requester completion\",\"path\":\"crates/spt-daemon/src/propagate.rs\",\"selector\":\"335-455,540-625\"}","call_id":"call_hCktTYAUdlz0bCa5Rer6PXXf","name":"read"},{"id":"fc_0a2b06952984995b016a59d51a7c308195ab4dc2d951076ab0","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading subscribe command and cleanup\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"1575-1615\"}","call_id":"call_ry72QdGS79xzlv5gYmtjCq5z","name":"read"},{"id":"fc_0a2b06952984995b016a59d51a7c388195984b50cf3da2abd8","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream detach implementation\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1900-1940\"}","call_id":"call_MBfFL9Ybu7bEIzMjuZyUhopR","name":"read"}]},"duration":7319.581499993801,"ttft":2500.4305000007153,"contextSnapshot":{"promptTokens":129806,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_JKPmylKYkyW7zwkvhElCOCmQ|fc_0a2b06952984995b016a59d51a7c0481958ff9c4b0ddb0e06c","toolName":"read","startedAt":"2026-07-17T07:09:15.567Z","args":{"path":"crates/spt-daemon/src/sync.rs"},"intent":"Reading requester stream completion"},"id":"20c1d058","parentId":"33eb3e44","timestamp":"2026-07-17T07:09:15.567Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hCktTYAUdlz0bCa5Rer6PXXf|fc_0a2b06952984995b016a59d51a7c188195bbe1c91d764f11aa","toolName":"read","startedAt":"2026-07-17T07:09:15.567Z","args":{"path":"crates/spt-daemon/src/propagate.rs"},"intent":"Reading update requester completion"},"id":"c5a444a2","parentId":"20c1d058","timestamp":"2026-07-17T07:09:15.567Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ry72QdGS79xzlv5gYmtjCq5z|fc_0a2b06952984995b016a59d51a7c308195ab4dc2d951076ab0","toolName":"read","startedAt":"2026-07-17T07:09:15.567Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading subscribe command and cleanup"},"id":"a355b1e6","parentId":"c5a444a2","timestamp":"2026-07-17T07:09:15.567Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_MBfFL9Ybu7bEIzMjuZyUhopR|fc_0a2b06952984995b016a59d51a7c388195984b50cf3da2abd8","toolName":"read","startedAt":"2026-07-17T07:09:15.568Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream detach implementation"},"id":"c83f7aaa","parentId":"a355b1e6","timestamp":"2026-07-17T07:09:15.568Z"}
{"type":"message","id":"a09d9595","parentId":"c83f7aaa","timestamp":"2026-07-17T07:09:15.577Z","message":{"role":"toolResult","toolCallId":"call_hCktTYAUdlz0bCa5Rer6PXXf|fc_0a2b06952984995b016a59d51a7c188195bbe1c91d764f11aa","toolName":"read","content":[{"type":"text","text":"/// exactly-once stream open; everything after rides fire-and-forget. A torn\n/// pull errors — recovery is a fresh `request_update` (the query is\n/// idempotent, staging is atomic; FAULT-MATRIX stream-loss row).\n///\n/// The gate runs **twice by design** (REQ-UPD-2: per-node, never inherited):\n/// [`verify_metadata`] on the offer before any bytes move, then the whole\n/// [`plan_verified`] front door (metadata + artifact digest + classify) on\n/// the real bytes before staging.\n// [impl->REQ-UPD-1]\n// [impl->REQ-UPD-2]\npub fn request_update(\n    brain: &mut Brain,\n    conn_id: u64,\n    open_op: MintedOp,\n    running: &BrokerAbi,\n    policy: &VerifyPolicy,\n    cache: &ReleaseCache,\n    scratch_dir: &Path,\n) -> io::Result<UpdatePullOutcome> {\n    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n    let stream_id = opened.stream_id;\n    brain.net_stream_subscribe(stream_id, 0)?;\n\n    let upd_id = format!(\"upd-{}\", open_op.seq);\n    let query = UpdRecord::Query {\n        upd_id: upd_id.clone(),\n        current_version: policy.current_version,\n        channel: policy.pinned_channel.clone(),\n    }\n    .encode_line();\n    brain.net_stream_send(stream_id, &query, None, false)?;\n\n    std::fs::create_dir_all(scratch_dir)?;\n    let artifact_path = scratch_dir.join(format!(\"pull-{}.artifact\", open_op.seq));\n    let mut file: Option<std::fs::File> = None;\n    let mut signed: Option<OfferedUpdate> = None;\n    let mut decoder = UpdDecoder::new();\n\n    // Peer-reply reads use the DECOUPLED reply-read budget + reclassifying read\n    // (REQ-PUMP-DIAL-FASTFAIL): a connect-then-silent peer drops PROMPTLY as an\n    // ORDINARY per-peer failure instead of burning the 30s carrier deadline into\n    // a whole-round poison — THIS reply-read was the round-2 empirical stall root.\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\n    let mut deadline = brain.reply_read_deadline();\n    loop {\n        let ev = brain.read_peer_reply_until(deadline)?;\n        // Re-arm the budget on each chunk on OUR stream: a whole reply-read budget\n        // of NO progress is a wedge, a streaming pull is healthy (REQ-HAZARD-PUMP-IPC-DEADLINE).\n        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n            deadline = brain.reply_read_deadline();\n        }\n        match ev {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        UpdRecord::UpToDate { upd_id: xid } if xid == upd_id => {\n                            brain.net_stream_send(stream_id, &[], None, true)?;\n                            return Ok(UpdatePullOutcome::UpToDate);\n                        }\n                        UpdRecord::Offer {\n                            upd_id: xid,\n                            metadata_json,\n                            signature_hex,\n                        } if xid == upd_id => {\n                            let as_set = SignedUpdateSet {\n                                metadata_json: metadata_json.clone(),\n                                signature_hex: signature_hex.clone(),\n                            };\n                            // The pre-fetch gate: reject a bad offer before a\n                            // single artifact byte moves (REQ-UPD-2,\n                            // REQ-HAZARD-UPDATE-ROLLBACK — a rollback offer\n                            // costs the subnet nothing).\n                            // [impl->REQ-HAZARD-UPDATE-ROLLBACK]\n                            // [impl->REQ-UPD-6] Try the update-set schema\n                            // first; legacy SignedRelease remains supported\n                            // during migration.\n                            match verify_update_set_metadata(&as_set, policy) {\n                                Ok(meta) => {\n                                    if let Err(reason) = meta.artifact_for(current_platform()) {\n                                        brain.net_stream_send(stream_id, &[], None, true)?;\n                                        return Ok(UpdatePullOutcome::Rejected(reason));\n                                    }\n                                    signed = Some(OfferedUpdate::Set(as_set));\n                                    let fetch = UpdRecord::FetchArtifact {\n                                        upd_id: upd_id.clone(),\n                                        platform: current_platform().to_string(),\n                                    }\n                                    .encode_line();\n                                    brain.net_stream_send(stream_id, &fetch, None, true)?;\n                                }\n                                Err(set_reason) => {\n                                    let offer = SignedRelease {\n                                        metadata_json,\n                                        signature_hex,\n                                    };\n                                    if let Err(reason) = verify_metadata(&offer, policy) {\n                                        brain.net_stream_send(stream_id, &[], None, true)?;\n                                        return Ok(UpdatePullOutcome::Rejected(match set_reason {\n                                            RejectReason::Malformed(_) => reason,\n                                            _ => set_reason,\n                                        }));\n                                    }\n                                    signed = Some(OfferedUpdate::Single(offer));\n                                    let fetch = UpdRecord::Fetch {\n                                        upd_id: upd_id.clone(),\n                                    }\n                                    .encode_line();\n                                    brain.net_stream_send(stream_id, &fetch, None, true)?;\n                                }\n                            }\n                        }\n                        UpdRecord::Chunk {\n                            upd_id: xid,\n                            offset,\n                            data_b64,\n                        } if xid == upd_id => {\n                            let chunk = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}\n…\n// [impl->REQ-UPD-6]\n#[derive(Debug, Clone, Default, PartialEq, Eq)]\npub struct UpdateStatusReport {\n    pub channel: Option<String>,\n    pub staged_version: Option<u64>,\n    pub applied_version: Option<u64>,\n    pub last_outcome: Option<String>,\n}\n\n/// Requester side of the status-only query: one `StatusQuery` on a fresh\n/// stream, one `Status` back — no artifact bytes ever move. The stream open\n/// is non-journaled (`op_id: None`): a torn status query is simply re-asked\n/// next poll round — there is nothing exactly-once about reading status.\n/// Used by `xtask debug-converge`'s poll loop, never the public CLI.\n// [impl->REQ-UPD-6]\npub fn request_update_status(brain: &mut Brain, conn_id: u64) -> io::Result<UpdateStatusReport> {\n    let opened = brain.net_open_stream(conn_id, None)?;\n    let stream_id = opened.stream_id;\n    brain.net_stream_subscribe(stream_id, 0)?;\n\n    let upd_id = format!(\"upds-{stream_id}\");\n    let query = UpdRecord::StatusQuery {\n        upd_id: upd_id.clone(),\n    }\n    .encode_line();\n    brain.net_stream_send(stream_id, &query, None, true)?;\n\n    let mut decoder = UpdDecoder::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let UpdRecord::Status {\n                        upd_id: xid,\n                        channel,\n                        staged_version,\n                        applied_version,\n                        last_outcome,\n                    } = rec\n                    {\n                        if xid == upd_id {\n                            return Ok(UpdateStatusReport {\n                                channel,\n                                staged_version,\n                                applied_version,\n                                last_outcome,\n                            });\n                        }\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                return Err(io::Error::other(\n                    \"peer closed without a status (pre-status-wire responder?)\",\n                ));\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One expected node's classification in the convergence table (M8 decision\n/// 19; state names mirror `docs/DEBUG-CONVERGE-PLAN.md` so the pull/apply\n/// vocabulary stays one set).\n// [impl->REQ-UPD-6]\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum ConvergeState {\n    /// The node applied a version ≥ the target — converged.\n    Applied { version: u64 },\n    /// The node is pinned to a different channel — the metadata gate would\n    /// reject the rollout (expected-set bug, or intentional).\n    NotPinned { channel: String },\n    /// The target (or newer) is staged, default-gated, no apply yet.\n    StagedAwaitingConsent { staged: u64 },\n    /// A broker-touching candidate refused while live endpoints are held —\n    /// the operator must quiesce before apply succeeds.\n    BlockedByBrokerResources,\n    /// A typed refusal was the node's last outcome.\n    Rejected { reason: String },\n    /// Answered, but no candidate at the target has landed yet.\n    Pending,\n…\n}"}],"details":{"displayContent":{"text":"/// exactly-once stream open; everything after rides fire-and-forget. A torn\n/// pull errors — recovery is a fresh `request_update` (the query is\n/// idempotent, staging is atomic; FAULT-MATRIX stream-loss row).\n///\n/// The gate runs **twice by design** (REQ-UPD-2: per-node, never inherited):\n/// [`verify_metadata`] on the offer before any bytes move, then the whole\n/// [`plan_verified`] front door (metadata + artifact digest + classify) on\n/// the real bytes before staging.\n// [impl->REQ-UPD-1]\n// [impl->REQ-UPD-2]\npub fn request_update(\n    brain: &mut Brain,\n    conn_id: u64,\n    open_op: MintedOp,\n    running: &BrokerAbi,\n    policy: &VerifyPolicy,\n    cache: &ReleaseCache,\n    scratch_dir: &Path,\n) -> io::Result<UpdatePullOutcome> {\n    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n    let stream_id = opened.stream_id;\n    brain.net_stream_subscribe(stream_id, 0)?;\n\n    let upd_id = format!(\"upd-{}\", open_op.seq);\n    let query = UpdRecord::Query {\n        upd_id: upd_id.clone(),\n        current_version: policy.current_version,\n        channel: policy.pinned_channel.clone(),\n    }\n    .encode_line();\n    brain.net_stream_send(stream_id, &query, None, false)?;\n\n    std::fs::create_dir_all(scratch_dir)?;\n    let artifact_path = scratch_dir.join(format!(\"pull-{}.artifact\", open_op.seq));\n    let mut file: Option<std::fs::File> = None;\n    let mut signed: Option<OfferedUpdate> = None;\n    let mut decoder = UpdDecoder::new();\n\n    // Peer-reply reads use the DECOUPLED reply-read budget + reclassifying read\n    // (REQ-PUMP-DIAL-FASTFAIL): a connect-then-silent peer drops PROMPTLY as an\n    // ORDINARY per-peer failure instead of burning the 30s carrier deadline into\n    // a whole-round poison — THIS reply-read was the round-2 empirical stall root.\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\n    let mut deadline = brain.reply_read_deadline();\n    loop {\n        let ev = brain.read_peer_reply_until(deadline)?;\n        // Re-arm the budget on each chunk on OUR stream: a whole reply-read budget\n        // of NO progress is a wedge, a streaming pull is healthy (REQ-HAZARD-PUMP-IPC-DEADLINE).\n        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n            deadline = brain.reply_read_deadline();\n        }\n        match ev {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        UpdRecord::UpToDate { upd_id: xid } if xid == upd_id => {\n                            brain.net_stream_send(stream_id, &[], None, true)?;\n                            return Ok(UpdatePullOutcome::UpToDate);\n                        }\n                        UpdRecord::Offer {\n                            upd_id: xid,\n                            metadata_json,\n                            signature_hex,\n                        } if xid == upd_id => {\n                            let as_set = SignedUpdateSet {\n                                metadata_json: metadata_json.clone(),\n                                signature_hex: signature_hex.clone(),\n                            };\n                            // The pre-fetch gate: reject a bad offer before a\n                            // single artifact byte moves (REQ-UPD-2,\n                            // REQ-HAZARD-UPDATE-ROLLBACK — a rollback offer\n                            // costs the subnet nothing).\n                            // [impl->REQ-HAZARD-UPDATE-ROLLBACK]\n                            // [impl->REQ-UPD-6] Try the update-set schema\n                            // first; legacy SignedRelease remains supported\n                            // during migration.\n                            match verify_update_set_metadata(&as_set, policy) {\n                                Ok(meta) => {\n                                    if let Err(reason) = meta.artifact_for(current_platform()) {\n                                        brain.net_stream_send(stream_id, &[], None, true)?;\n                                        return Ok(UpdatePullOutcome::Rejected(reason));\n                                    }\n                                    signed = Some(OfferedUpdate::Set(as_set));\n                                    let fetch = UpdRecord::FetchArtifact {\n                                        upd_id: upd_id.clone(),\n                                        platform: current_platform().to_string(),\n                                    }\n                                    .encode_line();\n                                    brain.net_stream_send(stream_id, &fetch, None, true)?;\n                                }\n                                Err(set_reason) => {\n                                    let offer = SignedRelease {\n                                        metadata_json,\n                                        signature_hex,\n                                    };\n                                    if let Err(reason) = verify_metadata(&offer, policy) {\n                                        brain.net_stream_send(stream_id, &[], None, true)?;\n                                        return Ok(UpdatePullOutcome::Rejected(match set_reason {\n                                            RejectReason::Malformed(_) => reason,\n                                            _ => set_reason,\n                                        }));\n                                    }\n                                    signed = Some(OfferedUpdate::Single(offer));\n                                    let fetch = UpdRecord::Fetch {\n                                        upd_id: upd_id.clone(),\n                                    }\n                                    .encode_line();\n                                    brain.net_stream_send(stream_id, &fetch, None, true)?;\n                                }\n                            }\n                        }\n                        UpdRecord::Chunk {\n                            upd_id: xid,\n                            offset,\n                            data_b64,\n                        } if xid == upd_id => {\n                            let chunk = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}\n…\n// [impl->REQ-UPD-6]\n#[derive(Debug, Clone, Default, PartialEq, Eq)]\npub struct UpdateStatusReport {\n    pub channel: Option<String>,\n    pub staged_version: Option<u64>,\n    pub applied_version: Option<u64>,\n    pub last_outcome: Option<String>,\n}\n\n/// Requester side of the status-only query: one `StatusQuery` on a fresh\n/// stream, one `Status` back — no artifact bytes ever move. The stream open\n/// is non-journaled (`op_id: None`): a torn status query is simply re-asked\n/// next poll round — there is nothing exactly-once about reading status.\n/// Used by `xtask debug-converge`'s poll loop, never the public CLI.\n// [impl->REQ-UPD-6]\npub fn request_update_status(brain: &mut Brain, conn_id: u64) -> io::Result<UpdateStatusReport> {\n    let opened = brain.net_open_stream(conn_id, None)?;\n    let stream_id = opened.stream_id;\n    brain.net_stream_subscribe(stream_id, 0)?;\n\n    let upd_id = format!(\"upds-{stream_id}\");\n    let query = UpdRecord::StatusQuery {\n        upd_id: upd_id.clone(),\n    }\n    .encode_line();\n    brain.net_stream_send(stream_id, &query, None, true)?;\n\n    let mut decoder = UpdDecoder::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let UpdRecord::Status {\n                        upd_id: xid,\n                        channel,\n                        staged_version,\n                        applied_version,\n                        last_outcome,\n                    } = rec\n                    {\n                        if xid == upd_id {\n                            return Ok(UpdateStatusReport {\n                                channel,\n                                staged_version,\n                                applied_version,\n                                last_outcome,\n                            });\n                        }\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                return Err(io::Error::other(\n                    \"peer closed without a status (pre-status-wire responder?)\",\n                ));\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One expected node's classification in the convergence table (M8 decision\n/// 19; state names mirror `docs/DEBUG-CONVERGE-PLAN.md` so the pull/apply\n/// vocabulary stays one set).\n// [impl->REQ-UPD-6]\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum ConvergeState {\n    /// The node applied a version ≥ the target — converged.\n    Applied { version: u64 },\n    /// The node is pinned to a different channel — the metadata gate would\n    /// reject the rollout (expected-set bug, or intentional).\n    NotPinned { channel: String },\n    /// The target (or newer) is staged, default-gated, no apply yet.\n    StagedAwaitingConsent { staged: u64 },\n    /// A broker-touching candidate refused while live endpoints are held —\n    /// the operator must quiesce before apply succeeds.\n    BlockedByBrokerResources,\n    /// A typed refusal was the node's last outcome.\n    Rejected { reason: String },\n    /// Answered, but no candidate at the target has landed yet.\n    Pending,\n…\n}","startLine":335,"lineNumbers":[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,null,471,null,523,524,525,null,533,534,535,null,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,null,628]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\propagate.rs"}}},"isError":false,"timestamp":1784272155577}}
{"type":"message","id":"6f83a0d3","parentId":"a09d9595","timestamp":"2026-07-17T07:09:15.587Z","message":{"role":"toolResult","toolCallId":"call_JKPmylKYkyW7zwkvhElCOCmQ|fc_0a2b06952984995b016a59d51a7c0481958ff9c4b0ddb0e06c","toolName":"read","content":[{"type":"text","text":"pub fn select_refs(\n…\n) -> io::Result<Vec<String>> {\n    Ok(cs\n…\n        .into_iter()\n        .filter(|b| policy.allows(b, peer_node))\n        .collect())\n}\n\n/// Requester side: pull `refs` from the peer over `conn_id`, apply each\n/// fetched tip with the vector driver, and report per-branch verdicts.\n/// `open_op` is the durable id for the exactly-once stream open; everything\n/// after rides fire-and-forget. A torn pull errors — recovery is a fresh\n/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).\n// [impl->REQ-NET-3]\npub fn request_sync(\n    brain: &mut Brain,\n    conn_id: u64,\n    refs: &[String],\n    open_op: MintedOp,\n    cs: &ContextStore,\n    scratch_dir: &Path,\n) -> io::Result<SyncPullReport> {\n    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\n    let stream_id = opened.stream_id;\n    brain.net_stream_subscribe(stream_id, 0)?;\n\n    let sync_id = format!(\"pull-{}\", open_op.seq);\n    let mut have_tips = BTreeMap::new();\n    for r in refs {\n        if let Some(tip) = cs.branch_store().tip(r)? {\n            have_tips.insert(r.clone(), tip);\n        }\n    }\n    let line = SyncRecord::Request {\n        sync_id: sync_id.clone(),\n        refs: refs.to_vec(),\n        have_tips,\n    }\n    .encode_line();\n    brain.net_stream_send(stream_id, &line, None, true)?;\n\n    std::fs::create_dir_all(scratch_dir)?;\n    let bundle: PathBuf = scratch_dir.join(format!(\"recv-{}.bundle\", safe_stem(&sync_id)));\n    let mut file = Some(\n        std::fs::OpenOptions::new()\n            .create(true)\n            .truncate(true)\n            .write(true)\n            .open(&bundle)?,\n    );\n    let mut report = SyncPullReport::default();\n    let mut decoder = SyncDecoder::new();\n\n    // Peer-reply reads use the DECOUPLED reply-read budget + reclassifying read\n    // (REQ-PUMP-DIAL-FASTFAIL): the LATENT TWIN of request_update's wedge — a\n    // non-empty want-set against a connect-then-silent peer would burn the 30s\n    // carrier deadline into a whole-round poison. Drop that peer PROMPTLY instead.\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\n    let mut deadline = brain.reply_read_deadline();\n    loop {\n        let ev = brain.read_peer_reply_until(deadline)?;\n        // Re-arm the budget on each chunk that arrives on OUR stream: a whole\n        // reply-read budget of NO progress is a wedge; a streaming pull that keeps\n        // delivering stays healthy however long it runs (REQ-HAZARD-PUMP-IPC-DEADLINE).\n        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\n            deadline = brain.reply_read_deadline();\n        }\n        match ev {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        SyncRecord::Deny {\n                            sync_id: xid, refs, ..\n                        } if xid == sync_id => {\n                            report.denied.extend(refs);\n                        }\n                        SyncRecord::Chunk {\n                            sync_id: xid,\n                            offset,\n                            data_b64,\n                        } if xid == sync_id => {\n                            let chunk = decode_bytes(&data_b64)\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                            let f = file\n                                .as_mut()\n                                .ok_or_else(|| io::Error::other(\"chunk after Done\"))?;\n                            f.seek(SeekFrom::Start(offset))?;\n                            f.write_all(&chunk)?;\n                        }\n                        SyncRecord::Done {\n                            sync_id: xid,\n                            total,\n                            refs,\n                        } if xid == sync_id => {\n                            // Close our handle before git reads the bundle.\n                            if let Some(mut f) = file.take() {\n                                f.flush()?;\n                            }\n                            if total == 0 {\n                                let _ = std::fs::remove_file(&bundle);\n                                return Ok(report); // everything already up to date\n                            }\n                            let got = std::fs::metadata(&bundle)?.len();\n                            if got != total {\n                                let _ = std::fs::remove_file(&bundle);\n                                return Err(io::Error::new(\n                                    io::ErrorKind::UnexpectedEof,\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\n                                ));\n                            }\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\n                            let _ = std::fs::remove_file(&bundle);\n                            for branch in &refs {\n                                if let Some(tip) = tips.get(branch) {\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\n                                }\n                            }\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\n                            // cutover): a sync pull moves `p-*` tips on the RAW\n                            // BranchStore — `apply_fetched`'s adopt leg can even\n                            // CREATE a p-* branch (a membership change) — and\n                            // none of it rides the ContextStore nudge funnel.\n                            // Nudge ONE global refresh per pull that applied any\n                            // p-* branch; the fingerprint gates what actually\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}\n\n[Showing lines 324-448 of 724. Use :449 to continue]"}],"details":{"truncation":{"content":"        .into_iter()\r\n        .filter(|b| policy.allows(b, peer_node))\r\n        .collect())\r\n}\r\n\r\n/// Requester side: pull `refs` from the peer over `conn_id`, apply each\r\n/// fetched tip with the vector driver, and report per-branch verdicts.\r\n/// `open_op` is the durable id for the exactly-once stream open; everything\r\n/// after rides fire-and-forget. A torn pull errors — recovery is a fresh\r\n/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).\r\n// [impl->REQ-NET-3]\r\npub fn request_sync(\r\n    brain: &mut Brain,\r\n    conn_id: u64,\r\n    refs: &[String],\r\n    open_op: MintedOp,\r\n    cs: &ContextStore,\r\n    scratch_dir: &Path,\r\n) -> io::Result<SyncPullReport> {\r\n    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\r\n    let stream_id = opened.stream_id;\r\n    brain.net_stream_subscribe(stream_id, 0)?;\r\n\r\n    let sync_id = format!(\"pull-{}\", open_op.seq);\r\n    let mut have_tips = BTreeMap::new();\r\n    for r in refs {\r\n        if let Some(tip) = cs.branch_store().tip(r)? {\r\n            have_tips.insert(r.clone(), tip);\r\n        }\r\n    }\r\n    let line = SyncRecord::Request {\r\n        sync_id: sync_id.clone(),\r\n        refs: refs.to_vec(),\r\n        have_tips,\r\n    }\r\n    .encode_line();\r\n    brain.net_stream_send(stream_id, &line, None, true)?;\r\n\r\n    std::fs::create_dir_all(scratch_dir)?;\r\n    let bundle: PathBuf = scratch_dir.join(format!(\"recv-{}.bundle\", safe_stem(&sync_id)));\r\n    let mut file = Some(\r\n        std::fs::OpenOptions::new()\r\n            .create(true)\r\n            .truncate(true)\r\n            .write(true)\r\n            .open(&bundle)?,\r\n    );\r\n    let mut report = SyncPullReport::default();\r\n    let mut decoder = SyncDecoder::new();\r\n\r\n    // Peer-reply reads use the DECOUPLED reply-read budget + reclassifying read\r\n    // (REQ-PUMP-DIAL-FASTFAIL): the LATENT TWIN of request_update's wedge — a\r\n    // non-empty want-set against a connect-then-silent peer would burn the 30s\r\n    // carrier deadline into a whole-round poison. Drop that peer PROMPTLY instead.\r\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\r\n    let mut deadline = brain.reply_read_deadline();\r\n    loop {\r\n        let ev = brain.read_peer_reply_until(deadline)?;\r\n        // Re-arm the budget on each chunk that arrives on OUR stream: a whole\r\n        // reply-read budget of NO progress is a wedge; a streaming pull that keeps\r\n        // delivering stays healthy however long it runs (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\r\n            deadline = brain.reply_read_deadline();\r\n        }\r\n        match ev {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        SyncRecord::Deny {\r\n                            sync_id: xid, refs, ..\r\n                        } if xid == sync_id => {\r\n                            report.denied.extend(refs);\r\n                        }\r\n                        SyncRecord::Chunk {\r\n                            sync_id: xid,\r\n                            offset,\r\n                            data_b64,\r\n                        } if xid == sync_id => {\r\n                            let chunk = decode_bytes(&data_b64)\r\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                            let f = file\r\n                                .as_mut()\r\n                                .ok_or_else(|| io::Error::other(\"chunk after Done\"))?;\r\n                            f.seek(SeekFrom::Start(offset))?;\r\n                            f.write_all(&chunk)?;\r\n                        }\r\n                        SyncRecord::Done {\r\n                            sync_id: xid,\r\n                            total,\r\n                            refs,\r\n                        } if xid == sync_id => {\r\n                            // Close our handle before git reads the bundle.\r\n                            if let Some(mut f) = file.take() {\r\n                                f.flush()?;\r\n                            }\r\n                            if total == 0 {\r\n                                let _ = std::fs::remove_file(&bundle);\r\n                                return Ok(report); // everything already up to date\r\n                            }\r\n                            let got = std::fs::metadata(&bundle)?.len();\r\n                            if got != total {\r\n                                let _ = std::fs::remove_file(&bundle);\r\n                                return Err(io::Error::new(\r\n                                    io::ErrorKind::UnexpectedEof,\r\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\r\n                                ));\r\n                            }\r\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            for branch in &refs {\r\n                                if let Some(tip) = tips.get(branch) {\r\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\r\n                                }\r\n                            }\r\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\r\n                            // cutover): a sync pull moves `p-*` tips on the RAW\r\n                            // BranchStore — `apply_fetched`'s adopt leg can even\r\n                            // CREATE a p-* branch (a membership change) — and\r\n                            // none of it rides the ContextStore nudge funnel.\r\n                            // Nudge ONE global refresh per pull that applied any\r\n                            // p-* branch; the fingerprint gates what actually\r","truncated":true,"truncatedBy":"lines","totalLines":401,"totalBytes":5936,"outputLines":125,"outputBytes":5936,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn select_refs(\n…\n) -> io::Result<Vec<String>> {\n    Ok(cs\n…\n        .into_iter()\r\n        .filter(|b| policy.allows(b, peer_node))\r\n        .collect())\r\n}\r\n\r\n/// Requester side: pull `refs` from the peer over `conn_id`, apply each\r\n/// fetched tip with the vector driver, and report per-branch verdicts.\r\n/// `open_op` is the durable id for the exactly-once stream open; everything\r\n/// after rides fire-and-forget. A torn pull errors — recovery is a fresh\r\n/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).\r\n// [impl->REQ-NET-3]\r\npub fn request_sync(\r\n    brain: &mut Brain,\r\n    conn_id: u64,\r\n    refs: &[String],\r\n    open_op: MintedOp,\r\n    cs: &ContextStore,\r\n    scratch_dir: &Path,\r\n) -> io::Result<SyncPullReport> {\r\n    let opened = brain.net_open_stream(conn_id, Some(open_op))?;\r\n    let stream_id = opened.stream_id;\r\n    brain.net_stream_subscribe(stream_id, 0)?;\r\n\r\n    let sync_id = format!(\"pull-{}\", open_op.seq);\r\n    let mut have_tips = BTreeMap::new();\r\n    for r in refs {\r\n        if let Some(tip) = cs.branch_store().tip(r)? {\r\n            have_tips.insert(r.clone(), tip);\r\n        }\r\n    }\r\n    let line = SyncRecord::Request {\r\n        sync_id: sync_id.clone(),\r\n        refs: refs.to_vec(),\r\n        have_tips,\r\n    }\r\n    .encode_line();\r\n    brain.net_stream_send(stream_id, &line, None, true)?;\r\n\r\n    std::fs::create_dir_all(scratch_dir)?;\r\n    let bundle: PathBuf = scratch_dir.join(format!(\"recv-{}.bundle\", safe_stem(&sync_id)));\r\n    let mut file = Some(\r\n        std::fs::OpenOptions::new()\r\n            .create(true)\r\n            .truncate(true)\r\n            .write(true)\r\n            .open(&bundle)?,\r\n    );\r\n    let mut report = SyncPullReport::default();\r\n    let mut decoder = SyncDecoder::new();\r\n\r\n    // Peer-reply reads use the DECOUPLED reply-read budget + reclassifying read\r\n    // (REQ-PUMP-DIAL-FASTFAIL): the LATENT TWIN of request_update's wedge — a\r\n    // non-empty want-set against a connect-then-silent peer would burn the 30s\r\n    // carrier deadline into a whole-round poison. Drop that peer PROMPTLY instead.\r\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\r\n    let mut deadline = brain.reply_read_deadline();\r\n    loop {\r\n        let ev = brain.read_peer_reply_until(deadline)?;\r\n        // Re-arm the budget on each chunk that arrives on OUR stream: a whole\r\n        // reply-read budget of NO progress is a wedge; a streaming pull that keeps\r\n        // delivering stays healthy however long it runs (REQ-HAZARD-PUMP-IPC-DEADLINE).\r\n        if matches!(&ev, BrokerEvent::NetStreamData { stream_id: sid, .. } if *sid == stream_id) {\r\n            deadline = brain.reply_read_deadline();\r\n        }\r\n        match ev {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        SyncRecord::Deny {\r\n                            sync_id: xid, refs, ..\r\n                        } if xid == sync_id => {\r\n                            report.denied.extend(refs);\r\n                        }\r\n                        SyncRecord::Chunk {\r\n                            sync_id: xid,\r\n                            offset,\r\n                            data_b64,\r\n                        } if xid == sync_id => {\r\n                            let chunk = decode_bytes(&data_b64)\r\n                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                            let f = file\r\n                                .as_mut()\r\n                                .ok_or_else(|| io::Error::other(\"chunk after Done\"))?;\r\n                            f.seek(SeekFrom::Start(offset))?;\r\n                            f.write_all(&chunk)?;\r\n                        }\r\n                        SyncRecord::Done {\r\n                            sync_id: xid,\r\n                            total,\r\n                            refs,\r\n                        } if xid == sync_id => {\r\n                            // Close our handle before git reads the bundle.\r\n                            if let Some(mut f) = file.take() {\r\n                                f.flush()?;\r\n                            }\r\n                            if total == 0 {\r\n                                let _ = std::fs::remove_file(&bundle);\r\n                                return Ok(report); // everything already up to date\r\n                            }\r\n                            let got = std::fs::metadata(&bundle)?.len();\r\n                            if got != total {\r\n                                let _ = std::fs::remove_file(&bundle);\r\n                                return Err(io::Error::new(\r\n                                    io::ErrorKind::UnexpectedEof,\r\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\r\n                                ));\r\n                            }\r\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            for branch in &refs {\r\n                                if let Some(tip) = tips.get(branch) {\r\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\r\n                                }\r\n                            }\r\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\r\n                            // cutover): a sync pull moves `p-*` tips on the RAW\r\n                            // BranchStore — `apply_fetched`'s adopt leg can even\r\n                            // CREATE a p-* branch (a membership change) — and\r\n                            // none of it rides the ContextStore nudge funnel.\r\n                            // Nudge ONE global refresh per pull that applied any\r\n                            // p-* branch; the fingerprint gates what actually\r\n…\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}","startLine":316,"lineNumbers":[316,null,320,321,null,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,null,459,null,468,469,470,null,480,481,482]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\sync.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":724,"totalBytes":5936,"outputLines":125,"outputBytes":5936,"shownRange":{"start":324,"end":448},"nextOffset":449}}},"isError":false,"timestamp":1784272155587}}
{"type":"message","id":"82cafbd9","parentId":"6f83a0d3","timestamp":"2026-07-17T07:09:15.600Z","message":{"role":"toolResult","toolCallId":"call_ry72QdGS79xzlv5gYmtjCq5z|fc_0a2b06952984995b016a59d51a7c308195ab4dc2d951076ab0","toolName":"read","content":[{"type":"text","text":"    /// dispatcher's session→endpoint resolution (D9-1).\n    pub fn sessions(&mut self) -> io::Result<SessionsReply> {\n        self.send(KIND_SESSIONS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Sessions(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// (Re)subscribe to a stream's buffered reads from `from_seq`, setting the\n    /// local cursor (gapless, exactly-once resume — the PTY `subscribe`\n    /// discipline on a QUIC stream, D4b).\n    // [impl->REQ-DAEMON-2]\n    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n        self.net_cursors.insert(stream_id, from_seq);\n        self.send(\n            KIND_NET_STREAM_SUBSCRIBE,\n            serde_json::to_value(NetStreamSubscribeReq {\n                stream_id,\n                from_seq,\n            })\n            .expect(\"NetStreamSubscribeReq serializes\"),\n        )\n    }\n\n    /// The next stream-read sequence still needed for `stream_id` (the durable\n    /// state a handed-off brain carries to resubscribe gaplessly).\n    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {\n        self.net_cursors.get(&stream_id).copied()\n    }\n\n    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,\n    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:\n    /// events produced while no brain was attached replay from the cursor.\n    // [impl->REQ-EP-4]\n    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {\n        self.presence_cursor = Some(from_seq);\n        self.send(\n            KIND_NET_PRESENCE_SUBSCRIBE,\n            serde_json::to_value(NetPresenceSubscribeReq { from_seq })\n                .expect(\"NetPresenceSubscribeReq serializes\"),\n        )\n    }\n\n[Showing lines 1574-1618 of 2247. Use :1619 to continue]"}],"details":{"truncation":{"content":"    /// dispatcher's session→endpoint resolution (D9-1).\n    pub fn sessions(&mut self) -> io::Result<SessionsReply> {\n        self.send(KIND_SESSIONS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Sessions(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// (Re)subscribe to a stream's buffered reads from `from_seq`, setting the\n    /// local cursor (gapless, exactly-once resume — the PTY `subscribe`\n    /// discipline on a QUIC stream, D4b).\n    // [impl->REQ-DAEMON-2]\n    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n        self.net_cursors.insert(stream_id, from_seq);\n        self.send(\n            KIND_NET_STREAM_SUBSCRIBE,\n            serde_json::to_value(NetStreamSubscribeReq {\n                stream_id,\n                from_seq,\n            })\n            .expect(\"NetStreamSubscribeReq serializes\"),\n        )\n    }\n\n    /// The next stream-read sequence still needed for `stream_id` (the durable\n    /// state a handed-off brain carries to resubscribe gaplessly).\n    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {\n        self.net_cursors.get(&stream_id).copied()\n    }\n\n    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,\n    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:\n    /// events produced while no brain was attached replay from the cursor.\n    // [impl->REQ-EP-4]\n    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {\n        self.presence_cursor = Some(from_seq);\n        self.send(\n            KIND_NET_PRESENCE_SUBSCRIBE,\n            serde_json::to_value(NetPresenceSubscribeReq { from_seq })\n                .expect(\"NetPresenceSubscribeReq serializes\"),\n        )","truncated":true,"truncatedBy":"lines","totalLines":674,"totalBytes":1935,"outputLines":45,"outputBytes":1935,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    /// dispatcher's session→endpoint resolution (D9-1).\n    pub fn sessions(&mut self) -> io::Result<SessionsReply> {\n        self.send(KIND_SESSIONS, serde_json::Value::Null)?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Sessions(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// (Re)subscribe to a stream's buffered reads from `from_seq`, setting the\n    /// local cursor (gapless, exactly-once resume — the PTY `subscribe`\n    /// discipline on a QUIC stream, D4b).\n    // [impl->REQ-DAEMON-2]\n    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n        self.net_cursors.insert(stream_id, from_seq);\n        self.send(\n            KIND_NET_STREAM_SUBSCRIBE,\n            serde_json::to_value(NetStreamSubscribeReq {\n                stream_id,\n                from_seq,\n            })\n            .expect(\"NetStreamSubscribeReq serializes\"),\n        )\n    }\n\n    /// The next stream-read sequence still needed for `stream_id` (the durable\n    /// state a handed-off brain carries to resubscribe gaplessly).\n    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {\n        self.net_cursors.get(&stream_id).copied()\n    }\n\n    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,\n    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:\n    /// events produced while no brain was attached replay from the cursor.\n    // [impl->REQ-EP-4]\n    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {\n        self.presence_cursor = Some(from_seq);\n        self.send(\n            KIND_NET_PRESENCE_SUBSCRIBE,\n            serde_json::to_value(NetPresenceSubscribeReq { from_seq })\n                .expect(\"NetPresenceSubscribeReq serializes\"),\n        )\n    }","startLine":1574,"lineNumbers":[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]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2247,"totalBytes":1935,"outputLines":45,"outputBytes":1935,"shownRange":{"start":1574,"end":1618},"nextOffset":1619}}},"isError":false,"timestamp":1784272155600}}
{"type":"message","id":"70d4497a","parentId":"82cafbd9","timestamp":"2026-07-17T07:09:15.617Z","message":{"role":"toolResult","toolCallId":"call_MBfFL9Ybu7bEIzMjuZyUhopR|fc_0a2b06952984995b016a59d51a7c388195984b50cf3da2abd8","toolName":"read","content":[{"type":"text","text":"\n    /// A held stream's current undrained ring depth in chunks (test introspection\n    /// for the retentive backpressure bound). `None` ⇒ no such stream.\n    #[cfg(test)]\n    fn ring_len(&self, stream_id: u64) -> Option<usize> {\n        self.shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(|e| e.log.lock().unwrap().ring.len())\n    }\n\n    /// Inbound (peer-initiated) stream rows currently held on `conn_id` — the\n    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just\n    /// minted.\n    fn inbound_rows_on(&self, conn_id: u64) -> HashSet<u64> {\n        self.shared\n            .streams\n            .lock()\n            .unwrap()\n            .iter()\n            .filter(|(_, e)| e.conn_id == conn_id && !e.initiated_locally)\n            .map(|(id, _)| *id)\n            .collect()\n    }\n\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\n    /// drives and the cross-wired peer row the shell side drives. The loopback\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\n    /// write to either send appears on the other's read log). Serialized so the\n    /// before/after diff sees exactly the one peer row this open minted.\n    // [impl->REQ-SHELL-4]\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\n        let _guard = self.loopback_pair_lock.lock().unwrap();\n        let (conn_id, _) = self.dial_loopback();\n        let before = self.inbound_rows_on(conn_id);\n        let owner_stream = self.open_stream(conn_id)?;\n        let shell_stream = self\n            .inbound_rows_on(conn_id)\n            .difference(&before)\n            .copied()\n            .next()\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\n…\n    }\n\n[Showing lines 1899-1943 of 2865. Use :1944 to continue]"}],"details":{"truncation":{"content":"\r\n    /// A held stream's current undrained ring depth in chunks (test introspection\r\n    /// for the retentive backpressure bound). `None` ⇒ no such stream.\r\n    #[cfg(test)]\r\n    fn ring_len(&self, stream_id: u64) -> Option<usize> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(|e| e.log.lock().unwrap().ring.len())\r\n    }\r\n\r\n    /// Inbound (peer-initiated) stream rows currently held on `conn_id` — the\r\n    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just\r\n    /// minted.\r\n    fn inbound_rows_on(&self, conn_id: u64) -> HashSet<u64> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .iter()\r\n            .filter(|(_, e)| e.conn_id == conn_id && !e.initiated_locally)\r\n            .map(|(id, _)| *id)\r\n            .collect()\r\n    }\r\n\r\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\r\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\r\n    /// drives and the cross-wired peer row the shell side drives. The loopback\r\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\r\n    /// write to either send appears on the other's read log). Serialized so the\r\n    /// before/after diff sees exactly the one peer row this open minted.\r\n    // [impl->REQ-SHELL-4]\r\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\r\n        let _guard = self.loopback_pair_lock.lock().unwrap();\r\n        let (conn_id, _) = self.dial_loopback();\r\n        let before = self.inbound_rows_on(conn_id);\r\n        let owner_stream = self.open_stream(conn_id)?;\r\n        let shell_stream = self\r\n            .inbound_rows_on(conn_id)\r\n            .difference(&before)\r\n            .copied()\r\n            .next()\r\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\r","truncated":true,"truncatedBy":"lines","totalLines":967,"totalBytes":1946,"outputLines":45,"outputBytes":1946,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"\r\n    /// A held stream's current undrained ring depth in chunks (test introspection\r\n    /// for the retentive backpressure bound). `None` ⇒ no such stream.\r\n    #[cfg(test)]\r\n    fn ring_len(&self, stream_id: u64) -> Option<usize> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(|e| e.log.lock().unwrap().ring.len())\r\n    }\r\n\r\n    /// Inbound (peer-initiated) stream rows currently held on `conn_id` — the\r\n    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just\r\n    /// minted.\r\n    fn inbound_rows_on(&self, conn_id: u64) -> HashSet<u64> {\r\n        self.shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .iter()\r\n            .filter(|(_, e)| e.conn_id == conn_id && !e.initiated_locally)\r\n            .map(|(id, _)| *id)\r\n            .collect()\r\n    }\r\n\r\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\r\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\r\n    /// drives and the cross-wired peer row the shell side drives. The loopback\r\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\r\n    /// write to either send appears on the other's read log). Serialized so the\r\n    /// before/after diff sees exactly the one peer row this open minted.\r\n    // [impl->REQ-SHELL-4]\r\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\r\n        let _guard = self.loopback_pair_lock.lock().unwrap();\r\n        let (conn_id, _) = self.dial_loopback();\r\n        let before = self.inbound_rows_on(conn_id);\r\n        let owner_stream = self.open_stream(conn_id)?;\r\n        let shell_stream = self\r\n            .inbound_rows_on(conn_id)\r\n            .difference(&before)\r\n            .copied()\r\n            .next()\r\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\r\n…\n    }","startLine":1899,"lineNumbers":[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,null,1945]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":1946,"outputLines":45,"outputBytes":1946,"shownRange":{"start":1899,"end":1943},"nextOffset":1944}}},"isError":false,"timestamp":1784272155617}}
{"type":"message","id":"1072f0d3","parentId":"70d4497a","timestamp":"2026-07-17T07:09:29.418Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Identifying implementation detachment point**"},{"type":"toolCall","id":"call_FjxMx2t8vdXTOgjUhyDdFxYS|fc_0de4612daca33544016a59d521387c819796c55d6dfd7cc2e6","name":"grep","arguments":{"i":"Locating detach stream subscription","pattern":"fn detach_stream_sub","path":"crates/spt-daemon/src/nethost.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8735,"output":75,"cacheRead":125440,"cacheWrite":0,"totalTokens":134250,"reasoningTokens":13,"cost":{"input":0.043675000000000005,"output":0.0022500000000000003,"cacheRead":0.06272,"cacheWrite":0,"total":0.108645}},"stopReason":"toolUse","timestamp":1784272155659,"responseId":"resp_0de4612daca33544016a59d51c07e081979ec43910e60e2e42","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0de4612daca33544016a59d51e78e081979d4d162412f29d53","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdUhr_G9ECDIZmEJpGoid0AwFJB5Q_4SikuCryPROoNuHJw3bfOyekWdO7BR4vghgKJFomrTOMbgDe-oTKONm0S-AAAuZ6BX9_ImZuJFW7ybfoZcaeBG6HrSS53RNHb9a-C5OicYgOCdSntYa2By3sDExksJKfqNAGSTMpOWXRr4V69JUZuPjPVJDLbncZ3R2-OV3chmorAIKb0mtVB1d6Qp3_9Xgi25p8Tz_iG3jKam7Yv3Kc7o4mhC6ybXqhnD_S6iYYAXrh2A1Ju0nPysqGHHM-cKty37-4cvYWCMOyQpau2lQDaMeGmsVKu3xAqjeKb71gfWlshyHhccolhpWPvMUQ_GNOcUQgz84B3SiWKYJF6pjyTOoy4QqIi8A25mw2YNAoOSHMxxCoRFDqt8nKDYy1bJv1EzT8k6l93RPywY5mlV0FXrjHrUxdx9XYjrxAdjGRxghd5PqhZE-3VqBU3u_tolCywPvvBUVKBpIuevv0ctRPQpspmN_1Z_aXugw6tGyukLMuVt_gdYpAElS68PrAXcbqIQdudC4H_8F6Y5jxfAKKBGm1h5PVJUYq3uEwPGXW_YD1_3u7f5YDaYjwbzd1nXCmSosNB-AQ9-tmpXQqicRPePiDcAp1KbJTr9JfyjOYQJGcIaGiVKcu1xvAmku9PmuaD1G-RZsZuHpuSEc0_j20wKNmhcCSnB7im6au4QvBllTXU9NRqfeN02cY-JPN4eZGcQNlsdVmtOuI59HiPgdV2ikhmpeCKJ44QRY4vC01qvLn5V1gWepp4j6M-bpu3qVJX7tbIAb00uQOS62e62lRHXXMndrTB9dZGEb111-gHo63VLy6Bpc5_m-EqvwHnajwNwnGSc6DwRU6NZNPBn-DZMtiBPaL3P3ebGcSnebPEjCrgLFeGMszTRUWkdZkclneqr7-sKx4_2A7bWKKBgLFZ_MNefby2g2bmk4pwJouVXHCcx8H2Q3hQwgdwPuqHaYa1IhNVinsVdWtYfjSo=","summary":[{"type":"summary_text","text":"**Identifying implementation detachment point**"}]},{"id":"fc_0de4612daca33544016a59d521387c819796c55d6dfd7cc2e6","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating detach stream subscription\",\"pattern\":\"fn detach_stream_sub\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_FjxMx2t8vdXTOgjUhyDdFxYS","name":"grep"}]},"duration":13758.265300005674,"ttft":2906.7274000048637,"contextSnapshot":{"promptTokens":134175,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_FjxMx2t8vdXTOgjUhyDdFxYS|fc_0de4612daca33544016a59d521387c819796c55d6dfd7cc2e6","toolName":"grep","startedAt":"2026-07-17T07:09:29.418Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Locating detach stream subscription"},"id":"55f7071d","parentId":"1072f0d3","timestamp":"2026-07-17T07:09:29.418Z"}
{"type":"message","id":"7c91a10f","parentId":"55f7071d","timestamp":"2026-07-17T07:09:29.420Z","message":{"role":"toolResult","toolCallId":"call_FjxMx2t8vdXTOgjUhyDdFxYS|fc_0de4612daca33544016a59d521387c819796c55d6dfd7cc2e6","toolName":"grep","content":[{"type":"text","text":" 1980|    /// disconnect cleanup; the stream + its ring persist).\n*1981|    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1982|        let log = {\n 1983|            let streams = self.shared.streams.lock().unwrap();\n 1984|            streams.get(&stream_id).map(|e| Arc::clone(&e.log))"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":1,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":1}],"truncated":false,"displayContent":" 1980│    /// disconnect cleanup; the stream + its ring persist).\n*1981│    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n 1982│        let log = {\n 1983│            let streams = self.shared.streams.lock().unwrap();\n 1984│            streams.get(&stream_id).map(|e| Arc::clone(&e.log))"},"isError":false,"timestamp":1784272169419}}
{"type":"message","id":"8d6e91ae","parentId":"7c91a10f","timestamp":"2026-07-17T07:09:34.535Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_K4z9lvcKXs5Hu6iPEXaYOoP6|fc_0e9a98c3d4c7338a016a59d52c95d0819089c659a2e26f7bfd","name":"read","arguments":{"i":"Reading detach stream subscription","path":"crates/spt-daemon/src/nethost.rs","selector":"1968-1995"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4816,"output":42,"cacheRead":129536,"cacheWrite":0,"totalTokens":134394,"cost":{"input":0.02408,"output":0.00126,"cacheRead":0.06476799999999999,"cacheWrite":0,"total":0.090108}},"stopReason":"toolUse","timestamp":1784272169421,"responseId":"resp_0e9a98c3d4c7338a016a59d52b0b648190ae44b8fde596ae4f","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0e9a98c3d4c7338a016a59d52c95d0819089c659a2e26f7bfd","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading detach stream subscription\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1968-1995\"}","call_id":"call_K4z9lvcKXs5Hu6iPEXaYOoP6","name":"read"}]},"duration":5112.963499993086,"ttft":3345.182300001383,"contextSnapshot":{"promptTokens":134352,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_K4z9lvcKXs5Hu6iPEXaYOoP6|fc_0e9a98c3d4c7338a016a59d52c95d0819089c659a2e26f7bfd","toolName":"read","startedAt":"2026-07-17T07:09:34.535Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading detach stream subscription"},"id":"aab109b0","parentId":"8d6e91ae","timestamp":"2026-07-17T07:09:34.535Z"}
{"type":"message","id":"e225b411","parentId":"aab109b0","timestamp":"2026-07-17T07:09:34.552Z","message":{"role":"toolResult","toolCallId":"call_K4z9lvcKXs5Hu6iPEXaYOoP6|fc_0e9a98c3d4c7338a016a59d52c95d0819089c659a2e26f7bfd","toolName":"read","content":[{"type":"text","text":"    pub(crate) fn subscribe_stream(\n…\n    ) -> io::Result<()> {\n…\n        // section (ADR-0038 Amendment fix 3, gate follow-up): the snapshot\n        // handoff is a memory move; the seat's writer thread performs every\n        // conn write outside the mutex, replay first, live after — so a\n        // producer appending concurrently can never slot a live frame into\n        // the middle of the replay (the \"got seq 134 want 6\" re-serve red).\n        // A replay write failure poisons the seat writer-side; the next\n        // producer enqueue halt-and-removes it (the T1 discipline).\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\n        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\n        Ok(())\n    }\n\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\n    /// disconnect cleanup; the stream + its ring persist).\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n        };\n        if let Some(log) = log {\n            log.lock().unwrap().detach_if(sub);\n        }\n    }\n\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\n    // [impl->REQ-EP-4]\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n        // Same atomic install-with-replay discipline as subscribe_stream\n        // (the PresenceLog mirror): the seat's writer drains the replay\n        // before any live event, conn I/O outside the mutex. A replay write\n        // failure poisons the seat; the next push halt-and-removes it — the\n…\n    }\n\n[Showing lines 1967-1998 of 2865. Use :1999 to continue]"}],"details":{"truncation":{"content":"        // section (ADR-0038 Amendment fix 3, gate follow-up): the snapshot\r\n        // handoff is a memory move; the seat's writer thread performs every\r\n        // conn write outside the mutex, replay first, live after — so a\r\n        // producer appending concurrently can never slot a live frame into\r\n        // the middle of the replay (the \"got seq 134 want 6\" re-serve red).\r\n        // A replay write failure poisons the seat writer-side; the next\r\n        // producer enqueue halt-and-removes it (the T1 discipline).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\r\n        Ok(())\r\n    }\r\n\r\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\r\n    /// disconnect cleanup; the stream + its ring persist).\r\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\r\n        };\r\n        if let Some(log) = log {\r\n            log.lock().unwrap().detach_if(sub);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\r\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\r\n    // [impl->REQ-EP-4]\r\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\r\n        // Same atomic install-with-replay discipline as subscribe_stream\r\n        // (the PresenceLog mirror): the seat's writer drains the replay\r\n        // before any live event, conn I/O outside the mutex. A replay write\r\n        // failure poisons the seat; the next push halt-and-removes it — the\r","truncated":true,"truncatedBy":"lines","totalLines":899,"totalBytes":1739,"outputLines":32,"outputBytes":1739,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub(crate) fn subscribe_stream(\n…\n    ) -> io::Result<()> {\n…\n        // section (ADR-0038 Amendment fix 3, gate follow-up): the snapshot\r\n        // handoff is a memory move; the seat's writer thread performs every\r\n        // conn write outside the mutex, replay first, live after — so a\r\n        // producer appending concurrently can never slot a live frame into\r\n        // the middle of the replay (the \"got seq 134 want 6\" re-serve red).\r\n        // A replay write failure poisons the seat writer-side; the next\r\n        // producer enqueue halt-and-removes it (the T1 discipline).\r\n        // [impl->REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE]\r\n        log.lock().unwrap().begin_attach(Arc::clone(&sub), from_seq)?;\r\n        Ok(())\r\n    }\r\n\r\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\r\n    /// disconnect cleanup; the stream + its ring persist).\r\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\r\n        };\r\n        if let Some(log) = log {\r\n            log.lock().unwrap().detach_if(sub);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\r\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\r\n    // [impl->REQ-EP-4]\r\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\r\n        // Same atomic install-with-replay discipline as subscribe_stream\r\n        // (the PresenceLog mirror): the seat's writer drains the replay\r\n        // before any live event, conn I/O outside the mutex. A replay write\r\n        // failure poisons the seat; the next push halt-and-removes it — the\r\n…\n    }","startLine":1950,"lineNumbers":[1950,null,1955,null,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,null,2007]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2865,"totalBytes":1739,"outputLines":32,"outputBytes":1739,"shownRange":{"start":1967,"end":1998},"nextOffset":1999}}},"isError":false,"timestamp":1784272174552}}
{"type":"message","id":"97dd6396","parentId":"e225b411","timestamp":"2026-07-17T07:09:46.922Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying seat thread scalability**\n\n**Assessing thread cleanup on refresh**"},{"type":"toolCall","id":"call_dnVMNTRj7eE48p6XmNFP11tT|fc_053bcf48796b1f2c016a59d539f9108195a2c87e7577b05715","name":"grep","arguments":{"i":"Checking net unsubscribe protocol","pattern":"NET_STREAM_UNSUB|net_stream_unsub|KIND_NET_STREAM_UNSUB","path":"crates","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_i8aCDu6abOzkH0TnMAne7FWX|fc_053bcf48796b1f2c016a59d539f92481958e6aeb1120bb73ef","name":"grep","arguments":{"i":"Checking generic unsubscribe protocol","pattern":"pub fn unsubscribe|KIND_UNSUBSCRIBE|UnsubscribeReq","path":"crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/msg.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5329,"output":518,"cacheRead":129536,"cacheWrite":0,"totalTokens":135383,"reasoningTokens":356,"cost":{"input":0.026645000000000002,"output":0.01554,"cacheRead":0.06476799999999999,"cacheWrite":0,"total":0.10695299999999999}},"stopReason":"toolUse","timestamp":1784272174602,"responseId":"resp_053bcf48796b1f2c016a59d52efba4819581d1ea06cd493e32","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_053bcf48796b1f2c016a59d5306a048195bbf5f963df91a82a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdU5vUT5_rkpp32yM6s7elVenozHt9GdoLLZJUzB6W8ftMMu7hlSlBXOOis0BfaH2Bsk-_CN2i1A7XFrPdI-gB-kFbr0u4a7Rv9rgvTxjeBaOA9xDXxXke2B9eYms_8ZWY9QU64BMo38_E2pclMDkRuyop2-DVMHxOBMJVKdUTW3SG33aRmezXO3f4ATkHOT0AlFwrHyh72bOdjgZbWSX3zBLd6Vbm9uHbeiBdhzVKbJTXM5OSh_5ff2PK0-AWpp9wyevLyvK0rwTZwlFY3kPUynPzUmgLX9KjmuRCUYQYs_p5PF2JL5WrkZ48pne2CRIwVu7-QtA1C520umboEXf7Xi7LeXIge2Ffe3TjpIU_2UJgU0v2x0yFBcwnWdPwf-2qST63dL5mfyUQOUR5hy6c42KjBr3XHchfWmuuDp_vE4FEkAKrOZa4gPLQX4_B9-xDWQqTaFwbtM7rYXu7sp5ujelzuvITCa9O5KkOdSbcfUS-OvKYNOYcrPhrHb2WPfJwzAh2jlZLteBDsdqiQ8guX6i62zwoygTh3mKzN5y9PMPuZ8HjEjWPoutOV-_mXqC9rQmzNfkAg0ORFvIOIx6rjS9jzqa2coN9TXmYHm6E8GkwqdSj581HojcfiuGucDM0bMejpXM08QhhL8-AkB3ICMcnXJbjxu1gahRaGoW-rQcdGz6jpYddonMNkfKccfWbH2MYWrNSFip65LU9tkCTpv_aMkUKaezqsN8pL3fKtIw0sBXMo-f9Os818Ot89DHdTDVQira6fXlNyeCpbWfPPPlU0Tq-YTl3iNxtJteIEBSgX1i4L9nwUC40wP5UcwquN2Mao4fy23cldKagjS9u0VsuBkQInpJo3SNRzFW4mZ_nPtBoizoISSScHT5UsA5Ps3JfCwiW_bZlx6JLYx0vocgS-dP-wPR92m_MnyTfs_lYUlazmMUBsF0mVZfsoVx2ShU7Ccafjz13D423JSPY_fCCm-REjrUX5Hnh3vt1BiLf5NuKQvrKLBpoOg4zXCMdfAilpvBL52q3NOfwLP09AwbHC3Llpcey8Q9KU9nZhCb1okwK_ht0qoqSRKW8mVkUlNjJaAQseYaC7lQrG5vgWqOZLNBWBgUQg7dYCuBL-twPw6NQQHLA1_WL67q05psWc4wGcJt9vS4nTe80Apujrx4Wy1OGYTmCEiByaAnm7igCRBx48-ZUMpj4bvl9UdP1B9aLdD_VbvuewkkbTArzLKvT8AIkrwNwUOFyELpWdOvT3WTiMjERKVTFsy8w2SaXF7GrHpv4EF2h8Ktiu3ATH2LM2o15EQmrG_PlCIG3lquCt2WsBh6xIIzqXC1UhNBxOV9hthebljSy03GOculKjD0OTAxrZAa4Fd_Ii_hpQCEHu5pfxWd05jPV_UJ27GoeR6RSM3Ppz_RenfO5xT2-rHLYvaLYynJQ4-aFPYbrzXPcHWtv63dpaYqDYl_MYqOBUNEBDMPw6m_gtOoxQ-Ev2LvRKsoO2nfBOQXS9O2gnMKeUQ9kpJ_FxMSGPi5KnbzA_iT3xxMq5pfvaF5NpPpDlSVyQWT5hv2sEvPnyz-j_7QnNKy0Y_yhrHAVIHQgOK8o1HOHTCHLsx27odtrBLpvG1bMs2vjHJuZTcZ_73TxVbyANAxnLI7KLx5RaCtWaKkKk7M4aYJCckhHkZn99Iicznv5uUhWArn8wdeqkoQCVswP2je_bhb-WRdqfvwr7YUHzK5q-aPaY7zn1rIUpXUcmx6jAWL9wREl-oTWkN0LpXOiFLR6MgNqQHHW74sJ4mkB00W8P3JtvYxVhXkDqfq6lCp3oQ9tIhxnXGZf2l4Rb5xmk28fvHzIIO-3xMDpiwGfUU0Hhefosra7KJAO3DCOHOIF-bZTODp0OjkkLSfO-2zcqsI8f3wqmjqiyKPFO31rtTFhMXk9KiuvZ2N8sDfgd4eVD5_EZ93NxWLuD7TM6dL8MPJsCiy8XfDfyVUw0VY2y50qoQf38_d6ShwOGrBu2ojwsfrB6otpv3BJs-8hMZKYgzofapwOSVHzMOPQZucTUwgHoLDvr40MqvN_MTkfAWfL7K8_pRMwm6xTfVL6UqFYaIyMDary9dA-S99r54CfehNppQWc1_TmyH5cx-amyerTZzfR7gJOsGXs0deyz4uxToLv6sUnHGetsQZBs0ipx4s0L5gsY4jBNDSM9wy-bgRjX_OfmwO5zIQ3IwNTt3j1uSXr6F-d8XTWnVT3Q_9zE5BgT5wxCoMtBVVIcgsGLDeM08Z41d92bxJ9Tq80wCoAVQn0E4he-VQwVeZ4BBBbpZMTPI1voLRHtrF3FlKsE_Dpmpxz8iuEGrhX_7QvSX4_g7AmvP6AMQHgUEarNOuzO_5BejgkPOVdEw0EFTAZsafkOqGpNPzHnmEASvEwdmhqKuQwjTecNfVKLR40L51vu_0FmTOgvMQSybM4sXC4KuzEMrduil7cw5nausfewQ1nDBCU-1nVINHSSCVSBXb6v-vrTyjCKCYZvLirauEbHFKMivQwDVCmuP5vehluryiiLG9XeZOai13Am95vZffAebFLryY4n88CPlqYq8nYCSplUTel1ZccAh266vEow8nQLvBB6_PpK_iCk6RA8bg3j84_RVeIcjyvqZBnOjVS4iudBvtcUHbOdMsmuyDl2ennNnzFMguqiI4T2SaCcyJRx3GPLrq4VUBSWPRZURZWdNdASCYYFKBOL7877vjhN_7cnUkbT8NWCv5oOWDxtiD5OdWpK6eRWbj7jc1iey8YPE68hvvIxXUkwdFb-sX7G6lDMHMLtXZTjz4lWcMRMVS0hK0H--jOpjLF1Bn-z51Hbo2uMK3bXBfxrxQvSUaUJMxDiseP_Q38JApFcyH2ABnQ6YBbP-OGuK2ei7dGOshvYJSvipsoomw5BJrryB_ygLUJA6FfKvUFtqzvSZY4ETvuvFE_sIq-7xywth5GWmMpKqMbPJwjaMJeiXG2Ck9dPM5baiHc8BdScT7lvySyv56us6ZLbFyNxXwoy0obGlP3JGdCUFzni-NKeiBB0Uc2ePc19ZF8QjrUY5mVUYBRwl0AQ9l8Q7sx0Srlr3zmP8gbTWFLIlc00qOpRf7Z8ilePSZl5_KB6sqMWLWSqOcVAt79GOMWzCFA1Rj1kjtMiVmv3k5PuXOntiV2WIGhXimQeA6kLXWnWx9MJltRgkWgswzXrL7eW3EoqniAHwK0BRmNF_NKIU_JBPhX22H-acOrifgRUiP5YFiMYuBpzTmNgynYDh_ebfexCcOd_BOjB6sh1jeg==","summary":[{"type":"summary_text","text":"**Verifying seat thread scalability**"},{"type":"summary_text","text":"**Assessing thread cleanup on refresh**"}]},{"id":"fc_053bcf48796b1f2c016a59d539f9108195a2c87e7577b05715","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking net unsubscribe protocol\",\"pattern\":\"NET_STREAM_UNSUB|net_stream_unsub|KIND_NET_STREAM_UNSUB\",\"path\":\"crates\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_dnVMNTRj7eE48p6XmNFP11tT","name":"grep"},{"id":"fc_053bcf48796b1f2c016a59d539f92481958e6aeb1120bb73ef","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking generic unsubscribe protocol\",\"pattern\":\"pub fn unsubscribe|KIND_UNSUBSCRIBE|UnsubscribeReq\",\"path\":\"crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/msg.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_i8aCDu6abOzkH0TnMAne7FWX","name":"grep"}]},"duration":12318.18029999733,"ttft":1816.3525999933481,"contextSnapshot":{"promptTokens":134865,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_dnVMNTRj7eE48p6XmNFP11tT|fc_053bcf48796b1f2c016a59d539f9108195a2c87e7577b05715","toolName":"grep","startedAt":"2026-07-17T07:09:46.922Z","args":{"path":"crates"},"intent":"Checking net unsubscribe protocol"},"id":"c3cc69fa","parentId":"97dd6396","timestamp":"2026-07-17T07:09:46.922Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_i8aCDu6abOzkH0TnMAne7FWX|fc_053bcf48796b1f2c016a59d539f92481958e6aeb1120bb73ef","toolName":"grep","startedAt":"2026-07-17T07:09:46.922Z","args":{"path":"crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/msg.rs"},"intent":"Checking generic unsubscribe protocol"},"id":"7b1a072b","parentId":"c3cc69fa","timestamp":"2026-07-17T07:09:46.922Z"}
{"type":"message","id":"2d2703c6","parentId":"7b1a072b","timestamp":"2026-07-17T07:09:46.926Z","message":{"role":"toolResult","toolCallId":"call_i8aCDu6abOzkH0TnMAne7FWX|fc_053bcf48796b1f2c016a59d539f92481958e6aeb1120bb73ef","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## brain.rs\n 55|    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n*56|    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n 57|};\n 58|use spt_net::net::attach::AttachIntent;\n 59|use crate::transport::{send_hello, LocalSocketTransport};\n...\n 1685|        self.send(\n*1686|            KIND_UNSUBSCRIBE,\n*1687|            serde_json::to_value(UnsubscribeReq { session_id, gen })\n*1688|                .expect(\"UnsubscribeReq serializes\"),\n 1689|        )\n 1690|    }\n 1691|\n## broker.rs\n 59|    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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...\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 3069|                },\n*3070|                KIND_UNSUBSCRIBE => {\n 3071|                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n 3072|                        send_error(&send, &msg);\n 3073|                    }\n...\n 3561|    fn dispatch_unsubscribe(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n*3562|        let req: UnsubscribeReq = serde_json::from_value(env.payload)\n 3563|            .map_err(|e| format!(\"bad unsubscribe payload: {e}\"))?;\n 3564|        let log = {\n 3565|            let sessions = recover(&self.sessions);\n## msg.rs\n 34|/// the role, so this is idempotent belt-and-braces).\n*35|pub const KIND_UNSUBSCRIBE: &str = \"unsubscribe\";\n 36|/// Brain→broker: write raw bytes to a session's input (send-keys).\n 37|pub const KIND_INPUT: &str = \"input\";\n 38|/// Brain→broker: resize a session's PTY.\n...\n 435|#[derive(Debug, Clone, Serialize, Deserialize)]\n*436|pub struct UnsubscribeReq {\n 437|    /// Target session.\n 438|    pub session_id: u64,\n 439|    /// The detaching worker's attach generation (ADR-0038 Amendment fix 6):"}],"details":{"scopePath":"crates/spt-daemon/src/brain.rs, crates/spt-daemon/src/broker.rs, crates/spt-daemon/src/msg.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":10,"fileCount":3,"files":["crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/msg.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brain.rs","count":4},{"path":"crates/spt-daemon/src/broker.rs","count":4},{"path":"crates/spt-daemon/src/msg.rs","count":2}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## brain.rs\n   55│    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n  *56│    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n   57│};\n   58│use spt_net::net::attach::AttachIntent;\n   59│use crate::transport::{send_hello, LocalSocketTransport};\n     │...\n 1685│        self.send(\n*1686│            KIND_UNSUBSCRIBE,\n*1687│            serde_json::to_value(UnsubscribeReq { session_id, gen })\n*1688│                .expect(\"UnsubscribeReq serializes\"),\n 1689│        )\n 1690│    }\n 1691│\n## broker.rs\n   59│    NetStreamsReply, NetStreamOpenerReply, NetStreamOpenerReq, NetStreamRetireReq, NetStreamRetired, 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     │...\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 3069│                },\n*3070│                KIND_UNSUBSCRIBE => {\n 3071│                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n 3072│                        send_error(&send, &msg);\n 3073│                    }\n     │...\n 3561│    fn dispatch_unsubscribe(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n*3562│        let req: UnsubscribeReq = serde_json::from_value(env.payload)\n 3563│            .map_err(|e| format!(\"bad unsubscribe payload: {e}\"))?;\n 3564│        let log = {\n 3565│            let sessions = recover(&self.sessions);\n## msg.rs\n  34│/// the role, so this is idempotent belt-and-braces).\n *35│pub const KIND_UNSUBSCRIBE: &str = \"unsubscribe\";\n  36│/// Brain→broker: write raw bytes to a session's input (send-keys).\n  37│pub const KIND_INPUT: &str = \"input\";\n  38│/// Brain→broker: resize a session's PTY.\n    │...\n 435│#[derive(Debug, Clone, Serialize, Deserialize)]\n*436│pub struct UnsubscribeReq {\n 437│    /// Target session.\n 438│    pub session_id: u64,\n 439│    /// The detaching worker's attach generation (ADR-0038 Amendment fix 6):"},"isError":false,"timestamp":1784272186926}}
{"type":"message","id":"dc78b846","parentId":"2d2703c6","timestamp":"2026-07-17T07:09:46.933Z","message":{"role":"toolResult","toolCallId":"call_dnVMNTRj7eE48p6XmNFP11tT|fc_053bcf48796b1f2c016a59d539f9108195a2c87e7577b05715","toolName":"grep","content":[{"type":"text","text":"No matches found"}],"details":{"scopePath":"crates","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":0,"fileCount":0,"files":[],"truncated":false},"isError":false,"useless":true,"timestamp":1784272186933}}
{"type":"message","id":"a7bedd6a","parentId":"dc78b846","timestamp":"2026-07-17T07:10:14.166Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning executable hash verification**\n\n**Analyzing broker write and connection churn rates**\n\n**Identifying resource usage per subscription**"},{"type":"thinking","thinking":"**Analyzing stream synchronization and subscription behavior**\n\n**Clarifying subscription count and session interpretation**"},{"type":"toolCall","id":"call_UrW9D0HLnMfoBbk2TL1HhVRy|fc_015d3a682cce71a9016a59d54dbd28819082e12a36ae781c06","name":"irc","arguments":{"i":"Sending complete causal chain","op":"send","to":"Main","message":"Decisive thread-count chain (BrokerCpu cross-check): pump owns one long-lived Brain carrier (`pump/mod.rs:519-557`, `connect` :1126-1142). Every sync pull opens+subscribes fresh stream on that SAME carrier (`sync.rs:335-346`; update likewise `propagate.rs:355-356,557-558`). Broker handler pushes every id into `my_stream_subs` (`broker.rs:3245-3247`) and only detaches them when physical Brain conn EOFs (`:3278-3282`). There is NO net-stream-unsubscribe command. Each subscribe spawns one `SubscriberSeat` writer (`nethost.rs:154-223`); after response EOF it blocks forever on `rx.recv()` because StreamLog retains seat/tx. `Brain.net_cursors` also retains every id (`brain.rs:1590-1597`, cursor map :271-276). Healthy cached QUIC conn retains StreamEntries until whole conn close (`nethost.rs:949-970`). Therefore thread slope ≈ completed sync/update subscriptions on current pump carrier; 532 parked broker threads + only ~4 active is exact fingerprint, and refresh should collapse old seats only when old pump IPC closes then restart accumulation. Separately Registry fire-forget opens fresh local FIN stream per 30s and never retires it, bloating dispatcher net_streams 10Hz. Test seam: production pump+dispatcher, fast repeated nonempty sync + Registry rounds over one held conn; assert broker subscriber-writer gauge, Brain cursor count, handler subscription count, eligible/physical stream count plateau O(peers/inflight), then refresh and assert old-gen counts go zero. Current tests only bound workers per poll, not lifetime across rounds.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5596,"output":1200,"cacheRead":130560,"cacheWrite":0,"totalTokens":137356,"reasoningTokens":785,"cost":{"input":0.02798,"output":0.036000000000000004,"cacheRead":0.06527999999999999,"cacheWrite":0,"total":0.12925999999999999}},"stopReason":"toolUse","timestamp":1784272186973,"responseId":"resp_015d3a682cce71a9016a59d53bc56881908e37213ad944a0d9","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_015d3a682cce71a9016a59d53f19ac8190a411790934273859","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdVI57kdrXQ0FXnXbSw_m0DGmIlEDGiTTmRLKQu-AnMoANgunkr0GOGWoTdZcesAvzr-BrCP7ya2iorFZlo1Pl4jb3oEJ0RvXd3H-VIw1YwvfDvDlvkB18vPQB0iDIq3Why0JWvDOwFEsmT8lpgp4m7KfajdVa_6oftoVZ5lc_7bxKuhnWGPnNgmuVIDnzAhbmTUgi-1YzA5pazSmPtxq6d6mhC0nwqtk69G92nqI_dJ6ePBw6lIlt4DGH9CFJeVzjbW-Ne1k9qvnXBu7u6zsf9QB8mFBpXoFV9GEr4uZX-ID5Q1596oWPG5ofyTLUY0QCcqJJQlIM3N392AVAOs3GEUNAhoWkcdx7O3g06oYpXVCkCchoUabU09amWeiSWxiMfKubPvBWi23URLP4tpGHm0YSXy_kIhKZpJiYikHN9xNPkYIaLs1rc3wulezZsHHHSm-kWT9BRdaF0fSxdWvMD7ZwMUtFy8sqkwZDuGxiliKb14pvsuZ3XStwRjpfAk_tDEutCrJ0IWnELIKz04K-3UTNhzBRn0y-lLMp3rT5OUQgNBtw2WPdKbuGeBrq5rWjHnj1G2u27jqYqk2ddtr5aysGUrramz9_uouZGUKTQNv-Nv0i__BIJBpDjq8BGtXKxp7DsA6ByyGFfsfZjxKHXD-wnaNEVdlq5bCI3erv7Yv9g1-YcDc0OCSZbh5HlItQWhUrG9jxrItk4iiQ2xDiNVmP95NdX4Mokcp3VEz5tp7KhaxKe5HQNboXQfah0FQ-2CnZyTkImi8WlAl9D1j3Tqm4n5qH4B34b2fqro2-0k0Q0IgEZECKjXVF5ght10kcvhLEzgtKhus1f5s05--PTTbgI9WVf5SJMEhDPi24tJHQIO6I7RRM5J8AZzUT6bgp-64Y6mDYjoiveYyt_It2pzkid2XqPZJbHbcdUtb_Rzysddw90EQv4lb64bLnDasBEmSOtWp5Igo6rS56R71if6iHdKNi4nOWaOQCAcXtHI8Lawm4o0-YmugDgT_IFvDdTIO4tPxPCS9rH0kkNG5_CcM8I48AR2HVJJRlio16ec7YScylE_InXM3gg0Yzc_xLLEw8Khtz8S2MgeWQU7xHovR10LzAu0oSpXHFTj3mIsr-ZZNx4j4HnGnXvYHnJUE5IlzNjbmhZK3Fy7_GsGXRGFa-FxMCLQ5YHKZLX1hF_JSNP58FPpQJnwmR15agmnX15wxU5ANP_sDS_LsFRahZfSpYENa8Fv_A8VVM30dYF9Jk0MJR4-4VcwDJyiZX08urmCnoTdw-bEnMkLJNzgQslXWhTIKQG2Nw1M2oH2LmVmnEJhbrTQHjFSxAzZ5YmWMFirRrM9QlbRg-Yd3f-Fe9f-t0Mo30jxjN67aY8wTtmfIyOo-LOKMzScK-0WAlgA2nLEs2b8M7gs_ZWqOq_T0uB3CVkYQ2-WwnvZdApOZu955oylI6gphjFo07K4iaKQFZrvjcFhyR5mAR4nZ3hpXS6rKZxX-lAYmPzhK_oJ3OK8ym0SNnLGR-qddNyVnfCShJqt2ds236A4WP29RRjt0tymdxQNqwpnaBL0oyJnHGBzstRjWdZVJAz9-lZfWndUK9WePKKr2Q1EZa_W9Byd6CgMmNOWHg5acEDi421nNX-2nqcY1EBipQ5mn4OKhacWly6kBvuSz3n06UGNUMwT0x4nyr25GhdyxCi5dF2D2JxPk47qfKduSGop7JCc2QPzUeiFI6u42hYAqIJvwOCuGOwvG4a1a0ZpJxzj7RuM5zww9RXZOBQ7HQaL_oXCW1AjSmiZRlBoiDyoTIi2BJ2Oh6LQtV3MneKEPOLEZdCOGjXRfqf8kYUzg2U7kj5Dh-jZALr4be1R4C_w-QWrxe-O7EFWA055-w7Xflzlf6fecM5WH5EAemHnbWukFafqpODVXxgDVs208ozwPUo9Cg8-turRlXpc1MzX2G3vkCMiwJ-dtpx0bd31-HZwd26eMnfx5kGYDYhR-eacFLujQ8Og3Ppw6mjiZePCMKiqgngXLBqcBMUFaUY3Z3VjcHQtGFzm6Ku4aAwa7jnRhjZ38ldkN2mDLwz360JxHj6n50lLYablWZ5eaZVNUjN9UcW6vhDDzPtFU3DRbiE_vIlGAXo9LbrQa-ObfD0bOxt6lPfxgEkNJOR3g17lBdCXktXI3EuymmMr2gy4-Urpdh2UNVpru4pzvZeuGBPs_QUgwbcTrJ-Yj9LnVrjECqlRZcjU8qoKINy2F9XhPNSQ9c_pQ2ed9KfDOntWFYOVCIG5AjBJHrGIZT3pQHvB38EEQeTCNDEJ3sW7Se8AmanJDTbsPcKlpsR-IsA55UkBB-IjmurYA7XpG0UD6S7cwBpRkRnQu0lsP4E1L1A_Wu3NNd-E_GYREM4vQ_TDy5EdcfIiUtFOwLe_0c0yPwEMmDVapr9ECz1tf7-HqXtAbsBMU3rZrpGRN4xoPeehx47T4wuYZkgR5i2vlsPB7ZToEIHsK8CVVg9YNUdIGBf6fbaCUqUuYisSrr8BMu5x1Yk5jW25biyt_6q7tVv5_15GdklODoy8qUY9Lx3XPYYDjblWQ6JpvRTpVDCNaYgMLIKrhKF52uf8DGRSOSgTAF1m4Xp9TuEFiutiVp93FM_AO8rotk0iG91-JlRqWIzIvOSNSsSswDS8C88hETadVGSx7Twg5XpJbfZgch77Xc0-oRTbiRYKZD_Wv5r3iE2BmIPtzIVmKeGbKiv4mQLrF3Eld-DCyZYlekH29tgI5KZbRKyrnGO5ror5vQZ16C_rRFX2H06vvHJDf-zdTUbaZ1D-Kjcc-7bfG8aDtx30IPzF1HjPTe4VRV6waaPWbuip2eIyIEwO6mUlwmyDAkhm6SSY4-h2HhJCLfUVGCacDzYYrZiWmqWhW_YkFn-fnOHOC4V6TL7rVsg_niH19eQ-d6UHWsip4hZHtwnHotpusycGIoRhlx3wnlco_p6Rv0qgmPk0E9jLntRq_rvgTOZ_DBhrlYloKVQ4_60syeLtjEot5xc-tTfbxhVaQItMvoascC7l_NzihNJu2Dj6XUQTjTuZPf4LGzclcAuXZTDztgxGWBfQgsH5yJGaQZUxfkwraVTY9vEAZt-l6X2HK-vUOvbz93rhNNng48Z6lp1l0C-_YJCHs8PlEtyCFlG5geXRxRBS7mtWy5m78rgmnvh1-IB9Iy2b8uldz5dTMImUobhiwuzyyWwADArIaVisQozvaVwErbyUeodmMhrDYZS_TJm_hRiaZRdx7zzIxwtjX2O-KJL-3FoJIi0qdyLuPmo9gPqTXysnyRXUkRx1Dgb65VE1mMaksgSV6kg32QnUAYx_VqfEVcXZ28SRGOh3aOmwIqPFy38E8ASsJ0dd_kIDxRJ9pCTJMAIE79PYFtaQyWj8SyMmyUHU9YkYauuZWI9lggQxgMIdTl7dd-rZInY9jbUJYYxb90DLWrbZE687ZA3j6b98dnIMkaEoNh629dDQIMY9Sq2S9Og142dCgi5EiG4gP0tPbBGCb2xXjRHy8kjsLDY3_JKqiCZQ9FYwmps-xQDrOt804sr8bgNmpNmuJJRcfA3JVwxmWmV33_f7ZDA-ADT7XmsVqYKWQY5o_ufDRCNwxe8oKd4KKO_FLK_qjV5nQ-NcsTwlScWj7YDzyI45fRUpxP2ys7TEsN5MVSTMy2081tiuv1dNf6LTiUNkyBRP5oTZDzdj5ivaa9X7Fya4ROVvSWGZBA3jspN-z_BAJky_ucPlLYcONna9hbtHFbbAugDeh3r0Ypev9qnMYnfTzrinrW5WIPpEMSPY0LSqqMWHh5lXP7MBOlsqpJFtH2J2toIkbxvKpAEvNJmP-EVFQV-QHk5tYONyHTwOdl-X59_Hhj6H7E_oKDqPB_lRLNTGBRXzfJfWkkWljyaN9VaSxNn5lne2DQeO2--6I5qdz2r5yHecCZV3MlWnJbfC-wI_R12DAdNpY3hTpgfu9Gty1lwsjDs9Qh01p5AeX-i9USMszbV22GjvfTea4h0=","summary":[{"type":"summary_text","text":"**Planning executable hash verification**"},{"type":"summary_text","text":"**Analyzing broker write and connection churn rates**"},{"type":"summary_text","text":"**Identifying resource usage per subscription**"}]},{"id":"rs_015d3a682cce71a9016a59d548aa108190941329b6fea653c9","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdVNPCN3FhPVyzcYfyzgY1cIbjpslbtaAI11NnwIx4oNq4iCo29Z-Yri46xn9Y1I1KF8dA9kh44YxoP-FTMF2x4bTaQPO10BuH9j6NiFP1u251uNe1dHTu3YYyWJceDslis1zx3SdXN4T2PIK8L9IDkOIH7rwBBU3v2m3iEzORQNWf10MukArwMfbaJchYRyN_Q1do190BBre4tA7Fu3UhY4rxii-rO7I91v-kY6u2qhE79qTim2CD99o7yV7febJMUf7xDPO-gGgcPnmZzE9_xUm2eW2lKa04t2bGPdLERohETea9MfnI4_I4hpIFBj-PqbWsfeuuHauffrh4mt3-YJg2jonyLlT2TapZYhJuqIuySeco4285RcpB3EVULTvQ1beh5lHP4SfwVd94TCDHfGWUQ5HmyLcJVsRn0CpF0zdwm4euTgF6_SNodezMqthykEht8JNKodWswt95-hxEb52Ru8c8G-wciFAAx67Me9bWTLfNn_zbgr5uV3oaUbr_e0_meRGeog6OFhZdCoe3AuwLE7buShgbtcaljvvyOQBi30lAhWvsj78SULPM1qoQqfeMSzKV0tE9p-nRtBoPcufDGjLX4Ja08P9R_RHfh6UAi8o-0b5X9hEuYeExTZDkNcG0AkNPYMKZrP0dr6D5Y4J8zf7m2m5mTMGm2zxLTYWdDxYRVQqFY56IpfpnayKgBCrmALLFgU0Zdx2isEP_fUsI33IHR3rLHftimTlnEoFU38p6BA7PuVZBiN2MX8K9UoCqjQPCFNfuW7ctnlZDpZaSjsE5-dcG0_TnIACLspY409P5musmlbMcciIpzwz5Wrdjva28luFMJZGqfyTrN413f8BgAz1hYv27uD-vstk-YMu-lH-ji1GNaCgkBLDiIU9toplZVhI_do2-45iWDrm6fgFHYB6j7QJ6QmOcO3LEXgxW8etP3t8rnmucrLh1A1_voVdRIDNVMX-lFMnaZpDcWKOqVSSktCW7rI5FaR42pESMI7HT5TdVtiTR71V_S4LhL9QgCwoosseDzRftWlXDFknLIrdCelfOoVUPFpbGif96S4YjBKhL30jDt7wkDIc_hfIsEsSjq1wBDnp2UkXmUguhVvhLipNKQvbrvCcKbdwgSWngZXrJZ2xI8eXqzY-C8P1mQDrhFBHjVRDMJ6o6tavzIj7kZ-BmN7TFOZ7Un6jqeC5eaBIZyYyFlO8pAoxamYMLbxWW4qb5uDmVx9i5UOtAFKvJiYZ-MIRiBsifVppD5GWFskryTikqlV5XVbwPtxjMPyKHj_-XcXGSPrHKh1Mpv3Sd57pMYiOPdGNFPvh2sYzUDlWT1VsMycAHnqzMYMNXoSCelOZS-g6kl5HnVi4g-sH7shc-FWi0Mlahy4TMR5BYEjZ5D6X2JrvGpJLl7Lr1qqxDOaELvANLp0bT8YdFOtgg4WZIwhZMLlUfQoEwdnBjg9NMn4njn1Tztg7ZDn19lhrvSZDpJpVHJBuy8fpkWL1PFdQFTTTl4aFDrWEI3HGG0X-h_p97c8AyqWPLLgvzLKxsZB5uGjdKXv3EbE_hYOKVEuhicF0Mg49FBCcII17EF3bpct0kp_9mvpb1hNhVWdWq-lx5vjy-r9gVqMe_pXYDohfZS6ZDGrU09WLrFTJOJ9XsfgDDBXPQT2At_nBM0M6R_yQIdQBJsDkpG0BX_2msdAWlaDZ0UEPyikFz90kAHUXecHp9rBeG0GJ6nhLe-h2-WkOQfuOkS-HiNA1BeWvDsqW7Uec6ufnVQtGzAW52gfeeGTTV-eagsiK0MQBDt3XxsL-PBsNn8hTXpZnSG0DJDaj7vTEJpnylq-OyqUI_sXr_38su59m4E_NbbA3XsiyUuC7l-SJZehB9kjzIBTaI741ZCZ-YbWlkGsyHEYNHGGr5XC69y0c-yIPaR9kZC3umknYUPxoOFiyTRLOW3n-w8xFgFNpHORWvVVsfHC1QHi7D4afZfNoE3K-F0xIMpvthWs1QYCzWmQ4tVfdrPMUEd1-OYnHZ7du3Dw1XesiTmyX-lS9JaAXAdYItP9h33Sg7E_tixuKSoDdftge2xIzkFerKlBVhjzbZ1yhPgyBsDYESH-moD9-voHvNcAqpaaUcL8Bnt8XCXsTNVAxpKu1hxMsAzfzItKHyI0t0-0EnFxiHb7B-taobmLRKpX08i2oNtz4FxU7g5sXRkuc1QywOu5TYhAQg9kTU-8pnUMXlnHRPguJLtLJ6Y9NLq2GF1F_4g3ZcI9bMjzrqUCHraRYT2UnT9Qn2c3xI_hzyNrArpwK_LkRw4_6TE_cxr7HT3UsuuVCOIv-Fc0s1QXuCSCCQRnjl3oB_rFAatLFX6ttRlbscxF7G6uht1JTlNNWuPj9DNTrNfL-ShUwa_4KmuEugn2GJ0Osl20MtVCG2tNeWHv_M2_MrqdylgS0Kxe7XLMGXze98XPwUYR84tP6dI_vGGad2wxCtKbEsGz2UTDyNXhazo-7WcFi7AXWZCoh9EPw10rvgTYgUz0na-5rxU6j_BNa6xH8oI7jmwacJPbmdr5KZJkZauG80N1MS5F3-NLPkt4leEf0AVYHi7IsbXHUdU4q6HaZE_EEpZ4BdcGH9eARbiIzlHhmRUl4Zgzn4hCu-usPQgSHad59BTpdL4miU9SsHpH3eBcehDo8AOD1pETqT6xOwdwVFmnx92Qgfxm_QY-GwXzVwoPWQ==","summary":[{"type":"summary_text","text":"**Analyzing stream synchronization and subscription behavior**"},{"type":"summary_text","text":"**Clarifying subscription count and session interpretation**"}]},{"id":"fc_015d3a682cce71a9016a59d54dbd28819082e12a36ae781c06","type":"function_call","status":"completed","arguments":"{\"i\":\"Sending complete causal chain\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Decisive thread-count chain (BrokerCpu cross-check): pump owns one long-lived Brain carrier (`pump/mod.rs:519-557`, `connect` :1126-1142). Every sync pull opens+subscribes fresh stream on that SAME carrier (`sync.rs:335-346`; update likewise `propagate.rs:355-356,557-558`). Broker handler pushes every id into `my_stream_subs` (`broker.rs:3245-3247`) and only detaches them when physical Brain conn EOFs (`:3278-3282`). There is NO net-stream-unsubscribe command. Each subscribe spawns one `SubscriberSeat` writer (`nethost.rs:154-223`); after response EOF it blocks forever on `rx.recv()` because StreamLog retains seat/tx. `Brain.net_cursors` also retains every id (`brain.rs:1590-1597`, cursor map :271-276). Healthy cached QUIC conn retains StreamEntries until whole conn close (`nethost.rs:949-970`). Therefore thread slope ≈ completed sync/update subscriptions on current pump carrier; 532 parked broker threads + only ~4 active is exact fingerprint, and refresh should collapse old seats only when old pump IPC closes then restart accumulation. Separately Registry fire-forget opens fresh local FIN stream per 30s and never retires it, bloating dispatcher net_streams 10Hz. Test seam: production pump+dispatcher, fast repeated nonempty sync + Registry rounds over one held conn; assert broker subscriber-writer gauge, Brain cursor count, handler subscription count, eligible/physical stream count plateau O(peers/inflight), then refresh and assert old-gen counts go zero. Current tests only bound workers per poll, not lifetime across rounds.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_UrW9D0HLnMfoBbk2TL1HhVRy","name":"irc"}]},"duration":27191.91700001061,"ttft":4173.021600008011,"contextSnapshot":{"promptTokens":136156,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UrW9D0HLnMfoBbk2TL1HhVRy|fc_015d3a682cce71a9016a59d54dbd28819082e12a36ae781c06","toolName":"irc","startedAt":"2026-07-17T07:10:14.166Z","intent":"Sending complete causal chain"},"id":"b778f548","parentId":"a7bedd6a","timestamp":"2026-07-17T07:10:14.166Z"}
{"type":"message","id":"3b09d051","parentId":"b778f548","timestamp":"2026-07-17T07:10:14.167Z","message":{"role":"toolResult","toolCallId":"call_UrW9D0HLnMfoBbk2TL1HhVRy|fc_015d3a682cce71a9016a59d54dbd28819082e12a36ae781c06","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"RedispatchCpuRca","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1784272214167}}
{"type":"message","id":"1dd706c5","parentId":"3b09d051","timestamp":"2026-07-17T07:10:27.268Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Ensuring exact line matching from grep output**"},{"type":"toolCall","id":"call_lbDpvHk7EHoB2KDUm89M8nct|fc_0c47216bb421922c016a59d562da54819682bfcd64d20178d7","name":"read","arguments":{"i":"Reading subscription retention lines","path":"crates/spt-daemon/src/broker.rs","selector":"3238-3290"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5794,"output":81,"cacheRead":131584,"cacheWrite":0,"totalTokens":137459,"reasoningTokens":38,"cost":{"input":0.028970000000000003,"output":0.00243,"cacheRead":0.065792,"cacheWrite":0,"total":0.097192}},"stopReason":"toolUse","timestamp":1784272214213,"responseId":"resp_0c47216bb421922c016a59d556c0dc819694bd8db50ba70b23","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0c47216bb421922c016a59d5584cac81968a9adf8e403707fb","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdViMknfmlMG7SfZgu49pGpIhJxmECPJex2r_Glr6Ysa1cfBhjwlcSEcR1mN-4YRpkGZ3b1Gb6UkNK5fTixO4BJn6mWEWwiOds_OwoK_wuqjHfLJuD7EDqRSR1a_hW6C28PJRW2flnfIkP8VkLSaS0p7QKjQGskxUWohvcCQTU7Hy14pNMbdY4tAwpFfoaJMgyei_R7zFqeg-NNxX4tx5IGRgh3Xhvbivin-UB7tj-Ph6Sn9wEh_mBb3krLpt_hMT8KxPYHfXtSGHii62hgKa_EE9jm5PaGlTkHgGPnNCIfTTQ16X7xCkYaj_pK2EShuOe9VGe85FTcGtO4hCr-OimCJqwT6hKM4ZLJZwZ1ljTJURM-s0eGgkRNpbexxT89VYdxAkZC4FB44FqfpOfIeDNdr_HNBjwKirWwkGN6QEBSSRpaull624Tz_hdpwuI-qgNXwEx9UPajCn0zkf9O6ZrSE1z-mfm5dpCr4O2iakbLI1ksmUySGCyfOgiCZz0FA4_i92POu0pTKLYf3FQtBaVwiSr4MQVz2tydop90l1m_kr5DPmudFPvI12d4n6R-JmySXUnH92MD7HkY_M4RHuUH0Rw4vgTiCVyujPdCul7TqwrNp8B7ViQbEV-LAlGLcapxg25BLp6A8oYZiDV5AF3DtklDjVxm6LBXl4iLo1ALyg-CK_ZxehhRvCv3Wu8R19NtCi92yHWPNyGZZRmOyRUtKc1c5w4Cx7bOjKBkvB4pW8lSVsLl6rtPCEysTHzCPudW-Bl2KNtXAUg1hrFsco50b9O-GSbkCshm8Y0iqCvWLa4GyKjcuTp32wRvwvXx1C2YbD4PIEhICq96kq_5tfgA0dCQx9B_xhMAxGxsVEndC2f6yGdGIDFQCwNNUIBRwEvF92rX0VY7B-bTy2zHv3v8lKoqv6wM3sFTomNubi65DDfsdhagUWQ4qvCEI9yZBXfqgdYmvP0Ks0l8D7B8g4-G_9wecb5z4poZAFN_QHizIgqLALrlzoEldk7y_q9bAKzSiuRhhsLcGgiUyXLxWSuI2aXTWkTxxb83dq0TJgOoG_UC2iBwzmFZP251uyUNQo63DkACenG13GT0wGNIXyyPiFA==","summary":[{"type":"summary_text","text":"**Ensuring exact line matching from grep output**"}]},{"id":"fc_0c47216bb421922c016a59d562da54819682bfcd64d20178d7","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading subscription retention lines\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"3238-3290\"}","call_id":"call_lbDpvHk7EHoB2KDUm89M8nct","name":"read"}]},"duration":13054.888799995184,"ttft":2119.5673000067472,"contextSnapshot":{"promptTokens":137378,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_lbDpvHk7EHoB2KDUm89M8nct|fc_0c47216bb421922c016a59d562da54819682bfcd64d20178d7","toolName":"read","startedAt":"2026-07-17T07:10:27.269Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading subscription retention lines"},"id":"07522dfc","parentId":"1dd706c5","timestamp":"2026-07-17T07:10:27.269Z"}
{"type":"message","id":"062c9786","parentId":"07522dfc","timestamp":"2026-07-17T07:10:27.307Z","message":{"role":"toolResult","toolCallId":"call_lbDpvHk7EHoB2KDUm89M8nct|fc_0c47216bb421922c016a59d562da54819682bfcd64d20178d7","toolName":"read","content":[{"type":"text","text":"    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n…\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_SESSIONS => {\n…\n                    let frame = Envelope::new(\n…\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n\n[Showing lines 3237-3293 of 6298. Use :3294 to continue]"}],"details":{"truncation":{"content":"                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH","truncated":true,"truncatedBy":"lines","totalLines":3062,"totalBytes":2611,"outputLines":57,"outputBytes":2611,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n…\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_SESSIONS => {\n…\n                    let frame = Envelope::new(\n…\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        // Clone the log Arcs out from under the sessions lock FIRST (ADR-0038\n        // Amendment fix 8, KH 7.12 kin): `detach_if` re-stamps info.json\n        // (`viewer_count`/`driven_by`) and emits a lifecycle record — file +\n        // conn I/O that must never run under the shared sessions table lock,\n        // or one slow perch write stalls every spawn/attach/kill dispatch.\n        // [impl->REQ-DISPATCH-HYGIENE-TELEMETRY]\n        let logs: Vec<Arc<Mutex<OutputLog>>> = {\n            let sessions = recover(&self.sessions);\n            my_subs\n                .iter()\n                .filter_map(|id| sessions.get(id).map(|h| Arc::clone(&h.log)))\n                .collect()\n        };\n        for log in &logs {\n            recover_log(log).detach_if(&send);\n        }\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH","startLine":3025,"lineNumbers":[3025,null,3055,null,3061,null,3165,null,3234,null,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6298,"totalBytes":2611,"outputLines":57,"outputBytes":2611,"shownRange":{"start":3237,"end":3293},"nextOffset":3294}}},"isError":false,"timestamp":1784272227307}}
{"type":"message","id":"f01ba926","parentId":"062c9786","timestamp":"2026-07-17T07:10:45.939Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Verifying sync request behavior**"},{"type":"toolCall","id":"call_GGfghdcV8hAyS03HufCEhzBM|fc_0830e7641b9ff633016a59d5758c848197a802419de8b9181c","name":"read","arguments":{"i":"Reading sync completion return","path":"crates/spt-daemon/src/sync.rs","selector":"430-510"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5497,"output":87,"cacheRead":132608,"cacheWrite":0,"totalTokens":138192,"reasoningTokens":46,"cost":{"input":0.027485000000000002,"output":0.00261,"cacheRead":0.066304,"cacheWrite":0,"total":0.09639900000000001}},"stopReason":"toolUse","timestamp":1784272227356,"responseId":"resp_0830e7641b9ff633016a59d56925f08197a2c4c5bfae92f994","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0830e7641b9ff633016a59d56adc988197ba305095e8195068","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdV1LiQgD9Fiom7tXpaqM3uuoZRos22tdd8aWgiBaXqQxMccw1l3zmRxaXAAFUPIL30XcwtMh6kOhsIY1tFfv931oJs6JtrBNXgMOXCLI7x1SAX6qjXj8F9ccnnFB2AyuFcJsErMNnPQARjbWrzptVQnEkradiDCNqWR3Xrc2VC4HxnbYZKw1i3JLAo6fq4NHxexvA3nSuOCVjVdYS-7DSUHZikrWIOBwHn-49mNTGPE3MVcXkzaS4VoenvtF1jiCsH79J2Q0Qdiqb1i_UvffwuGsg8NOggLS2iTsuV_nTjNch2XsN9DiGbJolnoKmKvndM5v4ZP-m1A9uHjbZaiOGfsuubc0vs3TrSXVtKdtzaTGD3pGgQbdTpIewHVmWmTMTxVOVZDKBm0Vm0M0RvJDYXqTLFgcXv9Hb3ImfArteQ-Ke3kA_byoB4csLQ1QUDgGBvICPgQKwL70uaSX3KBM26JAU0QKqYv4XEUIpBAhw_vx4EwX-HS3ApsdWiRv0i4RSnanSm8ZfGMvYCPWF0egYbhyaxWG6mFO31iEbflrzOJ50zX_fU2e2yoYdsPnctI9jF1B4P3L3XpJ04Ls23gFCN_aVJRLBNpisCbXxxs7f-TcBOZh3ed0ID5R3b0ZXw3fv3iV5Zhp7YNaybOTvX2Skric8EeNMjFKVT52D4xftTmq1xMpfNkfoVby_jCH5SbtrBSfiRZkbo3sm5nFcxkBjXBbZuBCE765Nt9ahqpimzamJX5CyE9iqIdyZtDV_sY1F5aJeTW7l3yi_pJ1tpgMeflvcfH-NtNek8YnMXJ35awvQ7Ysh3Z1MLqeBVHLQYlqpKqzVX-hNHNLPcgquEv1bzH8anDOMMVnLVdnNkehysdn5_NkYyc_hj8ficKg-Dlk7caUDg7oHyuKnaW-HbG8cNXJhyyLmC_h56wgUAa5uhMk1xbLL3hUx2uBJdf2pEs2oXPQ6rGtQvDCvzMkMfpm-Hq5fD71o4qk3DEtNFIB8EAdjZWwby7KspKX9XzcRr1_fgttaao1isFZdht3zanB8zpl19w_kFPyxaRIBYdUHNIoU32SC3e-nROC9LcszgQgpptsTZ7fSOezKf3kHp-t9TELuSnMTgmQkNGSdpp1JFX_qdPlb30cQsvSBXGbuRTJlNFq0XfbrxGb4ZO1LIc57OMIg==","summary":[{"type":"summary_text","text":"**Verifying sync request behavior**"}]},{"id":"fc_0830e7641b9ff633016a59d5758c848197a802419de8b9181c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading sync completion return\",\"path\":\"crates/spt-daemon/src/sync.rs\",\"selector\":\"430-510\"}","call_id":"call_GGfghdcV8hAyS03HufCEhzBM","name":"read"}]},"duration":18582.768099993467,"ttft":7507.528499990702,"contextSnapshot":{"promptTokens":138105,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GGfghdcV8hAyS03HufCEhzBM|fc_0830e7641b9ff633016a59d5758c848197a802419de8b9181c","toolName":"read","startedAt":"2026-07-17T07:10:45.940Z","args":{"path":"crates/spt-daemon/src/sync.rs"},"intent":"Reading sync completion return"},"id":"3e7c3ef9","parentId":"f01ba926","timestamp":"2026-07-17T07:10:45.940Z"}
{"type":"message","id":"3f371107","parentId":"3e7c3ef9","timestamp":"2026-07-17T07:10:45.947Z","message":{"role":"toolResult","toolCallId":"call_GGfghdcV8hAyS03HufCEhzBM|fc_0830e7641b9ff633016a59d5758c848197a802419de8b9181c","toolName":"read","content":[{"type":"text","text":"pub fn request_sync(\n…\n) -> io::Result<SyncPullReport> {\n…\n    loop {\n…\n        match ev {\n            BrokerEvent::NetStreamData {\n…\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n…\n                        SyncRecord::Done {\n…\n                        } if xid == sync_id => {\n…\n                            if got != total {\n                                let _ = std::fs::remove_file(&bundle);\n                                return Err(io::Error::new(\n                                    io::ErrorKind::UnexpectedEof,\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\n                                ));\n                            }\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\n                            let _ = std::fs::remove_file(&bundle);\n                            for branch in &refs {\n                                if let Some(tip) = tips.get(branch) {\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\n                                }\n                            }\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\n                            // cutover): a sync pull moves `p-*` tips on the RAW\n                            // BranchStore — `apply_fetched`'s adopt leg can even\n                            // CREATE a p-* branch (a membership change) — and\n                            // none of it rides the ContextStore nudge funnel.\n                            // Nudge ONE global refresh per pull that applied any\n                            // p-* branch; the fingerprint gates what actually\n                            // rescans, and the debounce coalesces a multi-peer\n                            // pull burst. Without this, readers (index-only\n                            // since W3) would lag a sync by up to PERIODIC_MS.\n                            // [impl->REQ-PROJECT-INDEX-INVALIDATION]\n                            if report.applied.iter().any(|a| a.branch.starts_with(\"p-\")) {\n                                spt_store::projinval::nudge(\n                                    &spt_store::projinval::InvalScope::Global,\n                                );\n                            }\n                            return Ok(report);\n                        }\n                        SyncRecord::Err {\n                            sync_id: xid,\n                            message,\n                        } if xid == sync_id => {\n                            let _ = std::fs::remove_file(&bundle);\n                            return Err(io::Error::other(format!(\"sync refused: {message}\")));\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                let _ = std::fs::remove_file(&bundle);\n                return Err(io::Error::new(\n                    io::ErrorKind::UnexpectedEof,\n                    \"stream ended before Done\",\n                ));\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// The inputs the reconcile wiring needs beyond the pull report (the Psyche\n/// turn's knobs — see [`spt_live::reconcile::ReconcileRequest`]).\npub struct ReconcileWiring<'a> {\n    /// The endpoint's registry rows (every instance, every node) — the\n    /// election input. Empty = local-only endpoint, reconcile here.\n    pub rows: &'a [Instance],\n    /// This node's id (pubkey hex).\n    pub self_node: &'a str,\n    /// Substitution keys for the Psyche turn (typically `session_id`).\n    pub keys: &'a BTreeMap<String, String>,\n    /// Turn budget (5.3).\n    pub timeout: Duration,\n    /// Wall-clock stamp for the merged write's marker.\n    pub now_ms: u64,\n    /// The 6.5 protection window for the merged (llm) write.\n    pub window_ms: u64,\n}\n\n/// Post-pull reconcile wiring (ADR-0013): when this node is the **elected\n/// reconciler** for the synced mind ([`is_reconciler`] — Active instance's\n/// node wins, lowest-id fallback), run the bounded Psyche reconcile over every\n/// conflict the pull surfaced. A non-elected node returns empty — its\n/// artifacts wait for the reconciler's dominating merge to arrive and clear\n/// them. Failures stay safe by construction: each file's outcome is reported,\n/// artifacts persist on any failure (hazard 6.6).\n// [impl->REQ-HAZARD-CONFLICT-BOTH-PRESERVED]\npub fn reconcile_after_sync(\n    runtime: &dyn AgentRuntime,\n    cs: &ContextStore,\n    report: &SyncPullReport,\n…\n) -> io::Result<Vec<(String, String, ReconcileOutcome)>> {\n…\n}\n\n[Showing lines 429-513 of 724. Use :514 to continue]"}],"details":{"truncation":{"content":"                                let _ = std::fs::remove_file(&bundle);\r\n                                return Err(io::Error::new(\r\n                                    io::ErrorKind::UnexpectedEof,\r\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\r\n                                ));\r\n                            }\r\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            for branch in &refs {\r\n                                if let Some(tip) = tips.get(branch) {\r\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\r\n                                }\r\n                            }\r\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\r\n                            // cutover): a sync pull moves `p-*` tips on the RAW\r\n                            // BranchStore — `apply_fetched`'s adopt leg can even\r\n                            // CREATE a p-* branch (a membership change) — and\r\n                            // none of it rides the ContextStore nudge funnel.\r\n                            // Nudge ONE global refresh per pull that applied any\r\n                            // p-* branch; the fingerprint gates what actually\r\n                            // rescans, and the debounce coalesces a multi-peer\r\n                            // pull burst. Without this, readers (index-only\r\n                            // since W3) would lag a sync by up to PERIODIC_MS.\r\n                            // [impl->REQ-PROJECT-INDEX-INVALIDATION]\r\n                            if report.applied.iter().any(|a| a.branch.starts_with(\"p-\")) {\r\n                                spt_store::projinval::nudge(\r\n                                    &spt_store::projinval::InvalScope::Global,\r\n                                );\r\n                            }\r\n                            return Ok(report);\r\n                        }\r\n                        SyncRecord::Err {\r\n                            sync_id: xid,\r\n                            message,\r\n                        } if xid == sync_id => {\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            return Err(io::Error::other(format!(\"sync refused: {message}\")));\r\n                        }\r\n                        _ => {}\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                let _ = std::fs::remove_file(&bundle);\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::UnexpectedEof,\r\n                    \"stream ended before Done\",\r\n                ));\r\n            }\r\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n            _ => {}\r\n        }\r\n    }\r\n}\r\n\r\n/// The inputs the reconcile wiring needs beyond the pull report (the Psyche\r\n/// turn's knobs — see [`spt_live::reconcile::ReconcileRequest`]).\r\npub struct ReconcileWiring<'a> {\r\n    /// The endpoint's registry rows (every instance, every node) — the\r\n    /// election input. Empty = local-only endpoint, reconcile here.\r\n    pub rows: &'a [Instance],\r\n    /// This node's id (pubkey hex).\r\n    pub self_node: &'a str,\r\n    /// Substitution keys for the Psyche turn (typically `session_id`).\r\n    pub keys: &'a BTreeMap<String, String>,\r\n    /// Turn budget (5.3).\r\n    pub timeout: Duration,\r\n    /// Wall-clock stamp for the merged write's marker.\r\n    pub now_ms: u64,\r\n    /// The 6.5 protection window for the merged (llm) write.\r\n    pub window_ms: u64,\r\n}\r\n\r\n/// Post-pull reconcile wiring (ADR-0013): when this node is the **elected\r\n/// reconciler** for the synced mind ([`is_reconciler`] — Active instance's\r\n/// node wins, lowest-id fallback), run the bounded Psyche reconcile over every\r\n/// conflict the pull surfaced. A non-elected node returns empty — its\r\n/// artifacts wait for the reconciler's dominating merge to arrive and clear\r\n/// them. Failures stay safe by construction: each file's outcome is reported,\r\n/// artifacts persist on any failure (hazard 6.6).\r\n// [impl->REQ-HAZARD-CONFLICT-BOTH-PRESERVED]\r\npub fn reconcile_after_sync(\r\n    runtime: &dyn AgentRuntime,\r\n    cs: &ContextStore,\r\n    report: &SyncPullReport,\r","truncated":true,"truncatedBy":"lines","totalLines":296,"totalBytes":4406,"outputLines":85,"outputBytes":4406,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn request_sync(\n…\n) -> io::Result<SyncPullReport> {\n…\n    loop {\n…\n        match ev {\n            BrokerEvent::NetStreamData {\n…\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n…\n                        SyncRecord::Done {\n…\n                        } if xid == sync_id => {\n…\n                            if got != total {\n                                let _ = std::fs::remove_file(&bundle);\r\n                                return Err(io::Error::new(\r\n                                    io::ErrorKind::UnexpectedEof,\r\n                                    format!(\"bundle incomplete: {got} of {total} bytes\"),\r\n                                ));\r\n                            }\r\n                            let tips = cs.branch_store().fetch_bundle(&bundle)?;\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            for branch in &refs {\r\n                                if let Some(tip) = tips.get(branch) {\r\n                                    report.applied.push(apply_fetched(cs, branch, tip)?);\r\n                                }\r\n                            }\r\n                            // PROJECT-INDEX W3 (the W2-carried gap, ruled at\r\n                            // cutover): a sync pull moves `p-*` tips on the RAW\r\n                            // BranchStore — `apply_fetched`'s adopt leg can even\r\n                            // CREATE a p-* branch (a membership change) — and\r\n                            // none of it rides the ContextStore nudge funnel.\r\n                            // Nudge ONE global refresh per pull that applied any\r\n                            // p-* branch; the fingerprint gates what actually\r\n                            // rescans, and the debounce coalesces a multi-peer\r\n                            // pull burst. Without this, readers (index-only\r\n                            // since W3) would lag a sync by up to PERIODIC_MS.\r\n                            // [impl->REQ-PROJECT-INDEX-INVALIDATION]\r\n                            if report.applied.iter().any(|a| a.branch.starts_with(\"p-\")) {\r\n                                spt_store::projinval::nudge(\r\n                                    &spt_store::projinval::InvalScope::Global,\r\n                                );\r\n                            }\r\n                            return Ok(report);\r\n                        }\r\n                        SyncRecord::Err {\r\n                            sync_id: xid,\r\n                            message,\r\n                        } if xid == sync_id => {\r\n                            let _ = std::fs::remove_file(&bundle);\r\n                            return Err(io::Error::other(format!(\"sync refused: {message}\")));\r\n                        }\r\n                        _ => {}\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                let _ = std::fs::remove_file(&bundle);\r\n                return Err(io::Error::new(\r\n                    io::ErrorKind::UnexpectedEof,\r\n                    \"stream ended before Done\",\r\n                ));\r\n            }\r\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n            _ => {}\r\n        }\r\n    }\r\n}\r\n\r\n/// The inputs the reconcile wiring needs beyond the pull report (the Psyche\r\n/// turn's knobs — see [`spt_live::reconcile::ReconcileRequest`]).\r\npub struct ReconcileWiring<'a> {\r\n    /// The endpoint's registry rows (every instance, every node) — the\r\n    /// election input. Empty = local-only endpoint, reconcile here.\r\n    pub rows: &'a [Instance],\r\n    /// This node's id (pubkey hex).\r\n    pub self_node: &'a str,\r\n    /// Substitution keys for the Psyche turn (typically `session_id`).\r\n    pub keys: &'a BTreeMap<String, String>,\r\n    /// Turn budget (5.3).\r\n    pub timeout: Duration,\r\n    /// Wall-clock stamp for the merged write's marker.\r\n    pub now_ms: u64,\r\n    /// The 6.5 protection window for the merged (llm) write.\r\n    pub window_ms: u64,\r\n}\r\n\r\n/// Post-pull reconcile wiring (ADR-0013): when this node is the **elected\r\n/// reconciler** for the synced mind ([`is_reconciler`] — Active instance's\r\n/// node wins, lowest-id fallback), run the bounded Psyche reconcile over every\r\n/// conflict the pull surfaced. A non-elected node returns empty — its\r\n/// artifacts wait for the reconciler's dominating merge to arrive and clear\r\n/// them. Failures stay safe by construction: each file's outcome is reported,\r\n/// artifacts persist on any failure (hazard 6.6).\r\n// [impl->REQ-HAZARD-CONFLICT-BOTH-PRESERVED]\r\npub fn reconcile_after_sync(\r\n    runtime: &dyn AgentRuntime,\r\n    cs: &ContextStore,\r\n    report: &SyncPullReport,\r\n…\n) -> io::Result<Vec<(String, String, ReconcileOutcome)>> {\n…\n}","startLine":335,"lineNumbers":[335,null,342,null,380,null,388,389,null,393,394,395,null,414,null,418,null,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,null,515,null,550]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\sync.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":724,"totalBytes":4406,"outputLines":85,"outputBytes":4406,"shownRange":{"start":429,"end":513},"nextOffset":514}}},"isError":false,"timestamp":1784272245946}}
{"type":"message","id":"5e73ef2e","parentId":"3f371107","timestamp":"2026-07-17T07:10:57.410Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Designing seat-based done counters**\n\n**Specifying public stream lifecycle stats**"},{"type":"toolCall","id":"call_zE6w77M153uCPTTDuJ19y5I8|fc_09d4a4b3f8eb22c9016a59d57ff5e08196b1c34ef756cf07ac","name":"grep","arguments":{"i":"Locating handler count visibility","pattern":"conn_handler_count","path":"crates/spt-daemon/src/broker.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5550,"output":313,"cacheRead":133632,"cacheWrite":0,"totalTokens":139495,"reasoningTokens":253,"cost":{"input":0.027750000000000004,"output":0.00939,"cacheRead":0.066816,"cacheWrite":0,"total":0.103956}},"stopReason":"toolUse","timestamp":1784272245959,"responseId":"resp_09d4a4b3f8eb22c9016a59d5766ba48196a074aa9fc409ad16","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_09d4a4b3f8eb22c9016a59d57ab50481969bd2e57a7ebcc1d2","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdV_oC4w_Aac1iIiqnObckPaP_s6_Ygmg9H6X1RLeHLJcwmeSKTBjiGnsoEOQ9wQU7OOOeV4DsY2pBTdwAsvmVg9BeVoQjH5R4h3XBl1dT1R_OtGnaekbggnWd6FkvDX6F0dM61m9VJAsC_lMytWVw4b9WcqENd_tzmXwn9cWyngMRgKb7Vn9uf7CCB97EsLZJUSho5ruYxQsK0MixTi-H2-xNXVupWhHkoOpHFruiBtdOdFt1bAtITsPzGdYFZg-2bH39uiLwz0OP6FNtzIVSxI8Mcnq6b_8Ir1tmT4TLJjKP1q41CyxS76j_5oczjnjhJlYL2b-j8kTFCqPQgs1QMg-2pDsvf-pvYuv23drDGXjAm-fmeUs_nKBKku9lAqqyimHTnqTry6VIRFZ5k7VZHrdvOwKxh-mWoTL2KS4-mraDeDcnz69I2_9ify9oDWUKujJkkZ_c6S1YNGu7Nkvx2WxUAkjqgR55Usc-aEB_sFjG2WtZLwHopn5_QBto_rmK5gtYY-I73bp5pVO3NJFMuEp41HlPbwySXhmxfhg1OGpPLe1WarzZ4NP6qD-CTbI1us8W_dN29d0ct4e-3QhJ6JPlYvwAqfieOHlxeXUfvDG4jOmpWBie_nYj-l1wqjXifnvO8FRu4PT-mFfyrgNBabW1hOZuT4483TR_qZGfM01G9WZjGUYXuJYUvC0IIw3ceCxhgQ2DxdGjmSh1yOBK5VrDisqlPt_75v0tj72lZa7OEMZTXTSL_i54IteqCS-vOCFnAt-KuZAzooDKWFUk7Uwgke1gVWKCXCDWu2_pjQ5nmSe8Z_7OGzxbQzSzsmQ9zGQeV7ensUyNFPafwqD_Fvq3tWgn5qSap22LIVG-9WqF3-I_6vFguqsLPIfYFcfDrTy1qLvDCBIeEIZ4XtTowMGG1k4Hj3lbx4XuAvOakMJd1xoKN8lrqXMcPN1Uf_7ToiAK4pm4Dsg3Hwt7-DQEB_3eFa8A0UUfG7xEyDsiiUKfXO8Zm3ISLpQ56ZpB42whdPS_VnFfgSQ32laROIgHqPIA-nuHo7VhDw6BU8QgpgrVRknuX79i0CdSBoWllEbDZ78y9C3PIYhX43L1MTW9pFZ3ZE5nswGwFCLxQoVTbu3NQmknQa9nD4871l-dbPpaRXP4U6BlAnoDDKRDurXBSqBtlX4NIFFI8FEt7R7B5iHoYkZI_JG9f_EyEf9E4ProvZ9AHabLRm8JVGfok1J7rZ2lK-lNgunX9Sui8CTYfkRcwbxRwuULIdlIe_52KHtfNRCDHiU2-KKuWMjqKkQwA5q8-cM4RpLGV7bdzkpNlW4cg8buvFzodq4507aG6mazM2mi75y3zhasTc8qgpQpQDcqgFb6c7nIJ93BvBBthsoThxogEry4BBnedbPoUYmZS9adtWSumPdam_NJPgfLz7YE8aw5dSlrFvlE7MvWDpOeG7p01mRgd9X9asJ0r82f6sv51PVNWp9gxKnbAWiDusgKC7iFJfcONsN0q522VMikbeqUwVbcFXKfvVdmk85km62HIS5saVhGShTtu_7ZlAuxnPvNUisN-IZZoogNSPUZteOwc5_fhCmBrnIro2voT-u4hbLsLw-Mox8HH5wHkbG82ckRMclPKy3kzOIU2QQriaWOv_dK6PpJlaW9V8TtbOa4CUXxCRu0WVrlSsJC8pHrzkRsEkLj24oyi0fprmDha-1iW5egRP1fXzXdkblfaadBL7u7ELRRl74L6zmZN2EHNspHWRnN4-ThpCr4ZYrgbmbvVU-noA7D2Jj23xegXTWid_tVpqNoQEtM3DHL5ezDnHOirq3NbNA6oqbmOOI7uK8ZnaPnr58xwakXdk7odUvml0A0euLLeSFKt9iS4lf7hdpPdPMpTzTgOjBXVWF3oZgIfAyeH9oyDL17dYgTUQ9FipCDJVr2-YpRFK_6pfDZVwNF80I5ayun71iN3tq5TMsOBcWu6Z9p24BUa_ieL5TwTV3CpLD5l7CvlldcSXvMA9rn4KDrTydUuFWrhZjBvVvVJn8QGyymdS2vHLKecY_cLWoGAYXNJI22RN8NDKM6aTKHk85vIxK4Sxjnw8a9QYLOAKPERagNoVfrOgBa_YSTxukrLLyOi3w42jUipwMN25s8_QGJfKzxBMfuOEycCP5kYRcF8C9vO41PCUg1tVSRdUX4BO0I3Q8sRunnK0dzNBgW4j94QiKjmAUGlxnRCB9dPXukpKmzaWwJTLZiUW1WsBEA4qOyd-vI-XL2_omz3lx0rXHdypr4gWDOT-3ASagAuqrJxiCQjc14wGaGdfuC2XSuxe5zwbyq1qGX1KnURMGK6C0pWGmuDUxweo8GUDGJeX4vBmg-9p7s1Onfxak6w_4mmMQfCb_u6Aiiqkb7U4mMqhVIXhcyWu64dojONRjmj20Rgbt2Jxr2DJ7pjz5nA99yVqYXxPio0wTELy_7UL2JIR_UTc7oc259jvQW9g0H9o4IanvFUTEgrSeNX9bjNgqpV5qDhdOPjnB5TRitZzTmSJTHoJv4m2T8wy78I=","summary":[{"type":"summary_text","text":"**Designing seat-based done counters**"},{"type":"summary_text","text":"**Specifying public stream lifecycle stats**"}]},{"id":"fc_09d4a4b3f8eb22c9016a59d57ff5e08196b1c34ef756cf07ac","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating handler count visibility\",\"pattern\":\"conn_handler_count\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_zE6w77M153uCPTTDuJ19y5I8","name":"grep"}]},"duration":11449.949000000954,"ttft":4775.460000008345,"contextSnapshot":{"promptTokens":139182,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_zE6w77M153uCPTTDuJ19y5I8|fc_09d4a4b3f8eb22c9016a59d57ff5e08196b1c34ef756cf07ac","toolName":"grep","startedAt":"2026-07-17T07:10:57.410Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Locating handler count visibility"},"id":"17a525a6","parentId":"5e73ef2e","timestamp":"2026-07-17T07:10:57.410Z"}
{"type":"message","id":"92de0343","parentId":"17a525a6","timestamp":"2026-07-17T07:10:57.411Z","message":{"role":"toolResult","toolCallId":"call_zE6w77M153uCPTTDuJ19y5I8|fc_09d4a4b3f8eb22c9016a59d57ff5e08196b1c34ef756cf07ac","toolName":"grep","content":[{"type":"text","text":" 2921|    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2922|    pub fn conn_handler_count(&self) -> usize {\n 2923|        self.conn_handlers.load(Ordering::Acquire)\n 2924|    }\n 2925|\n...\n 3026|        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n*3027|        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028|        struct HandlerTally<'a>(&'a AtomicUsize);\n 3029|        impl Drop for HandlerTally<'_> {\n 3030|            fn drop(&mut self) {"}],"details":{"scopePath":"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\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":2}],"truncated":false,"displayContent":" 2921│    // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*2922│    pub fn conn_handler_count(&self) -> usize {\n 2923│        self.conn_handlers.load(Ordering::Acquire)\n 2924│    }\n 2925│\n     │...\n 3026│        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n*3027│        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n 3028│        struct HandlerTally<'a>(&'a AtomicUsize);\n 3029│        impl Drop for HandlerTally<'_> {\n 3030│            fn drop(&mut self) {"},"isError":false,"timestamp":1784272257411}}
{"type":"message","id":"51fa95f7","parentId":"92de0343","timestamp":"2026-07-17T07:12:46.196Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Designing explicit net-stream-unsubscribe handling**\n\n**Planning explicit net_stream_retire and cleanup**\n\n**Verifying stream entry lifecycle and thread cleanup**"},{"type":"thinking","thinking":"**Tracing connection churn and worker spawning**\n\n**Assessing thread explosion regression in v0.36**\n\n**Identifying incomplete stream retirement in v0.36**"},{"type":"thinking","thinking":"**Planning unit test for subscription cleanup**\n\n**Designing detailed pump refresh regression test**"},{"type":"thinking","thinking":"**Analyzing thread scheduling and connection management**\n\n**Evaluating connection handler and subscription counts**"},{"type":"thinking","thinking":"**Identifying seat leak causing broker thread buildup**\n\n**Assessing dispatcher refresh and mixed version impact**"},{"type":"toolCall","id":"call_IFHLmYEab6i7WgJl49gsppC5|fc_05baf4dfc6c4b2e8016a59d5b4f20481949c05d35308fb7678","name":"yield","arguments":{"type":null,"result":{"data":{"summary":"High-confidence release-regression assessment: v0.36's REDISPATCH subscriber isolation converts a pre-existing logical subscription-lifetime leak into one parked OS thread per completed pull. `SubscriberSeat::install` spawns one writer thread per `net-stream-subscribe`; after replay/live EOF that thread does not terminate—it waits on `rx.recv()` until its owning `StreamLog` drops the seat. The long-lived peer-pump `Brain` reuses one broker IPC carrier across every sync/update pull, subscribes each newly opened response stream, and never unsubscribes. Broker-side `handle_conn` retains every subscribed stream ID until the entire pump carrier reaches EOF; `Brain.net_cursors` likewise never removes completed IDs. Healthy QUIC connections retain the corresponding `StreamEntry` rows until the entire peer connection closes. This violates intended O(active) behavior in four coupled dimensions: broker subscriber-writer threads, broker handler subscription IDs, brain cursor entries, and broker stream rows all grow O(completed pulls). Approximately 532 broker threads with only four CPU-active is the exact expected shape: hundreds of unnamed seat writers parked in `std::sync::mpsc::Receiver::recv`, while only the dispatcher/pump/net runtime threads work. It is not explained by current dispatcher worker concurrency (`MAX_DISPATCH_WORKERS = 8`), NetHost Tokio workers (fixed at 2), or 4–5 hosted PTYs.\n\nA second, independent O(time) defect amplifies CPU/I/O: the pump opens and FINs a fresh locally initiated Registry feed stream per peer every registry round, but locally initiated rows are never dispatched or retired. The dispatcher nevertheless asks the broker for the complete non-retired stream table every 100 ms; only after transferring and deserializing the full reply does it skip `initiated_locally` rows. Thus a healthy long-lived peer connection makes `net-streams` replies grow linearly with runtime and the fresh post-refresh dispatcher rereads the entire historical local table at 10 Hz. Incoming Served rows are hidden by v0.36 retirement, but retirement intentionally does not physically remove their `StreamEntry`; physical stream memory remains O(time) until peer-connection close.\n\nReplay behavior is bounded in concurrency but not always bounded in lifetime. Ordinary transient failures have three total attempts with 500 ms/1 s retry backoff and then become terminal. `SeatBlocked` failures intentionally do not consume that budget; they requeue indefinitely behind a global 2/4/8/16/30-second circuit breaker. Any successful sibling serve resets the breaker to 2 seconds, so a poisoned row mixed with healthy traffic can replay forever at roughly two-second intervals. This is a paced replay loop, not a CPU-tight spin. Cold-backlog connection churn is also possible: a worker can use a temporary opener-query connection, a serve connection, and a success-retirement connection. Although admission is eight workers per 100-ms poll (up to 80 newly admitted workers/s), `active_workers` is decremented before retirement/telemetry/claim completion, so slow completion tails are outside the advertised pool bound and can transiently accumulate broker handler/watchdog pairs.\n\nThe observed brain read rate has a separate, exact source and should not be attributed to stream replay: every 500-ms brain heartbeat calls `write_ready`, which reads and SHA-256 hashes the entire current executable. Predicted read rate is `2 × executable_size` per second; 63.91 MiB/s implies a 31.955-MiB executable. Registry session-ledger scans occur only on registry rounds and each ledger is capped at 64 rows, so they do not fit the continuous 63.91-MiB/s signature.\n\nTargeted regression seam: keep one real pump Brain carrier and one real QUIC peer connection alive, run many fast non-empty sync pulls plus Registry pushes through production pump/dispatcher code, and never disconnect between rounds. Add a broker/NetHost lifecycle-stats seam reporting physical rows, eligible rows split by direction, installed subscriber seats/writers, and per-handler subscribed-ID count; add a Brain cursor-count seam. After warm-up and N completed rounds, assert all counts plateau at O(peers + in-flight exchanges), broker thread count remains at baseline + O(active), locally initiated eligible rows do not grow, and a serialized `net-streams` reply remains constant-sized. Then perform the production brain refresh boundary: assert the old pump carrier's seats/cursors/handler IDs fall to zero before the new generation runs and that the new generation again plateaus rather than restarting linear growth. The essential discriminator is that the pump carrier must remain open during the first assertion—closing it would invoke existing EOF cleanup and hide the bug. Existing REDISPATCH tests cover historical Attach retirement, rolled opener reconstruction, poisoned replay, retry classification, and one-poll worker admission; none exercises repeated completed requester subscriptions on one live carrier or asserts lifetime plateaus.\n\nFalsifiable live probes:\n1. Decompose broker threads by stack/name. The subscription leak predicts about the excess count blocked in `mpsc::Receiver::recv` from `SubscriberSeat::install`, only two `spt-broker-net` threads, and relatively few `conn-watchdog`/`handle_conn` threads. If instead approximately half the excess are `conn-watchdog` and half are `handle_conn/read_frame`, physical IPC connections—not seats—are retained.\n2. Compare `Broker::conn_handler_count()` with total broker OS threads. Low handler count plus ~500 excess parked threads confirms seats; ~250 handlers plus ~250 watchdogs falsifies it and identifies connection retention.\n3. Instrument/count `net-stream-subscribe` calls on the long-lived pump carrier and active seat writers. Predicted slope is exactly one broker writer thread and one Brain cursor entry per completed subscribed pull. The slope tracks non-empty sync/update pulls, not hosted-session count directly.\n4. Trigger a brain refresh and sample at sub-second cadence. Existing EOF cleanup predicts a sharp collapse of the old pump carrier's seat writers when `handle_conn` iterates `my_stream_subs`; failure to collapse means old-carrier EOF cleanup is stuck or the old brain remains alive. Stream rows may remain because peer QUIC connections are broker-owned and survive refresh.\n5. Sample `net-streams` twice Δt apart and split rows by `initiated_locally`, `finished`, and family/opener. Registry-only local-row growth should be approximately `live_peers × Δt / 30s`; notification feeds add their own 15-s cadence. Measure serialized reply bytes B: with breaker closed, dispatcher traffic predicts about `10 × B` broker-to-brain bytes/s. A 30-s breaker window should suppress this 10-Hz component; no I/O fall during that window falsifies enumeration as the measured broker-write source.\n6. Group `DISPATCH_EV` by stream ID. Transient failures must stop after attempts 0,1,2; SeatBlocked repeats preserve the attempt number and follow breaker windows. Repeats substantially faster than the breaker window would be a true spin and contradict current source semantics.\n7. For the brain read counter, measure executable size S and a 20-s `ReadFile` delta. Prediction is approximately `40 × S`, with ProcMon reads targeting the running executable and `brain.ready` updating at 500-ms cadence. If paths or rate disagree, executable hashing is falsified.","files":[{"path":"crates/spt-daemon/src/nethost.rs","description":"Lines 154–223 implement `SubscriberSeat`: every install spawns an OS writer thread; after replay it waits on `rx.recv()` until the seat's sender is dropped. Lines 430–607 show EOF only enqueues a final frame and `begin_attach` stores the seat in `StreamLog`; no terminal-frame auto-detach exists. Lines 949–970 remove all stream rows only when the whole QUIC connection closes. Lines 1000–1068 register each stream/read pump. Lines 1804–1853 enumerate every non-retired row and implement retirement as visibility plus ring clear, explicitly retaining the physical entry/subscriber. Lines 1967–1990 expose detach only as brain-disconnect cleanup."},{"path":"crates/spt-daemon/src/broker.rs","description":"Lines 3025–3290 implement per-IPC-connection handling. Every successful `KIND_NET_STREAM_SUBSCRIBE` appends the stream ID to `my_stream_subs`; those IDs are detached only after the read loop ends because the Brain carrier disconnected. Lines 2921–2924 expose `conn_handler_count`, a decisive live/test discriminator between seat threads and retained IPC handler/watchdog pairs."},{"path":"crates/spt-daemon/src/brain.rs","description":"Lines 271–276 define the per-stream `net_cursors` HashMap. Lines 813–830 advance entries while reading stream data. Lines 1590–1605 insert cursors on subscribe and provide no completion removal/unsubscribe operation. Pump-mode construction additionally owns one reader thread, but that is one per pump carrier, not one per stream."},{"path":"crates/spt-daemon/src/pump/mod.rs","description":"Lines 519–593 construct two long-lived pump-mode Brain carriers and reuse the main one across all peer steps. Lines 1109–1123 open+FIN fresh fire-and-forget feed streams. Lines 1126–1142 show pump connections use `Brain::cold_start_pump`. The scheduler marks completed rounds and sleeps when idle, ruling out a cadence tight spin."},{"path":"crates/spt-daemon/src/pump/registry.rs","description":"Lines 158–176 encode one Registry feed per peer/subnet and call `push_feed`; combined with the 30-s default this continually creates locally initiated, finished, non-retired stream rows."},{"path":"crates/spt-daemon/src/config.rs","description":"Lines 39–42 define default cadences: Registry 30 s, Notif 15 s, Sync 60 s, Update 3600 s. These values make thread/row-growth slopes quantitatively falsifiable."},{"path":"crates/spt-daemon/src/sync.rs","description":"Lines 335–346 open and subscribe every requester-side sync stream on the reused pump Brain. Lines 430–487 return immediately on the protocol `Done` record, before stream EOF cleanup, without unsubscribing, retiring, or removing the Brain cursor. This is the dominant periodic source of permanent seats on the live pump carrier."},{"path":"crates/spt-daemon/src/propagate.rs","description":"Lines 355–356 and 557–558 repeat the same open+subscribe-without-unsubscribe pattern for update pulls and status queries. Lower cadence makes this a secondary contributor but the same invariant violation."},{"path":"crates/spt-daemon/src/dispatch.rs","description":"Line 87 sets the 100-ms poll. Lines 179–341 define three-attempt transient retry versus unbounded-but-breaker-paced SeatBlocked retry. Lines 429–563 fetch the whole `net-streams` table before filtering local rows, admit at most eight active workers, decrement the active gauge before completion-tail retirement, and retire Served incoming rows. Lines 639–1048 show each worker's opener query/fresh serve connection and Registry serve loop."},{"path":"crates/spt-daemon/src/conn.rs","description":"Lines 389–453 and 638–661 implement one named watchdog per physical broker IPC connection and join it on `BrokerConn` drop. `CONN_LIFECYCLE` write-start/transport-close deltas and thread names distinguish connection churn from the unnamed SubscriberSeat writer leak."},{"path":"crates/spt-daemon/src/brainproc.rs","description":"Lines 62–63 set a 500-ms heartbeat; lines 296–297 call `write_ready` every heartbeat; lines 365–384 read and hash the entire current executable on every write. This predicts the observed ~63.91-MiB/s brain reads independently of REDISPATCH."},{"path":"crates/spt-store/src/sessions.rs","description":"Lines 30–31 cap each session ledger at 64 rows, ruling out unbounded session-ledger scans as the continuous brain-read explanation."},{"path":"crates/spt-daemon/tests/redispatch.rs","description":"Existing production-path coverage for finished historical Attach retirement, active Attach survival, durable opener reconstruction after ring roll, and bounded transient retry. It does not keep one requester/pump Brain alive across many completed subscriptions."},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","description":"Existing T1–T7 coverage for poisoned replay, producer non-blocking behavior, lease cancellation/recovery, and mixed-image fallback. It tests incident mechanics but not long-lived pump subscription/stream-count plateaus."},{"path":"crates/spt-daemon/tests/mesh_recovery.rs","description":"Best existing integration harness for the proposed regression: it already runs real brokers, real QUIC, production dispatcher, and production pump with accelerated cadences. Extend it with repeated non-empty sync/Registry rounds and lifecycle gauges while deliberately keeping the pump carrier connected."}],"architecture":"Causal graph:\n\n`run_peer_pump` keeps one Brain IPC carrier alive → each periodic non-empty `request_sync`/update request opens a fresh QUIC stream and calls `net_stream_subscribe` → Brain inserts a permanent cursor; broker handler appends a permanent `my_stream_subs` ID; `StreamLog::begin_attach` installs a `SubscriberSeat` and spawns one writer thread → protocol `Done`/EOF completes the logical exchange but no unsubscribe removes cursor/ID/seat → writer parks forever on the empty channel while healthy pump IPC and peer QUIC connections remain alive → broker thread count grows O(completed pulls), almost all sleeping.\n\nIn parallel, every fire-and-forget Registry/Notif push opens and FINs a local stream → no dispatcher claims local rows and no requester retires them → broker stream table grows O(rounds × peers) → dispatcher refresh reconstructs an empty claim map and polls `net-streams` every 100 ms → broker clones/serializes and brain reads/deserializes every non-retired local historical row before filtering → sustained small-core CPU and broker IPC writes with little daemon-log growth.\n\nIncoming historical rows follow a different path: fresh dispatcher workers classify from the pinned opener, subscribe/replay, serve, then mark Served and issue best-effort retirement. Retirement hides the row but deliberately keeps physical state until connection close. Ordinary errors terminate after three attempts; subscriber-seat errors are retry-forever but globally paced. The worker pool bounds serve bodies, not all completion tails, so a cold refresh can create short-lived IPC churn even though it cannot explain hundreds of permanently parked threads by itself.\n\nA brain refresh is currently the only reclamation boundary for pump-carrier seats/cursors/handler IDs, because carrier EOF runs broker detach cleanup. It does not reclaim broker-owned stream rows riding healthy peer QUIC connections, and the replacement brain immediately primes every pump leg and reconstructs dispatcher polling. Therefore refresh can temporarily drop thread count yet preserve/re-expose the table-size cost and begin the same linear thread accumulation again."}}}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5013,"output":5655,"cacheRead":134656,"cacheWrite":0,"totalTokens":145324,"reasoningTokens":2468,"cost":{"input":0.025065,"output":0.16965,"cacheRead":0.067328,"cacheWrite":0,"total":0.262043}},"stopReason":"toolUse","timestamp":1784272257448,"responseId":"resp_05baf4dfc6c4b2e8016a59d581eefc819498581ad711d6f460","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_05baf4dfc6c4b2e8016a59d58454208194907f2c03689e26a6","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdWQ_6p85YMaZEweZVE5b2tPsVGdPGU-Ep-IzDnYT5mFKArqCrYQ01BoyEHaZUl7cu2yIC-EvClLJ3lO1vkHFVTSAN7IVb_wR0RTVijNjxs0tM4OONNVFvVN8rq4dYfdFnbwZludS4yemrv9uOhgLLbXsYFTy72GQWPpGdmpYxIu08f02iOHjpZS2JpMJIxGYQvi1gkR0TrvTKJWbGo6z6hlImy7Lc1RBX2caosOUra1d7rpceiHNE2EidZLgPkHjpsMha_2UORJPYTVlnLBVXgdFdAgnaR5pl91V8dwyAY6cF-oBItz8swKmpdHEvQVdW9cGnX6yAlAkCHWaKF9VylcRvAVVzRhnlx5pVLyUy_Cdy6NRYSY2qDfrpcjQNX5zXEuM9SIGPFK3FVHoagE1--Zg5YZ8QHIM4OV5WWgPZZfV9evUrszCYX0IgrH6lLrlU1XdsNjGVtxGMG7rIPg91S1fVgJlHzACs4x98FLS6s1gbBktQOYdx_M--6KPLf4HQeYpDoBhgOrDH49EV-7xAUDTN9iSdjKrVEJQo0VcEqT0KYL8KN6W0Ajyecs51cE7MjDa7PzK9XlaXDun-u7M1pPmE_REyakoVyIL_93GYOZV-RoFtEGrTtK4bw3_IPvgp-pz_UwNI0Wje1f0oifkVHx_kwie0gHsFzrTi6FTLuPp221tT2ub-Q4qmKdriUGJsV5EAyM7XlIonfODr7GijPb52SINt18cxYEFHOzmrFPG9dNjuGLKAbG1KpxB3laagwpfRcb0TQ9yeMdgyzsBJc4JNo0Ue1RJsnviBl247yhqk9BPWXsL7QW16iy0VS4rDhtvsk6VH0meCJfAB6VUwLAwb0j26CM3YIcRensXugyL7YWn-irKiZouPMXBwSoYCqkADgTHkM9vtC7cbbL5WSUpJs0436sQYnnobLWvZdYI0lCMEeKVX-zfKF2RqL6Aths-6qvIA9WClQjuvBv1-tiO3elIu-2Ze4cQnIqh0bOiF56sOd-JAZOF35C8jZSqpKEFhOjWONIQLNMo361rpMkciaMuoyi_rRkppTvwG1RyhKswiiGOh9BqilDUZL5rGU2Qh0mlzzMtQwSMZnutS9a941sa8q7QjK7I4Qasno4mNIiUZnWde3nhv8SMtZG3mR58yQbwHzjf_LiATxomvPQq4XNykXHwj1G3OXg1VYf5WRd1r4uS1ia5c3dPFtcpIEcK5uiZBdNISVkK0HjXcqyPJYaKBJIhj5sU4jYyJhdT4_GLEkwv80pWRfHI1dLiAxbZoZNP0pD4ESa7eB6Y2ob0zPfCHwxr956Pz4d_9gImmh4PhB893cL2VdxofKdvgUnKFljTL4w-jsHl6otEobmFF5yVcIpwVILqs8oWAkC98qBhxfTjyRhK7qD66WZXVPb2stcrTpfjKeHZw0_LARKbLoVlBg9w0q8Qms7Frc_4BujkeaVlo41XywBl0MJ9uiZAwgu06mr0DBQjpLaS2Hr2aHBbjbI__sBe4ICdZXTPcF1yvpD-a6XXyE39929a6P9mncWqagSrOSXbxWimZnUXuzFf7J_7TQpr5JTtkC5USIT5unJFask1pam3wGvcKnp8dL2VUCOn0a_4XkXdxPgRq5e7QjTe_qWtnzECAd5cviMbyc4BJ6hOq_siawNpZ3vpw_1QWKSgw_buYxDsMPOBykPutIe0Ni20QlTEJbqa1EiWNv8ddR18fEKK0zCeIhV-wBYOoHvHPw8XGOUmSGuVHek-ys1utt5AIKf1iHyQmsh5gNZmKMUo5L0lWwbfFTdoGxgvny3j4-hMbkRucr52J4LDUyFczVwFItHnZYdbLns33hcinte6vqEhcJggFoIHvikvp9ONlwP9cMbpjDKLL1Dupt6TxC92gfY3S66pwy0-tLqGA--BsHtxwkc-q2ZXyjAJc0lUJ0ftG7eC-hvEk_QNuD9HehzlaucmBFIgetMkbbD-SDWsKVFZg8oXZ2FkR0VaVJ9swsxgBUC4Lp1D8dZZPnWXDpguEKlaUML1ufLoT0N_raFuKK78RIquZjpzln8VEEMbdNf0jCRWEcJjFj-oeX63AEGm8fIGWCOWBU8DPmOkkpfY6Xd85_m4h-WmH6zgHtqmfIWmhB7nacK0rLsTL1h8WJcrPlOALu0Jpy31JsNjbzBALhzoGD32BgvQRhje6raABz-ziWFOiTxNzUOppEVNt9r2t7yMy2hwyrTBPL3uKu4R0oKqy8OoWjX4cBS2AgShpYoY8y9kZgdFPP8xPpOI7ydv-eXsOlPimD0dD-ReLHSbFQCQcdpyUdrdcdoixpHWD88v99XXiziOw7UClgjnIl-VB6POPiV3kMK-WFhXJg1iHXETmKDe5K3YtiB1LZzC8HhkDYTrLiBWrtYb7mn6wlcD6jpAOof3ZF16JzKDhmhGp9iZKJG7RWMxX85zRmXr4SzcFIGSelTHsGo1Qebz0QhN29fWKfdTaoNrtZdFfjr-Zxe-pVnSRQTaRGw4-NInsnf5uhJ4zLn7419zO0zfyvGAViq_sz0rL5mIo_vRT2v-d0cpRO4kmPLO-uf0dMmE44JpYF6tv-hgJ3l6hs5g3wlLbTlymmAqH_RPF6io1SCwICOZjvmksqYcJgxmZjzuO1c2Ufc9c3LMO_5X82zznLEiiujWazCqHA96kK5P3VRCB81O9TUaYIPHgORtyU2HO8BFikTaBXh8FDsBv9dpLFGXSXSjpwpMeu0ogyFo5rjnvtOwsxYXv9DshksHeMb_wxBADl5NZQl3yPWUa2SCGZHsPsN1UnIJtVesW87XFXh9rAyIW_IKa6m8lavyAGoITADaefOzI_MDNOBMEnYjdYElXriO8t2cmBHTCb1zNDUKfxbdQhz14clV6aF6qO7_7D9IAoS2sh5zrt6_UuHKozObUttPbxNj4rMOsFhSbuQbsBTxUuJYrjlxriAhIxAtmGNdYv_l_Ujvj0aht4O6_AkKQTQCBDZ-onCZY9RQBvg1r8ZrFDJuQA0rcbqv4M8J5Jr3Dyp_czOU9O4Ke_-MV6vtJ6GTFMktVHAIAJRahPx8fP8-TcArYN00BBzUQS6iyqPap6ki7avuun52n47_OufkMdVkQNC1594d9MpLtM16VcEfdrzhd3AVYB_uoD_S3aFEecl6irzLxyeG0ijuGK-uNTzEJjPchABe5B-dWnCP-64FE1ogOrlFkX30OTuVthudR8AqsO0u1mup1BdZMQviL7OU7zS7rlR0QpmDnzMrBVwEqxqmncpSBVYLC3Dyf3kTYcfSxHZY2VoWAjwE1z3H_fLwssTOvI0KXPixtG1Ag3fZIaWZA7FEDakPhcITkUS9nrshXl5NHtM3OgurZt60pmLe-8XB6yD4t1QrkLAuLX5WngvYBPY0ZIX22z66hw5PxKCH2KMHOR_VGJtGNCMsERFE7OF7fY8KgYrgJ9hmlQ2mpKZb7XLPFxgYHneMueS5zPvqCMgdXfnVXzdYv8CfQJDJTdi9W0BHXoSRauMFXtGByQlVdKA1V9rMy9nsBX3-wcqD16bWVhH1MC7Ym7tV4Ft65fWRAx-S2GRGYcPsT_Fn2in9ouNRuCoWamZ2hELdv34HadksFUJPEGzTlO34lDWuNS5SHtA3QBYVQMKXtdjUjR4OVSEiNBWC8U_T5X_O6TTU3qMRW-X-a2D6CpWZPuRaUxxnSQ10kXR26zQquOG6yl8qs-HZUpYc_sMEtFIBZft5udwlsVrgMdS0dcR-mAlEJ6hSaBivSjOU_ioJKTuORyDj69MwOheOyDLDahT6eNI_cvX2z2CRDO2vrtxIQl0xYZvPgKabFxwJ2HbAmQjWguoyaWw40LBs2L_ZXBLFW6xK7WZdvUVRvwftELMa20ZEh2Pi6v3FZYJlUeddQZzUMDcf-BLhW7IesIrPHDGA6GFXk8cY5vNXVfa42XW-hqRmXdYX8ioVyANaDU8llvnoSv-lRobUQXuALNZqbuUXPVs8Pmx21dbJlXKNo1A0OsZfdaj38g9F75KhKEwJB9--GbyDK0BuKu51DRZ3UN0W1wr9HQEoQ_KmCdnoVM6b_r_CtWD-AyEC54uvmA3grSTC5w20oEqT_OKOj7Y2Q_0qVBVmGrVhlL7XpJ7ulQIHLl9VYoKS_PEgDC3zYlA-QWudNgxiGbhuSuwqWI-9TXksC-KLWfax2NqU50FbrXlifQnQTw4MHwk2no8cdhUuK4sg0Q4uY9063WdI8465OLNHwZbB0Ou36f3pWrOeKcEt9H9F45t5EBQaOep6CzqOd5Wj9me-sbCZqMfZBg9VX7C0monPx1KbOVgRZ66xMdy03lOJ-ADVBtu2VRMbgb0IRjMioH526upux62e2KPgAhljGCeHpZerqyeNAL0dBYG_XnlCwwRU7bdzYbpFZrMav5Ws1wlyLIFObmIrqAstKq5ZovwV8wrDpkMHH4h8HB9NX-qchG8vDQ=","summary":[{"type":"summary_text","text":"**Designing explicit net-stream-unsubscribe handling**"},{"type":"summary_text","text":"**Planning explicit net_stream_retire and cleanup**"},{"type":"summary_text","text":"**Verifying stream entry lifecycle and thread cleanup**"}]},{"id":"rs_05baf4dfc6c4b2e8016a59d590edfc819484118f8a0afd8bae","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdWZL3dM4OcrS-Jir-rlRvITjeL3iinzMpl9qtvgz3nhh-7h6GH765IBEb8timi2ET-kV94_IOCyM8DGLgKnpcAOpWKkCEQX6HeN3ymw8ERIRYLyM-EqmTvyXgNj0U1svDD7ciTditl8YuW-P3n9zydz2TNJBYgFZgrpVnAxTitZWmvJ_vt773gs7REk0aL8S_0M1NZoV5qqlqjjxsdk2EO9cgmvQVgI5jYYQh6wpORmlYweCLf2BvINyGfqgFKq4AtDXN8kxdTrjH4ZutoPmivP-FOTFkFHzW3BYZlNvxPBLUShk7M44OGDMh0UXclBMagrA8gvPDXwbU-MZiVVdWC7dYpONBboDZ-8RTHHNcDQWE7IjjI4iAxByrY7am4he_pb-0gD2IntI2bOwoxA1aIdydXcSmrf72waffIBhOjayJ7lcFJYCBu-AzM2x5de4Nbukrxmooln3hp3hWGJUNyyWMOh3kueENXb3oU4jQGuzqf7q9nuFdAYMOHD0UGlDpTqTXydQMl1J9I-czXKD2ZFkEq5vKHBhysK3Yi1F2uVjWtYuGdXrJ0jU7yrkIUsxhXybT_GsuWfnjhvVI7avGx34j4YfPSmi4mlu3BCiuXUEs02lkHJWmhq2hcKiDnz9UIJaxGCcCkdjNDhQVP-QeyiU4g7W3FOIYBGKD1DkJM3WVxQaYzm80UJtiQFuiiTX7KXoniiTcTeXsLZwFFaOE7yTtOFlia2mSmEtOaTBHHyq2yRAQ_v_5Kfh2RlTeOtML2eSAbfxzlcjut_sRNVnQWXwujlUTHxhSArOOZgwD3akuOmQMF22gXdD8y1Y1mNN66aqcbw0d-SjosNqJT1dwg4s70uxyMLjqqzRnTq54quleT0OJZO606kyUpSAOD_k4FH9gXTlOZsHCAOoZrZeIOVBuFxzQ9FOMLLa-Hys4Ek0HfNr6GW4G0CBq6lBNa8x4A5CITjvfUEmJobB4fsxqFEuCbD06RQ678KfXnBoFtgIovT5QtM7-i2NJanp109K4DELbtFoRUDEoZhXcisfyOzTHHAnwrrFxF6CxbsiYrtgo4sexrHFk-zdE49l2qSTx_mvovW5JPTse8FePaSQCQ3NOT1bLAMDGYktL9f_N9l5CQ94JMq_B4Fi_o57cTNZJpVQ_ldTAKIm6kfrZzvz5dVjG-7MxoWgdAr5Piy_AUOojF4JUb6ps8pXESsOQjTFXEQKLWlrwS_NmlqWDJdMsn0JRH9d9-IyUnwuqSEI9WDxIv-WC6uy4mz_LFU028jzPui2ZiQWicOCVkc-6WlRDOUxKxQU1Qc5jQ1W68x_ZqbtLfMfAyaqv0PpiJfryNncAy9swZixPyZaWiA9e523l5OQrC3GeqoTWmCmUyEJ7LAlUZLGrYm8uWvAVWibr3-Hm_C21kx24pLDEl-vvQELM6E_mWMUZ12K-XGYPPywGZgwUo8Xi9zfRTrTZ2hsn232PAn2iMb-uCsyGrEPlKtIBw0v0g-8WmHTBkuSlHMpgoXhRXfYPIMKx6vgzj4aVCSPETGQYrH0R5kCVNKjZuvTT7uKkrOS3lZs_Yu2P1z4TefxqaRb1HRKCSRgYj9MD7l5lMw3k7a1OGVtl61D_gcVfZwh3JAPI2MDi7DVgdnVV30sbL_cFrMnVs6YNHH-wOeVa4HSItaQLBRiBOEmFZ_LEVJfwgiPpXVIQiQLk2CxwgRtrE_6pxnACc2CrRTz4wCcHWA-cgTaMz6To54uaWJTXkK903UK8tEUyDzdkM739T0V7vJoaXqKaBaVyWgP5YXq3eQHusbTOsV4sdMSns2baz1ZMkd-tUKBQw3wa-3B4IS1ZyCT06lyT9mzLL9XT5xy02-aBhEq8LjaruDrQsd-dkvM4PYBwFbu8QCgijav8qdbW67KEyzD2eVeJnrxwYDDD38QYRkaQFpZkbXTbKQirvQkupwkPhkZJ77sdDl4qoya6WVi0-3ZQ-3Evo7RwYcld4Rnk1j5tuRqs-tQ13RTx_FYcaCFSxG-jE702tPAcLncLnGI5CFSr_LnWJhBl8-PQGRnNwNNfOCjUblWjtYE-20zjA4CDlpf0i0ADQPy7LphhG04TlI9YsRdi0RMQT559ldIU4vclIOdDxHS99eoSuT6Xt5Fanma_bGGrNhRJz7uuiTcSlpx8q6xWjXvJHh0UViLHhegPptuM7btNC3frII03-6mrGGl9qc5O_-CZjh8rLzphiINZJg5eEJlUbIX-d20_J7oKMTwVRUB1JVXQR3bAHz5s13THXfYKSusAc-GnSjj65PH93Jx9UYxItvYR_XMc2kKPupxhZ08Ih4qdqmSDOV6j7dc0TjHg68ZE6d-2By8AdHwcbRGI6LgrIMhZE1i_BqRoQSv1g5bOXIrhqYQ-wwb1rp2uWG6VTGVausspWN9klIZTT-3NPKKw_Qr-h3pwjTFgij-I3bDexGNOHWnRcsVT6UNvduDD9DwT33dcAoTkeRQjfYBNrouhZlnWX8etvXKBwdPoPA6YcXYt3BiKCYUsasetaTiD-_KoaPymvUYvhQNMLtxDpXuFBakLrSw96dTY2JgYxdXxF_VvjR7uReFzN5x1Y7UTNIt5aSqaTDO7CReD5jacEK-7mhrRkQqvyMQ9-zYUCyBhUU2uZ3Eb5KgAJn5r-MHkp1q8ygKjrTfypfIFvgbCbkjZkThRM_rY3JQk0MjAaGTWbLJcndrQA4WYzqDRJrk007TrQzz-93UzwE4n_YLkG55pugPku81iDJeEn0oxO-pg6JSr7bWFVS0gRmSJcVSmD2k3vBlAV5OFZqIS3yHjktHzui3vkO6Lju_6Z2FgplNv4Quc5HikX1B8AUYhDrTopPA-WEHkNFel-Ff_EcuOgAYPmHIMreUEtUqJbYLtAKYoRWQ9vGcGRDthC052iCo_7B5fDwSEw7Cwd0OrtOoY1KWqiwMWbQaPXUD8J77XnQRjRYYk3-GUPJ2Q6-DxfuQKdIl11_hkUl8CxfjpV3RcXf5OG4QpONoTEmxuiTgEsoOvoX69mXoo698_NVxdtE5O9XXz3zm-0LfaX4PE_GCMA4CqTl15DAf8N-LK3_ma3XdPemQ3hSijtVs89pEBklj5v-KjBSKFVFMBxDt7jmTdbWVKCL7iDE4Eamf7ELBL7HVu43hy9T0LCVJ-SsnOg-_K4b5vKshfsk0BPMo2xY769d03cBD3qLq3lpcJpouUJJj0pYqVDBd6UdZwBtApn1ZPT0vkTgYNpZvTMBR7KptC4pM5PmTcTtOTP4ifH1DIuHMlUzBbXc26ZankoZsq7tmQL6G5SU3Zcfy1eaVrzYfVTKcwPKzMVD5AK3bxy-5l4vGjO-45RqVJ9ZK786LdDvqW7aNeJvasJHx5taBKW7FfeCRovdOiI6dEnysors2Gm13HLic6aMNh3VrHF1frEOIq4SsDh2OGkMRgSJvh70NpGwyZlY8w1UdPWhOcDF9ECd40H823_c_ldYYSfFcCCuvMowV0qrl-3H8x6HcgZOIkwvPk9scpxEbCQGHml4W4Ca26Lfdp2DtXgRkahbpSf4IGhztCM45XvK_gJPc6-yYNs_2ZCFmX3FdwJmJD2GbkZWWzPc7Vcb7_sCL4tnhNwlx2T_JXm7ome8OBMyhJUhtB_Noif9XOp2twUJZ2Qx5bwDb3pbJsWpnE_4R3JA9HI5kGEPdZYAic6AlNadHFFyN1J1sqekyBXbkiOdLMfieJ7n-4uvADGD2R1YhFUO4KWHjUuiXqekGA786pRo2HsBRggbYLwPcZ99CZU3jMgx6rm8WiDN2THIqqWQjWUMdzvsL2j0pAjcGDbGB0OJwX4dUXYtwUVpS37ts9UGOUpkJOxcUSYPtmOe0tp9Ati0wPTlC2tvVuhu31vutBwZEdtub9DGZDVuAFPIxnFUCbyNgh8ODhGFhJwsOid3zYrwuKCADRxaCXv5sMGIvL6YyfHffcpImEnxK0a5NgNql6L4_sTavcidWuWGzNCYO1NS_KUPJ92khxncKqSd-mQDzvJDN85n7TjRb8KLibGyhz_yqHAziNoMxYxTd9HS2NPgero6evX1zRFHY1PGmGMRiu9u8thOxYBK2t3Mcb-CR22WJgS32Kx9TvFtVppBSxeMeCgZf79WydeIDM_5VlgAajCRYtKeesZUmR40ffBUx1v2y7hEs1Hoaq1OwOIh8wtEZRzhak2LXxelhPsHxfCTqwQ0HB2vGt2jurtSkRLX3QGLzN7mTS6eXPMOY-Fzul3fgarapC-GLTCwxry1FVmHl8J81gZoy3ho6FJq","summary":[{"type":"summary_text","text":"**Tracing connection churn and worker spawning**"},{"type":"summary_text","text":"**Assessing thread explosion regression in v0.36**"},{"type":"summary_text","text":"**Identifying incomplete stream retirement in v0.36**"}]},{"id":"rs_05baf4dfc6c4b2e8016a59d59948088194ac91e509bf0dcce8","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdWkokhjlWmfmVEH7tUkrVIMNHp5bzDIYgN8M3G0emSQoGSisnlS7Oupz292k6InX0iiy6hDOXIDyZSHLK7eImUzZFzmIQiRJj7TIyHfsQvrAV2TOkVidI74rIb9GKEb1DDeuQFRm_XnNQucjyyMdGRd2Ss9dhWuaL_yXbNGayKJ2TY_qWM_wdUT_bWFrZA2FyoPDjFS2FR1KsD38aWdiCiVd9tg9bSGWLMU1B7dbcW6IuUVa55_vZnRvR5lz4w-Pte6phzUR4IVQZgP-cNam1zsZWNHk4AJGm2yOiC6L8GeytulovFjQVjss_xBWpR8A4P9n2LfCtJCk1GylLHqSwhAh-yfNDIDqkEWIax3xseAnKp7o3bCtEPdBg3omOBBpYfJOH-OsFImOuuXNn69Rd77Cx-aCtfefdMsx4Z2og2rPdETHK18WRLz7oe_JG6m5GH84GQARu0apniTKDfLx7f8zAX0M1ccGgKxFXro359Y6qPjQ-WIxooXp8lUPpIhRTM1T9zHwggZEH0v8mlQKWkInsFaOpoUNPFk8I_lLrgi2NoF6llqCz-ZTj-Lux3q_BU5GfYZ9xFKMV6Y8f95LQjdIKsQ-PTHNvAYC8CqFv3nFFDU3Wu8ljb3kFic4WYW1j4VLrUtJd-Vgp-boyvWWFaxAFBjxQeFcOnJlfQkcOkCQVb8t-P9oGL-J_F6JAU_Isep0N5BqObfaJ_A_xEWUSPku9KIMQa8n7IBypfWcMMPOFA_4h87P2RJBVKqsdUZMWaBGZz-xpdlU7HgE6jOJrjumyVlcnqeZTDEWGChyGbnzood2ypdSStRmfy4HB8ZquKJ91_s6THRrpQtvw4lQA3ONocjeBLTVsTs-xhFjBIfJi07PK0L4cDsapzXIN3XVNNpkVB42hDU0fc83Mlwlfq19rMULESvCbgoRKNddK6ek8pcY9YJWj0MHmFWpZk7W4JEMM9PP6cLTMNy-XzxVAJ-X3rCiiORn8goOVj1gy-UyFzxXiyYo2saaWVzvT04gFxgCkkimj8L2ftvyPjZMBY95mcbyyATZ7To3gIpYhW8OQHfti6MAAVR-jA0xNyBIxA0E7SS7out8hb2MLVfnXil6srCE1iUdIX8kPavqnop6gM7MoD5HQA7WRqJt7wlGA_zINfpIwDB4GLqx-EOchGbDOPiJOSTRYccdD35Bar51FEUruRjwOJGQHH98HxlEO731_2oZUs7uf5qe9tN3GOZox0NITrK_pZ0-8r3knUOh1BMSsalqT9fRYNZrZm26Wuw2NK7CTk7dF3wJrztcYRpGBEwlPDLUJ3eVuNfFsgZ86losHQyQwzMDL_5swNh-C4_Jx8GB_o83dZu3_4ssAP5X5KCrDQ8V7yJ9StRtFktBrgIwx_II7ceh7yxQjiuUv2lDr9aWyuKt5dac092bI8lxPQTQeezBEyJB71hAXbc0rdzUS26Bnk4q1EN89wcOqSnuQb8zEeMZCfEjqxncsDN2eUC4QDybB4xJ2u7QFUJBa8T7qPvkxOsg4gLou8t-ri-57CC8aKbsqXm6CMlSn2sP3TmoCV49FDqQSPyMUq9jxXJKWm7yAeNoJIGdBXXm6Saup0kZ0qH6w7owOWgSqSXBqyRPX30IdQB6fGDivzw87ktdUIOyMlVzmGpsG_fIwg4LupLg6EIKtX3C8bJW27d1FTpMAWI2KscDgm8vn_X6ZrBb5mH-NivmazsxcR4hfhxdyWgp31_fPaeQC1xwEsHDV7qLoY2OYR54GV_-DeNPV_WNlSEqonJKBNub7qu6ra1FRshKTWx8Mr7Pt4wK6zJ6zs5DXk9npJ9O57XwRiv4MpDSsCyyzIdjpxVaNwt5lTw8NTfUGkRlMjU_nlyfhY_2lrNfKoIheScV5_rquR_3vFrtMMOo25GXyo-qKKEDsXdvsHUSmMc00isQG2UttnuQ_Dzl-DJJAV-sCE89MAwijcxXo1CRed8i3Ci5wLURN4V_OWJRdM_xEjGFypsq2-vYb9UcCbifTEy92u5fgJ_-9WECterTqzhWPDUb_s3CoM2BGpTVztwJPxXa4xuCOP5TQSUOakiNbIHN4XRLZHSp_q4eMTM4RzJBucEn_wF1-0Yzp2JpJ3kp3GfsPCJnuKZI636NfMTrbhHfSZOXOGen0NVneW4lNrcxF0VCW24e2bfUKXXzPeuFxjOyHXQKqlHMnpT7eAITKdv1pmxT9FMlFA4UWBJ25lpxP6FCskw5P8iss-iB3L0sshksyJL1V6x-LvMC99QQQVASeIn3GG-cDm8BNfNnOR80oDIaPQ1aPz-MirnbZ5Vayl9dxRom4xTkIdAVfOol7mYVsalKP-gH6vZfQkCmnCD7TIr6sah1PeWA4Y6SbX0yzO-VeldRDppYgjRTIGlLO8KY5FkBW0SfJGWUjYFdsx-1m3fRWy2pAfPck9yVRhWKEvw6HGRILc_X5TBhjOHaB_Cl9RJkU2-XRU90hYORWwHIDJAYQ81g_9b1iqrls6OrovN3E0DPsPFCMW1wDzZDMtPobGcwV4mSqGpUcG8uycyWX9ca7ivHE1gldYdMBl56GrQ52Nwv3Mjn8EvHeZsoANMKexpDDvumlrLiotTFmwpDKdizTH5pVUGcn3ZMft9Cmd4vnFLrQVtO_snIF7pI5dbZQi6V0YPwTefk9S4ESKrfeuUX9fzKcTI13PH6X3nYU71LbavOibv7WgsbwdLXfST_EDRnC623eLuy7Zg-yPBN7PTKzPTLD1UDFKrBsPmF-_hQFMFmX7_5f2H51UBWLdyDDHf7EEMGKwSo2TLcU93UOHEC02r4w7uDJhIJ758xr0S_SipH0a8mvxDlz3C_rf76mZTBtG2C1qmYlNgIoO39HVR2WmNLfAO9BseZdb4PmC_ToApG-s-IiERaPLGB3cG-R3CkEwmPuplmImsLxVWtuu_9Z0QYi3gGnTpGMVOlaOemxI1AP_GYEXQnMvez15gUrZmR6raEVMehwbAtx0k1bFZ-QSKrynTs28mYDYRPN5uRkmlBKammxmYoWibf4Uc_x6mE94gXgnHVY15s2sQn_4iRMn_0-GiyZS0BjRKZTNghN76cSWH9LNpNeF1VfJkkQ7UXRvGMvEWquIOIsxSBN9n1KfDJOrzGpMkt1mQ4TuyINFfA4qzy8IYdO4OoaWelk7VKypJWmpdc_ThxnvEYNAEKrFslshTper46WREFuNgSyaQc1Epgsyqb8jptFoA86TRxlUd8uwel96PkjA4q9DaRH5U1AFwR6oEoMNIcOYADpDFYBdNgGNEf5j8lhzj42Naqw3eb_4AGE4a5a4c8TfHyfnF0xihmWX_9YgZjqIJ4hDuszQkuMBJRdghQGxnBprY43WnOxJtL0wiuLkJ0IQuC6gvGc2LFdmcb0CAXayVV2WH0t-nmnect0wbkf9AiCuEtFR9ndF-vlZ-pr1y1VaHwA50HTsLP4BKD_On2FCgyL7y4y2PDxCPdP-YgBhaTrA4rH7r-VzS5SdhvjbXX1ohwGBV3za8sQyNnBvZJhtITZ3FlawQkOdmomJOizUqKYSfEHvJTEzNcJeeHDKpeAVE5OqFpfoGPFvNr_B6x3lPcXPRx7syIrjRGDrzD7NWkt784WUT3KtuO3mc6ggNAknyiN-V3m574IIczqU4vSU-UM597Im4MFsthw711_skSbK-T9ZVi3oicxKXNkldZykgQTdxNlwhz-dRqxpje3hnGmGkaW_EZF6C6aZncD9TCZuwn-o_ZzWv43izKymCAKxg1Sm4K38FQulapGddYv8k-d4zS9g6kTXFC5irNCLCEs3FYQSAXcAMm5Yo7FUqx8kTEBRP9MWij3L3z8D7xaUmjRYbn7fTY91qTEkrLNCAN-QA7F79ePRrY6zlZJsMLvqoZJbgIygO","summary":[{"type":"summary_text","text":"**Planning unit test for subscription cleanup**"},{"type":"summary_text","text":"**Designing detailed pump refresh regression test**"}]},{"id":"rs_05baf4dfc6c4b2e8016a59d5a47dbc819495d04cce83371d4d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdWtmhgSEipwjn3SPrOg3FsaBPy7dwQLxb02eh4Kj7FV3clr6UlnWcToD0rnURBDWbuTIQ0cruGTplrGv_nuFhUyTMCrtcJIzmAHMhdrTFM_my5MAnc79G1unFN3ehqCOYsskXip-hT6iRV8FOnKxh994heyMyov3cT_YAAIuSC5-oJNxYIvulpdMb18aeTJYjPzssu3N1dSKvrf_wQLBZkapVDAH66XU6uQh6Bx_rpjv8jsZUkHRKOB48H2GImAjlAxxMYJNDfhMxUKQNJV6SmXlDenVFcgxbxEUuvi8bZ_xj_6g5fMxLHLzAPqZmuuV45Yp7Mx3JJl6ndxM0DlbRMg9actrt1myKfjWwm2eFYMQXcU3D23BIF15oq3DYbpY0wsal8PE7ytr8OVU6dpb1aTfomn1mq-QKQ9S9BZKH-zFkfROvxqpbkry2CBk52T_j8-_TxN11bnLogIcwJWxeAgmWL4K5Qx3mrcyIeu6lHW8gPdRz9oJ6uQuifGP8XiO6GxGD183fuAvkhhRPB1rb72jNegFC9VpVqrWlT2JjLqGyBMzZXm2w1fhKSdRIbg4wvwRDfFGEPWk_QLVwgtshiaKFHE5szKr-BuKkFR6umfY14rKD-RCPbNdNFYEUP-9RjLU-iHCVTWjiTBEax0oCJp1O5p_YDDtw2drfMFzGf0jDKsJYstAs9RBv5YGPCjVnX-7QCE_DqJp9vS_XbsrscR3zqO5FmdvdWWf_L0niaJ8LCcBa-JGiJ1tsHPHIsrdBbpZizgD-l4OSRXn3zoymVcuVXT2sWdG_hFeiF7MaExTzTAuQt3t1DS0IsZZS6F0kTR-Ig8oWvi5lLITg_qE5YrtD8mdxucsXX6xLPNlcDtiHpsJrE7Cbyut8HEF-g4ChC_vYO5ItretNzP53hbsR0JlHM5LIoKHwdd4sjftqiYu6EYo8s7Pns9TShGxd84RTSW0kjNfpx0TE3gRn9I1-nt-TQcd1on_jYg5fRsxfQnk5tLWopOGezEPXG7Rsd2DHmqjkuPoLHREKgwDvPb7fNyozTc_cUSsvskpE7Q3OJUXsLl1vFaB3zPst7XkHVlbhMH6Z2GKmJT7SqECgZ1DUZCCuG2wpbtXq_FQfwicPVSAcQpm6u1C1QAgxuu__R1UuofB6xhMFUBr5q6faz77Zg4EV6VJaqlR96nQpDXgUo_YUbQU4lTOZVjj9BPZ_2gB8szifUHaa4Qw51CAYde4JNyxpoB141jJBm1yXaFWV61YIqFAjJJwiljzCZo8OJsHzUorUrwyPFmYgif9Z0tMt-3KU8zMZg5TCGesVPfTTkgNOUOi1EgLSCCppW7kv_fbKsbIf2K9b4lxzkadm22NZPXso7FgBC9MAPoCBlDNs8JHAWqmia3NPOYepXRHF35A5Oua6yXzFZQo_mhgckJegxLh-jMsGWudHJBjqGmJhalpuVW7IvulGQItCK4-1MUHldCyWnka11xfYhpaRUg9nuo87CCvs99A38nf1Xu-7_7cJAEszBp7f3NJfNyAk3BYnztqWsf6r3sm8NSVP0F49GO8mbGsvN7c1f8Y_m101XuV9lYMzUacQb1rxW-TxmPOOl1nkRJjvBtCSZgTvvfulFU5JMm_ioez7wY2MuwKFLO4POQeetjaVGpq_VMhZAr9gBn_IYEUXmrtqSJfb5SzpMPNfE4ScOTADnVeu37jT7g0KIWxfHRc5FfZ4EcB5QpkiImPSLvk2UcfmLrhISX2g1JX4i-9cQsyl5_z6Nu2t7nLUMI8p7XbiVkH4C8AD2x1qtbuijSKrvmY7iq5UCKM2W9zGJ-DLQO0PiqP-OtmLepamqFIminuPW842IurRsnkZJpWN0H5NuboaTETJTnyrGdkiJMskxcj2UXJk5Q0mBpJpa_676nX2kQjQnf_cO-Olpou9A7dOyjOtg78r6mtgirF2bxtO9bo007yXq-piqL0Chpmp7OkqJ0fMFDOEcYqsTluFEDBKWCdddQwCXnWWmgIX4_X_7qoITsiPXd2spXUH1BHPg-ze_DZH1OznUx8DTM2Y1LQfb3pWPRABvEaT2N7AMvKmJdvaE42RO6dRt2ObDILXStDbzLwcn5egqWUyIl-zRM5p0mGt8e35-gh_duzaVnPURHQ-2i9GvigoztEvGiUkbCEtpmP8UOiQOXeT3vC96AuErmoVj79hVVH5w1hZ3027GYMXYa48iE1xcHOMXNoxkD7ASwggf3eafs2igcwKlFgjq0yzbppHX2E4YaYl9-5HpLfvJFkXD1X2l40J9JsUS2pAOvOyj3g-LKhLbXpBeQr28GcWfxKVMskjZGQDxEr6zQaFIqUt8bM7Vwo499lYMbMFA_vYS4vbTM7VazNcyOvFA2medTIfwcBkUOZRodWioWP-CBsPPaRHzIT5cau9ItS_KxMS3OPrOH_qWnwNhny4rpzqUE0yvrxbzbgu81sQI3hQ3iCWMAb-Eo6Ql8W_aweVkkBfIoAx3PbLBzS6oIf-JuEOx2y2gN8Np7A5jtFco9XvDCfSbCfddSulf4R-lLswOOQfvAt3bpVeTaJ-VZzipycHImKF6JtGgB0RD3R-XB-VHzfCCmSTWiM63D96H8lVWQ8gatzcHOWsIzRiHsmZXSWvKbJ-wXsXYdF10oK3VSa0TsTgf9YEu3PkrW2XPS0ClmhCYELp6tREus3KZpSHdlcrnOTDBo8lc6jYSObX_Sia15KHXxDLr2RSptR_taKsDPp_9DPTJfbDMU6lEVyQSJVEbG6bZIhuSH-WVFXlCgedCxlbgTyhNQf3kx-FwSM08s60QWk0aiyPUEkxhvDNtZhQXrvaz8pIXvwQ0jRFoSQng8VN8oJicKWtqkpYt4Zs9ImORxXVe2W4Mw_A9enL8k1-QsSb67nIr7ROj9c9_Kb93b5DmKDzNiUswd7gnnOi4w76zt9l9k-oTn8DhJ2ivI-7FEv2KeWfTGzD-CHKtIcm_QkzZIs2_p4QhdtSeak0UQ9s_ITAlX9yhHruTB9KBx1rETKAlVfj5xqL9pIzTEvX28U-ArpfTjZ2qHikL0l0wPIfoLMM-Sn2SwNLFNsjRBckqDakOjP8iud1MF-pRyAchIZuu7NGmL3FllRrJskKEaoSNY9lw_4FW6kqMgd-rI9wK5DHMbvdDGjRHZmbRpco9Rpl5x0cmvfwgzQpZGkbmaDkSLsGMqF8nrcLEIbaKIsABtB_X8_UwPI3DJ7lELRPRBBESF3-PYkPl9bdMnTRhud6A0MrRKnm1xYYo2xCDRXhYu-ze5bT8uSK5ts1e_vu3UW3X-iGKDbvI1z-yDNlhkyyapyfUc1vdtrAz_UpIBc_jznBFZHJ_r_-gQUYAdI2K15pk4yYYcR7nzfbEOC6xNQIM0LoSuU1Kq-QtDHGC3TGjkpjB0XCrGnUrw_UjCF4ANb5_rBN77Gd-exGU5rbsgoC6Pu0baDjeuuC6EL4yVbpW_jT1MZixBBcLI0vifNhDU4IHW4KWHRg2ybxT8ubOrTKsl0n8Ci5YYXuVLfZQPa3zaxiqDFAoWt_cMFZll296cny27nV7SQzkkBc-yRolDEDuAt8GIwlFYtNuPmemrxyM74nrwyMh5w-FT20NUKR_C9AHQTD-xBC0=","summary":[{"type":"summary_text","text":"**Analyzing thread scheduling and connection management**"},{"type":"summary_text","text":"**Evaluating connection handler and subscription counts**"}]},{"id":"rs_05baf4dfc6c4b2e8016a59d5adecb48194bcff9e3d6340c55b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWdW022cyjWsFQN3WsQMZhE896j17k1A9ZqvkVt0rvUad2nhTMBPUYEkwF-tFjoLXUaWqFljp1ZdU25ZFYOLWXeCkmkMpC-DlPutQkrqAvRVWUfYqBU7mPNw5t-_K7FpamgXPFdd5R7oyklaH-UOcsJRKlCg0Envqxz2KBqs4G0lHtVbUZiIEjh-pf7b3ZvEVPLecAmdCfW6yLdQ75U_a9NdMGLsewYgiKokrXx43pb_EgQTYC0PzfBbp8KcHdPh4W7g5OcE2y6OhqdP9D42NaYPtTceQCvoj_aBMkKDG8my6vuP1hMAukRqVi6P8apezIa1OwdhT9CXvexjOuPVdtlxAo7h3Nn0-9x2w74kJIei7ae4y7e0Ie5TAk-0b5de6aVcLQ9Pr2dZqhWirSjnwZDOVvqtM7fHhzYpcxSSYKDvMdk3PaiWXLEe5Z_s2zaT2ShC5_srkW7X6s6YYCdf33MzT1vrfe-sFEa5eKhw2QVzE7wrRmsqGmvXlaFZMryKFZRrLnK_iKxK5Nd8n2ycTAAJgkVlm-farYYzTD7NZw4vz5WS_Roao-w_hXFoeyXm7-26Tro49RVEJIESG5vu0MCj8aHbQimPTHwF54JTtt2MCBfbUpLdBjrd9Ng7CJokAjjqprWmz7pibhj7D2_Xs3xILFVe6mTwolPY2kOcBvW7RXQF9kD2cF8LfETA47YBg38lPS1pX2xNMQuokkSOosn2gh40F2LkI28nQAB0ggtGbnBQ71GfbHITSxypuxKkg-p2jgYyukbjTpG5Y-VXtDZKByNH0Q7CbcY5hn77GpnH195K8kEexTXWqws4wgajcRxY5VrfA4k9DGqV1rFvNt2bfDQorPgNLeNso7zWZwuUS5aY543J1CZrmMieVyDARLR9_vjnP_pKWAYH0GNGzHVpJqj2GsAw7BquTAL_krTV34MFumc4XfOLG43zRRKzB_TsjfqazFqIguUxJo0_Kum0AF199RJDS-_TwFH60Acz54OftHGgrl2UVggCDhUVBpaDbCfApXUXa0RhvSjWnRiCw5qHqZD6YolMD_CEhPAkAncheaEIzz5ctV8dw2WNvOuoZ6hhbgYRFqql8sojaYi3DTAlbXRTC-1wqDIfLRB4KzUPhTaWdhyylTOqNNmYfuLBzt0MWLjc_4Y2YubtDvAN8bfj1yvi9Sy4pU5yt9mYMFbXvW2OcZxe-3xICeKFJtJYiCkn86sjrVipEZfUYH2MEjCjTzp1N8tePRFi1Dq7-C-mcYJLy4meV57VMkwrrmPEkL9jnBEdW4sgP73VoKml_GdpdQ9Ogsjcd9K4rQsRPdM4tNUZ67yqbTtkfvskhV-EJ-aNbku2vqWUiYzr0fKXYYgO5YVO4d7zhb5ZSCps1IgXZZHDZBCI0_XU_LRi2xxy0wH-dLEzz6qWvAaGNFhkWlhy5LAkJm53V0bbmALth3UN8EtKp3trwkHtLfL54S5JXLhchXK8gN2o4Lvw4xxNN_smkB5DAg_heU5aEKGzWxyYQZtlvnPgj47R4J6gbpkgWNl30CQPKZXYebTRESK1CicMORh1lEtfZUFZxLAvKAYPbiPHeL4FLDLfccIjMW1SmqruzpWgezGRgyqnZzXAOEWVunGij61G3AwvCCpOfpy78cSLlCkxVXE9L5JVdG5lWw3Sq_seZiZ6duSy6Elka-1okLvUCOgZGj0ffVHJwvCFWY_oxI7hhCNi3Wt8BxwmHq-eheaTJzBoCIb7-wiayhQTrc3SjUDbQYPmBV-UNr1O3ZqJhtfbeCA9WiSGII-u3LuXb78O0WreikChswjqkMBA1C6CKXTv2OXn_pJ_z3p_C-kKIkLb1vfMbuCHDaU2vuFt_X_mE8RqfaH4v54tp8FzEUGXHOridweAV8vjLPgCAJl8P_KXPm49HmkC-X1zp4HPQI9EYHr3g62FvtJPhk-lr70I5hA_FD9tegYBocT6YYhxjAZBFimmFdWKA8IKB1mYtfHME5ixbEjLi-HMTWF-TMCbNQCy0a8Psqtxgb9uU6VMK8n8YLzrlbZ3jN1geJsW91a1qmlEzqhgjHGsjSu3sMyiElbUYb6Rr4icd9V1v4RH6Pmza2os_CWzuymxuDWKmojhYJ584WFp9P7IyAQ12OZulusDmMHL7fA9XLQOGahFZLjQsYmcRSPoH4cpuimwiWXY0d9BMQ0X2oOz3EK6SeyU6gZosNsqW2dwSb3ZPd6WVBh7CMFIYFP_L8gFNRAaf_9vcEpw9DQ2a09nEVkte_EqKJ3pKw5x4iUDw3l70N-hKah0AxnXm4jaEbXSY3rZ9R9YtpeSspQ4wT1S-SCVXe3JREcwTbi_bs3t6c_Uh9oQnDBsNQPf8BY8pKc71gzY3XeuzaWmjmDTVAzysGYyXGtUlfJtKnbyVHIovwuI4oE1TCzNucWAMJS2mtZux2MnKuVU55iu4Y8rDu9psok02u9MxdzrjROOhVbl9Zrvq4waFnJ7EbEENeDvNgQ1vEJWGhwTleD9ItaCeZEBjcH0ooGi4ic7AabF1dv7abQBu79uICu6cGF1q_zIJtF7e-za_fvwLCx1mutU68VBB5zudxLe3Ns0of015r_YtYYwun7oMtjbc1Y5yWWau3dyb6bxokMEDpUj_LuwZCt4_YFeDhhBfQhZE5Fw5DgKLPoFLquaVtyhX0QkS7sgBMPr1FZeUBYUW3s6siZNSbG3tF3Y_fiZVi1mKUfGr_136WBkBC_rfOL2MJWRmdfGF7_0OTbxodGX7DQX4XV_tnhLOvkqy1DRkpx7VP_GUgaRxMCMBiAYHJmadffn0ZiB_wgCw85nC4_jQ6VOv2Z9TnH8UGNlrU6f4d3BqaTrgZmIEcHBcWQ0bYupUzuieIMGaLrIG9mRkZjj3dLZZOlpaUE89N5NnHNHMPJOQVViG1E77-d0yK4I3PqTIqSiTaqFxR1h4XWd6TRa5LYtbhp99pkgWNScwh08ZzJJ6-VyNlEXH94KDMQ6fGSiDyEMP3eSRQ6r9OxXSdWMwDQpG1K-1n8f_miIEONHs16RFjho0BZVq2UUxMztHaBwCV9HigapiomklI9AXgQEN0x2mHjU62VbQ4cYM59bjo8vmps95gqkIMFPAUwZGx5wCpCgVUXqjKeX8UgtEGsvXYoHkAoNCbILEiQnu0peqbT4-uWfyMNJtApLNiCUBK9ZWURxDmgEtUjNUPrdkPLCCcSJ868J0mstnSuQSOP9Ca_17ks9zYiOqOXaBH1B80GweiP1f7LCAmTbZeqqCPbPZb9TirJet93o3Op8Ac90JbWzvrfM8OwiWZhHCeaMii96urxBlrFyQfRwljURFzabe6ScWK2bOsnF1qdokTAyNIdJwVlBWRa88O-s=","summary":[{"type":"summary_text","text":"**Identifying seat leak causing broker thread buildup**"},{"type":"summary_text","text":"**Assessing dispatcher refresh and mixed version impact**"}]},{"id":"fc_05baf4dfc6c4b2e8016a59d5b4f20481949c05d35308fb7678","type":"function_call","status":"completed","arguments":"{\"type\":null,\"result\":{\"data\":{\"summary\":\"High-confidence release-regression assessment: v0.36's REDISPATCH subscriber isolation converts a pre-existing logical subscription-lifetime leak into one parked OS thread per completed pull. `SubscriberSeat::install` spawns one writer thread per `net-stream-subscribe`; after replay/live EOF that thread does not terminate—it waits on `rx.recv()` until its owning `StreamLog` drops the seat. The long-lived peer-pump `Brain` reuses one broker IPC carrier across every sync/update pull, subscribes each newly opened response stream, and never unsubscribes. Broker-side `handle_conn` retains every subscribed stream ID until the entire pump carrier reaches EOF; `Brain.net_cursors` likewise never removes completed IDs. Healthy QUIC connections retain the corresponding `StreamEntry` rows until the entire peer connection closes. This violates intended O(active) behavior in four coupled dimensions: broker subscriber-writer threads, broker handler subscription IDs, brain cursor entries, and broker stream rows all grow O(completed pulls). Approximately 532 broker threads with only four CPU-active is the exact expected shape: hundreds of unnamed seat writers parked in `std::sync::mpsc::Receiver::recv`, while only the dispatcher/pump/net runtime threads work. It is not explained by current dispatcher worker concurrency (`MAX_DISPATCH_WORKERS = 8`), NetHost Tokio workers (fixed at 2), or 4–5 hosted PTYs.\\n\\nA second, independent O(time) defect amplifies CPU/I/O: the pump opens and FINs a fresh locally initiated Registry feed stream per peer every registry round, but locally initiated rows are never dispatched or retired. The dispatcher nevertheless asks the broker for the complete non-retired stream table every 100 ms; only after transferring and deserializing the full reply does it skip `initiated_locally` rows. Thus a healthy long-lived peer connection makes `net-streams` replies grow linearly with runtime and the fresh post-refresh dispatcher rereads the entire historical local table at 10 Hz. Incoming Served rows are hidden by v0.36 retirement, but retirement intentionally does not physically remove their `StreamEntry`; physical stream memory remains O(time) until peer-connection close.\\n\\nReplay behavior is bounded in concurrency but not always bounded in lifetime. Ordinary transient failures have three total attempts with 500 ms/1 s retry backoff and then become terminal. `SeatBlocked` failures intentionally do not consume that budget; they requeue indefinitely behind a global 2/4/8/16/30-second circuit breaker. Any successful sibling serve resets the breaker to 2 seconds, so a poisoned row mixed with healthy traffic can replay forever at roughly two-second intervals. This is a paced replay loop, not a CPU-tight spin. Cold-backlog connection churn is also possible: a worker can use a temporary opener-query connection, a serve connection, and a success-retirement connection. Although admission is eight workers per 100-ms poll (up to 80 newly admitted workers/s), `active_workers` is decremented before retirement/telemetry/claim completion, so slow completion tails are outside the advertised pool bound and can transiently accumulate broker handler/watchdog pairs.\\n\\nThe observed brain read rate has a separate, exact source and should not be attributed to stream replay: every 500-ms brain heartbeat calls `write_ready`, which reads and SHA-256 hashes the entire current executable. Predicted read rate is `2 × executable_size` per second; 63.91 MiB/s implies a 31.955-MiB executable. Registry session-ledger scans occur only on registry rounds and each ledger is capped at 64 rows, so they do not fit the continuous 63.91-MiB/s signature.\\n\\nTargeted regression seam: keep one real pump Brain carrier and one real QUIC peer connection alive, run many fast non-empty sync pulls plus Registry pushes through production pump/dispatcher code, and never disconnect between rounds. Add a broker/NetHost lifecycle-stats seam reporting physical rows, eligible rows split by direction, installed subscriber seats/writers, and per-handler subscribed-ID count; add a Brain cursor-count seam. After warm-up and N completed rounds, assert all counts plateau at O(peers + in-flight exchanges), broker thread count remains at baseline + O(active), locally initiated eligible rows do not grow, and a serialized `net-streams` reply remains constant-sized. Then perform the production brain refresh boundary: assert the old pump carrier's seats/cursors/handler IDs fall to zero before the new generation runs and that the new generation again plateaus rather than restarting linear growth. The essential discriminator is that the pump carrier must remain open during the first assertion—closing it would invoke existing EOF cleanup and hide the bug. Existing REDISPATCH tests cover historical Attach retirement, rolled opener reconstruction, poisoned replay, retry classification, and one-poll worker admission; none exercises repeated completed requester subscriptions on one live carrier or asserts lifetime plateaus.\\n\\nFalsifiable live probes:\\n1. Decompose broker threads by stack/name. The subscription leak predicts about the excess count blocked in `mpsc::Receiver::recv` from `SubscriberSeat::install`, only two `spt-broker-net` threads, and relatively few `conn-watchdog`/`handle_conn` threads. If instead approximately half the excess are `conn-watchdog` and half are `handle_conn/read_frame`, physical IPC connections—not seats—are retained.\\n2. Compare `Broker::conn_handler_count()` with total broker OS threads. Low handler count plus ~500 excess parked threads confirms seats; ~250 handlers plus ~250 watchdogs falsifies it and identifies connection retention.\\n3. Instrument/count `net-stream-subscribe` calls on the long-lived pump carrier and active seat writers. Predicted slope is exactly one broker writer thread and one Brain cursor entry per completed subscribed pull. The slope tracks non-empty sync/update pulls, not hosted-session count directly.\\n4. Trigger a brain refresh and sample at sub-second cadence. Existing EOF cleanup predicts a sharp collapse of the old pump carrier's seat writers when `handle_conn` iterates `my_stream_subs`; failure to collapse means old-carrier EOF cleanup is stuck or the old brain remains alive. Stream rows may remain because peer QUIC connections are broker-owned and survive refresh.\\n5. Sample `net-streams` twice Δt apart and split rows by `initiated_locally`, `finished`, and family/opener. Registry-only local-row growth should be approximately `live_peers × Δt / 30s`; notification feeds add their own 15-s cadence. Measure serialized reply bytes B: with breaker closed, dispatcher traffic predicts about `10 × B` broker-to-brain bytes/s. A 30-s breaker window should suppress this 10-Hz component; no I/O fall during that window falsifies enumeration as the measured broker-write source.\\n6. Group `DISPATCH_EV` by stream ID. Transient failures must stop after attempts 0,1,2; SeatBlocked repeats preserve the attempt number and follow breaker windows. Repeats substantially faster than the breaker window would be a true spin and contradict current source semantics.\\n7. For the brain read counter, measure executable size S and a 20-s `ReadFile` delta. Prediction is approximately `40 × S`, with ProcMon reads targeting the running executable and `brain.ready` updating at 500-ms cadence. If paths or rate disagree, executable hashing is falsified.\",\"files\":[{\"path\":\"crates/spt-daemon/src/nethost.rs\",\"description\":\"Lines 154–223 implement `SubscriberSeat`: every install spawns an OS writer thread; after replay it waits on `rx.recv()` until the seat's sender is dropped. Lines 430–607 show EOF only enqueues a final frame and `begin_attach` stores the seat in `StreamLog`; no terminal-frame auto-detach exists. Lines 949–970 remove all stream rows only when the whole QUIC connection closes. Lines 1000–1068 register each stream/read pump. Lines 1804–1853 enumerate every non-retired row and implement retirement as visibility plus ring clear, explicitly retaining the physical entry/subscriber. Lines 1967–1990 expose detach only as brain-disconnect cleanup.\"},{\"path\":\"crates/spt-daemon/src/broker.rs\",\"description\":\"Lines 3025–3290 implement per-IPC-connection handling. Every successful `KIND_NET_STREAM_SUBSCRIBE` appends the stream ID to `my_stream_subs`; those IDs are detached only after the read loop ends because the Brain carrier disconnected. Lines 2921–2924 expose `conn_handler_count`, a decisive live/test discriminator between seat threads and retained IPC handler/watchdog pairs.\"},{\"path\":\"crates/spt-daemon/src/brain.rs\",\"description\":\"Lines 271–276 define the per-stream `net_cursors` HashMap. Lines 813–830 advance entries while reading stream data. Lines 1590–1605 insert cursors on subscribe and provide no completion removal/unsubscribe operation. Pump-mode construction additionally owns one reader thread, but that is one per pump carrier, not one per stream.\"},{\"path\":\"crates/spt-daemon/src/pump/mod.rs\",\"description\":\"Lines 519–593 construct two long-lived pump-mode Brain carriers and reuse the main one across all peer steps. Lines 1109–1123 open+FIN fresh fire-and-forget feed streams. Lines 1126–1142 show pump connections use `Brain::cold_start_pump`. The scheduler marks completed rounds and sleeps when idle, ruling out a cadence tight spin.\"},{\"path\":\"crates/spt-daemon/src/pump/registry.rs\",\"description\":\"Lines 158–176 encode one Registry feed per peer/subnet and call `push_feed`; combined with the 30-s default this continually creates locally initiated, finished, non-retired stream rows.\"},{\"path\":\"crates/spt-daemon/src/config.rs\",\"description\":\"Lines 39–42 define default cadences: Registry 30 s, Notif 15 s, Sync 60 s, Update 3600 s. These values make thread/row-growth slopes quantitatively falsifiable.\"},{\"path\":\"crates/spt-daemon/src/sync.rs\",\"description\":\"Lines 335–346 open and subscribe every requester-side sync stream on the reused pump Brain. Lines 430–487 return immediately on the protocol `Done` record, before stream EOF cleanup, without unsubscribing, retiring, or removing the Brain cursor. This is the dominant periodic source of permanent seats on the live pump carrier.\"},{\"path\":\"crates/spt-daemon/src/propagate.rs\",\"description\":\"Lines 355–356 and 557–558 repeat the same open+subscribe-without-unsubscribe pattern for update pulls and status queries. Lower cadence makes this a secondary contributor but the same invariant violation.\"},{\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"description\":\"Line 87 sets the 100-ms poll. Lines 179–341 define three-attempt transient retry versus unbounded-but-breaker-paced SeatBlocked retry. Lines 429–563 fetch the whole `net-streams` table before filtering local rows, admit at most eight active workers, decrement the active gauge before completion-tail retirement, and retire Served incoming rows. Lines 639–1048 show each worker's opener query/fresh serve connection and Registry serve loop.\"},{\"path\":\"crates/spt-daemon/src/conn.rs\",\"description\":\"Lines 389–453 and 638–661 implement one named watchdog per physical broker IPC connection and join it on `BrokerConn` drop. `CONN_LIFECYCLE` write-start/transport-close deltas and thread names distinguish connection churn from the unnamed SubscriberSeat writer leak.\"},{\"path\":\"crates/spt-daemon/src/brainproc.rs\",\"description\":\"Lines 62–63 set a 500-ms heartbeat; lines 296–297 call `write_ready` every heartbeat; lines 365–384 read and hash the entire current executable on every write. This predicts the observed ~63.91-MiB/s brain reads independently of REDISPATCH.\"},{\"path\":\"crates/spt-store/src/sessions.rs\",\"description\":\"Lines 30–31 cap each session ledger at 64 rows, ruling out unbounded session-ledger scans as the continuous brain-read explanation.\"},{\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"description\":\"Existing production-path coverage for finished historical Attach retirement, active Attach survival, durable opener reconstruction after ring roll, and bounded transient retry. It does not keep one requester/pump Brain alive across many completed subscriptions.\"},{\"path\":\"crates/spt-daemon/tests/redispatch_stall.rs\",\"description\":\"Existing T1–T7 coverage for poisoned replay, producer non-blocking behavior, lease cancellation/recovery, and mixed-image fallback. It tests incident mechanics but not long-lived pump subscription/stream-count plateaus.\"},{\"path\":\"crates/spt-daemon/tests/mesh_recovery.rs\",\"description\":\"Best existing integration harness for the proposed regression: it already runs real brokers, real QUIC, production dispatcher, and production pump with accelerated cadences. Extend it with repeated non-empty sync/Registry rounds and lifecycle gauges while deliberately keeping the pump carrier connected.\"}],\"architecture\":\"Causal graph:\\n\\n`run_peer_pump` keeps one Brain IPC carrier alive → each periodic non-empty `request_sync`/update request opens a fresh QUIC stream and calls `net_stream_subscribe` → Brain inserts a permanent cursor; broker handler appends a permanent `my_stream_subs` ID; `StreamLog::begin_attach` installs a `SubscriberSeat` and spawns one writer thread → protocol `Done`/EOF completes the logical exchange but no unsubscribe removes cursor/ID/seat → writer parks forever on the empty channel while healthy pump IPC and peer QUIC connections remain alive → broker thread count grows O(completed pulls), almost all sleeping.\\n\\nIn parallel, every fire-and-forget Registry/Notif push opens and FINs a local stream → no dispatcher claims local rows and no requester retires them → broker stream table grows O(rounds × peers) → dispatcher refresh reconstructs an empty claim map and polls `net-streams` every 100 ms → broker clones/serializes and brain reads/deserializes every non-retired local historical row before filtering → sustained small-core CPU and broker IPC writes with little daemon-log growth.\\n\\nIncoming historical rows follow a different path: fresh dispatcher workers classify from the pinned opener, subscribe/replay, serve, then mark Served and issue best-effort retirement. Retirement hides the row but deliberately keeps physical state until connection close. Ordinary errors terminate after three attempts; subscriber-seat errors are retry-forever but globally paced. The worker pool bounds serve bodies, not all completion tails, so a cold refresh can create short-lived IPC churn even though it cannot explain hundreds of permanently parked threads by itself.\\n\\nA brain refresh is currently the only reclamation boundary for pump-carrier seats/cursors/handler IDs, because carrier EOF runs broker detach cleanup. It does not reclaim broker-owned stream rows riding healthy peer QUIC connections, and the replacement brain immediately primes every pump leg and reconstructs dispatcher polling. Therefore refresh can temporarily drop thread count yet preserve/re-expose the table-size cost and begin the same linear thread accumulation again.\"}}}","call_id":"call_IFHLmYEab6i7WgJl49gsppC5","name":"yield"}]},"duration":108746.82259999216,"ttft":2885.1989999860525,"contextSnapshot":{"promptTokens":139669,"nonMessageTokens":15464}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_IFHLmYEab6i7WgJl49gsppC5|fc_05baf4dfc6c4b2e8016a59d5b4f20481949c05d35308fb7678","toolName":"yield","startedAt":"2026-07-17T07:12:46.196Z"},"id":"a495f57a","parentId":"51fa95f7","timestamp":"2026-07-17T07:12:46.196Z"}
{"type":"message","id":"9ff1f5f3","parentId":"a495f57a","timestamp":"2026-07-17T07:12:46.197Z","message":{"role":"toolResult","toolCallId":"call_IFHLmYEab6i7WgJl49gsppC5|fc_05baf4dfc6c4b2e8016a59d5b4f20481949c05d35308fb7678","toolName":"yield","content":[{"type":"text","text":"Result submitted."}],"details":{"data":{"summary":"High-confidence release-regression assessment: v0.36's REDISPATCH subscriber isolation converts a pre-existing logical subscription-lifetime leak into one parked OS thread per completed pull. `SubscriberSeat::install` spawns one writer thread per `net-stream-subscribe`; after replay/live EOF that thread does not terminate—it waits on `rx.recv()` until its owning `StreamLog` drops the seat. The long-lived peer-pump `Brain` reuses one broker IPC carrier across every sync/update pull, subscribes each newly opened response stream, and never unsubscribes. Broker-side `handle_conn` retains every subscribed stream ID until the entire pump carrier reaches EOF; `Brain.net_cursors` likewise never removes completed IDs. Healthy QUIC connections retain the corresponding `StreamEntry` rows until the entire peer connection closes. This violates intended O(active) behavior in four coupled dimensions: broker subscriber-writer threads, broker handler subscription IDs, brain cursor entries, and broker stream rows all grow O(completed pulls). Approximately 532 broker threads with only four CPU-active is the exact expected shape: hundreds of unnamed seat writers parked in `std::sync::mpsc::Receiver::recv`, while only the dispatcher/pump/net runtime threads work. It is not explained by current dispatcher worker concurrency (`MAX_DISPATCH_WORKERS = 8`), NetHost Tokio workers (fixed at 2), or 4–5 hosted PTYs.\n\nA second, independent O(time) defect amplifies CPU/I/O: the pump opens and FINs a fresh locally initiated Registry feed stream per peer every registry round, but locally initiated rows are never dispatched or retired. The dispatcher nevertheless asks the broker for the complete non-retired stream table every 100 ms; only after transferring and deserializing the full reply does it skip `initiated_locally` rows. Thus a healthy long-lived peer connection makes `net-streams` replies grow linearly with runtime and the fresh post-refresh dispatcher rereads the entire historical local table at 10 Hz. Incoming Served rows are hidden by v0.36 retirement, but retirement intentionally does not physically remove their `StreamEntry`; physical stream memory remains O(time) until peer-connection close.\n\nReplay behavior is bounded in concurrency but not always bounded in lifetime. Ordinary transient failures have three total attempts with 500 ms/1 s retry backoff and then become terminal. `SeatBlocked` failures intentionally do not consume that budget; they requeue indefinitely behind a global 2/4/8/16/30-second circuit breaker. Any successful sibling serve resets the breaker to 2 seconds, so a poisoned row mixed with healthy traffic can replay forever at roughly two-second intervals. This is a paced replay loop, not a CPU-tight spin. Cold-backlog connection churn is also possible: a worker can use a temporary opener-query connection, a serve connection, and a success-retirement connection. Although admission is eight workers per 100-ms poll (up to 80 newly admitted workers/s), `active_workers` is decremented before retirement/telemetry/claim completion, so slow completion tails are outside the advertised pool bound and can transiently accumulate broker handler/watchdog pairs.\n\nThe observed brain read rate has a separate, exact source and should not be attributed to stream replay: every 500-ms brain heartbeat calls `write_ready`, which reads and SHA-256 hashes the entire current executable. Predicted read rate is `2 × executable_size` per second; 63.91 MiB/s implies a 31.955-MiB executable. Registry session-ledger scans occur only on registry rounds and each ledger is capped at 64 rows, so they do not fit the continuous 63.91-MiB/s signature.\n\nTargeted regression seam: keep one real pump Brain carrier and one real QUIC peer connection alive, run many fast non-empty sync pulls plus Registry pushes through production pump/dispatcher code, and never disconnect between rounds. Add a broker/NetHost lifecycle-stats seam reporting physical rows, eligible rows split by direction, installed subscriber seats/writers, and per-handler subscribed-ID count; add a Brain cursor-count seam. After warm-up and N completed rounds, assert all counts plateau at O(peers + in-flight exchanges), broker thread count remains at baseline + O(active), locally initiated eligible rows do not grow, and a serialized `net-streams` reply remains constant-sized. Then perform the production brain refresh boundary: assert the old pump carrier's seats/cursors/handler IDs fall to zero before the new generation runs and that the new generation again plateaus rather than restarting linear growth. The essential discriminator is that the pump carrier must remain open during the first assertion—closing it would invoke existing EOF cleanup and hide the bug. Existing REDISPATCH tests cover historical Attach retirement, rolled opener reconstruction, poisoned replay, retry classification, and one-poll worker admission; none exercises repeated completed requester subscriptions on one live carrier or asserts lifetime plateaus.\n\nFalsifiable live probes:\n1. Decompose broker threads by stack/name. The subscription leak predicts about the excess count blocked in `mpsc::Receiver::recv` from `SubscriberSeat::install`, only two `spt-broker-net` threads, and relatively few `conn-watchdog`/`handle_conn` threads. If instead approximately half the excess are `conn-watchdog` and half are `handle_conn/read_frame`, physical IPC connections—not seats—are retained.\n2. Compare `Broker::conn_handler_count()` with total broker OS threads. Low handler count plus ~500 excess parked threads confirms seats; ~250 handlers plus ~250 watchdogs falsifies it and identifies connection retention.\n3. Instrument/count `net-stream-subscribe` calls on the long-lived pump carrier and active seat writers. Predicted slope is exactly one broker writer thread and one Brain cursor entry per completed subscribed pull. The slope tracks non-empty sync/update pulls, not hosted-session count directly.\n4. Trigger a brain refresh and sample at sub-second cadence. Existing EOF cleanup predicts a sharp collapse of the old pump carrier's seat writers when `handle_conn` iterates `my_stream_subs`; failure to collapse means old-carrier EOF cleanup is stuck or the old brain remains alive. Stream rows may remain because peer QUIC connections are broker-owned and survive refresh.\n5. Sample `net-streams` twice Δt apart and split rows by `initiated_locally`, `finished`, and family/opener. Registry-only local-row growth should be approximately `live_peers × Δt / 30s`; notification feeds add their own 15-s cadence. Measure serialized reply bytes B: with breaker closed, dispatcher traffic predicts about `10 × B` broker-to-brain bytes/s. A 30-s breaker window should suppress this 10-Hz component; no I/O fall during that window falsifies enumeration as the measured broker-write source.\n6. Group `DISPATCH_EV` by stream ID. Transient failures must stop after attempts 0,1,2; SeatBlocked repeats preserve the attempt number and follow breaker windows. Repeats substantially faster than the breaker window would be a true spin and contradict current source semantics.\n7. For the brain read counter, measure executable size S and a 20-s `ReadFile` delta. Prediction is approximately `40 × S`, with ProcMon reads targeting the running executable and `brain.ready` updating at 500-ms cadence. If paths or rate disagree, executable hashing is falsified.","files":[{"path":"crates/spt-daemon/src/nethost.rs","description":"Lines 154–223 implement `SubscriberSeat`: every install spawns an OS writer thread; after replay it waits on `rx.recv()` until the seat's sender is dropped. Lines 430–607 show EOF only enqueues a final frame and `begin_attach` stores the seat in `StreamLog`; no terminal-frame auto-detach exists. Lines 949–970 remove all stream rows only when the whole QUIC connection closes. Lines 1000–1068 register each stream/read pump. Lines 1804–1853 enumerate every non-retired row and implement retirement as visibility plus ring clear, explicitly retaining the physical entry/subscriber. Lines 1967–1990 expose detach only as brain-disconnect cleanup."},{"path":"crates/spt-daemon/src/broker.rs","description":"Lines 3025–3290 implement per-IPC-connection handling. Every successful `KIND_NET_STREAM_SUBSCRIBE` appends the stream ID to `my_stream_subs`; those IDs are detached only after the read loop ends because the Brain carrier disconnected. Lines 2921–2924 expose `conn_handler_count`, a decisive live/test discriminator between seat threads and retained IPC handler/watchdog pairs."},{"path":"crates/spt-daemon/src/brain.rs","description":"Lines 271–276 define the per-stream `net_cursors` HashMap. Lines 813–830 advance entries while reading stream data. Lines 1590–1605 insert cursors on subscribe and provide no completion removal/unsubscribe operation. Pump-mode construction additionally owns one reader thread, but that is one per pump carrier, not one per stream."},{"path":"crates/spt-daemon/src/pump/mod.rs","description":"Lines 519–593 construct two long-lived pump-mode Brain carriers and reuse the main one across all peer steps. Lines 1109–1123 open+FIN fresh fire-and-forget feed streams. Lines 1126–1142 show pump connections use `Brain::cold_start_pump`. The scheduler marks completed rounds and sleeps when idle, ruling out a cadence tight spin."},{"path":"crates/spt-daemon/src/pump/registry.rs","description":"Lines 158–176 encode one Registry feed per peer/subnet and call `push_feed`; combined with the 30-s default this continually creates locally initiated, finished, non-retired stream rows."},{"path":"crates/spt-daemon/src/config.rs","description":"Lines 39–42 define default cadences: Registry 30 s, Notif 15 s, Sync 60 s, Update 3600 s. These values make thread/row-growth slopes quantitatively falsifiable."},{"path":"crates/spt-daemon/src/sync.rs","description":"Lines 335–346 open and subscribe every requester-side sync stream on the reused pump Brain. Lines 430–487 return immediately on the protocol `Done` record, before stream EOF cleanup, without unsubscribing, retiring, or removing the Brain cursor. This is the dominant periodic source of permanent seats on the live pump carrier."},{"path":"crates/spt-daemon/src/propagate.rs","description":"Lines 355–356 and 557–558 repeat the same open+subscribe-without-unsubscribe pattern for update pulls and status queries. Lower cadence makes this a secondary contributor but the same invariant violation."},{"path":"crates/spt-daemon/src/dispatch.rs","description":"Line 87 sets the 100-ms poll. Lines 179–341 define three-attempt transient retry versus unbounded-but-breaker-paced SeatBlocked retry. Lines 429–563 fetch the whole `net-streams` table before filtering local rows, admit at most eight active workers, decrement the active gauge before completion-tail retirement, and retire Served incoming rows. Lines 639–1048 show each worker's opener query/fresh serve connection and Registry serve loop."},{"path":"crates/spt-daemon/src/conn.rs","description":"Lines 389–453 and 638–661 implement one named watchdog per physical broker IPC connection and join it on `BrokerConn` drop. `CONN_LIFECYCLE` write-start/transport-close deltas and thread names distinguish connection churn from the unnamed SubscriberSeat writer leak."},{"path":"crates/spt-daemon/src/brainproc.rs","description":"Lines 62–63 set a 500-ms heartbeat; lines 296–297 call `write_ready` every heartbeat; lines 365–384 read and hash the entire current executable on every write. This predicts the observed ~63.91-MiB/s brain reads independently of REDISPATCH."},{"path":"crates/spt-store/src/sessions.rs","description":"Lines 30–31 cap each session ledger at 64 rows, ruling out unbounded session-ledger scans as the continuous brain-read explanation."},{"path":"crates/spt-daemon/tests/redispatch.rs","description":"Existing production-path coverage for finished historical Attach retirement, active Attach survival, durable opener reconstruction after ring roll, and bounded transient retry. It does not keep one requester/pump Brain alive across many completed subscriptions."},{"path":"crates/spt-daemon/tests/redispatch_stall.rs","description":"Existing T1–T7 coverage for poisoned replay, producer non-blocking behavior, lease cancellation/recovery, and mixed-image fallback. It tests incident mechanics but not long-lived pump subscription/stream-count plateaus."},{"path":"crates/spt-daemon/tests/mesh_recovery.rs","description":"Best existing integration harness for the proposed regression: it already runs real brokers, real QUIC, production dispatcher, and production pump with accelerated cadences. Extend it with repeated non-empty sync/Registry rounds and lifecycle gauges while deliberately keeping the pump carrier connected."}],"architecture":"Causal graph:\n\n`run_peer_pump` keeps one Brain IPC carrier alive → each periodic non-empty `request_sync`/update request opens a fresh QUIC stream and calls `net_stream_subscribe` → Brain inserts a permanent cursor; broker handler appends a permanent `my_stream_subs` ID; `StreamLog::begin_attach` installs a `SubscriberSeat` and spawns one writer thread → protocol `Done`/EOF completes the logical exchange but no unsubscribe removes cursor/ID/seat → writer parks forever on the empty channel while healthy pump IPC and peer QUIC connections remain alive → broker thread count grows O(completed pulls), almost all sleeping.\n\nIn parallel, every fire-and-forget Registry/Notif push opens and FINs a local stream → no dispatcher claims local rows and no requester retires them → broker stream table grows O(rounds × peers) → dispatcher refresh reconstructs an empty claim map and polls `net-streams` every 100 ms → broker clones/serializes and brain reads/deserializes every non-retired local historical row before filtering → sustained small-core CPU and broker IPC writes with little daemon-log growth.\n\nIncoming historical rows follow a different path: fresh dispatcher workers classify from the pinned opener, subscribe/replay, serve, then mark Served and issue best-effort retirement. Retirement hides the row but deliberately keeps physical state until connection close. Ordinary errors terminate after three attempts; subscriber-seat errors are retry-forever but globally paced. The worker pool bounds serve bodies, not all completion tails, so a cold refresh can create short-lived IPC churn even though it cannot explain hundreds of permanently parked threads by itself.\n\nA brain refresh is currently the only reclamation boundary for pump-carrier seats/cursors/handler IDs, because carrier EOF runs broker detach cleanup. It does not reclaim broker-owned stream rows riding healthy peer QUIC connections, and the replacement brain immediately primes every pump leg and reconstructs dispatcher polling. Therefore refresh can temporarily drop thread count yet preserve/re-expose the table-size cost and begin the same linear thread accumulation again."},"status":"success"},"isError":false,"timestamp":1784272366197}}
{"type":"message","id":"8d42cb1f","parentId":"9ff1f5f3","timestamp":"2026-07-17T07:12:46.247Z","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":1784272366241}}
{"type":"custom","customType":"session_exit","data":{"reason":"dispose","kind":"normal","recordedAt":"2026-07-17T07:19:46.256Z"},"id":"ebfa2741","parentId":"8d42cb1f","timestamp":"2026-07-17T07:19:46.256Z"}
