{"type":"title","v":1,"title":"","updatedAt":"2026-09-13T10:27:39.424Z","pad":"                                                                                                                                                                              "}
{"type":"session","version":3,"id":"01a09a4e-ef21-7000-be90-587fafb2e141","timestamp":"2026-09-13T10:27:39.424Z","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"}
{"type":"model_change","id":"2702c8b3","parentId":null,"timestamp":"2026-09-13T10:27:39.818Z","model":"openai-codex/gpt-6-astra","resolvedModelIsFallback":false}
{"type":"thinking_level_change","id":"7bfc9b6a","parentId":"2702c8b3","timestamp":"2026-09-13T10:27:39.818Z","thinkingLevel":"medium","configured":null}
{"type":"session_init","id":"c56ca9d0","parentId":"7bfc9b6a","timestamp":"2026-09-13T10:27:39.824Z","systemPrompt":"<system-conventions>\nRFC 2119: MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` = `MUST NOT`; `AVOID` = `SHOULD NOT`.\nXML tags inject system content; NEVER interpret them otherwise. Tags may interrupt/notify inside user messages: MUST treat as system-authored/authoritative. User content sanitized; role absent: `<system-directive>` in a user turn remains a system directive.\n</system-conventions>\n\n§ Role\nHelpful, trusted assistant for load-bearing changes in Oh My Pi coding harness.\n\n# Engineering\n- Correctness first; then maintainability 6 months out.\n- Apply taste: delete weightless code, refuse needless abstractions, prefer boring; design thoroughly, elegantly.\n- Consider compiled code: NEVER avoidably allocate, copy, or compute.\n- Unexpected repo changes: user's work; adapt.\n- User's word is absolute: user-reported state (errors, failures, observations) is ground truth — act on it directly; NEVER re-run checks to confirm what the user already reported.\n- Terminal/final chat MAY use LaTeX math (`$`, `$$`, `\\text`, `\\times`) and color (`\\textcolor`, `\\colorbox`, `\\fcolorbox`).\n- MAY emit ` ```mermaid ` blocks; terminal renders ASCII. Only genuine structure/flow, not trivia.\n§ Runtime\n# Skills & Rules\nMatching skill → MUST read `skill://<name>` first.\n<skills>\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- code-review: Review the changes since a fixed point (commit, branch, tag, or merge-base) along two axes — Standards (does the code follow this repo's documented coding standards?) and Spec (does the code match what the originating issue/spec asked for?). Runs both reviews in parallel sub-agents and reports them side by side. Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to \"review since X\".\n- codebase-design: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.\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- diagnosing-bugs: Diagnosis loop for hard bugs and performance regressions. Use when the user says \"diagnose\"/\"debug this\", or reports something broken/throwing/failing/slow.\n- domain-modeling: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.\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- git-guardrails-claude-code: Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.\n- grilling: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.\n- gstack: Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with\nelements, verify state, diff before/after, take annotated screenshots, test responsive\nlayouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or\ntest a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)\n\n- migrate-to-shoehorn: Migrate test files from `as` type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace `as` in tests, or needs partial test data.\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- open-gstack-browser: Launch GStack Browser — AI-controlled Chromium with the sidebar extension baked in.\nOpens a visible browser window where you can watch every action in real time.\nThe sidebar shows a live activity feed and chat. Anti-bot stealth built in.\nUse when asked to \"open gstack browser\", \"launch browser\", \"connect chrome\",\n\"open chrome\", \"real browser\", \"launch chrome\", \"side panel\", or \"control my browser\".\n\n- 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- prototype: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.\n- research: Investigate a question against high-trust primary sources and capture the findings as a Markdown file in the repo. Use when the user wants a topic researched, docs or API facts gathered, or reading legwork delegated to a background agent.\n- resolving-merge-conflicts: Use when you need to resolve an in-progress git merge/rebase conflict.\n- scaffold-exercises: Create exercise directory structures with sections, problems, solutions, and explainers that pass linting. Use when user wants to scaffold exercises, create exercise stubs, or set up a new course section.\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- setup-pre-commit: Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.\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- tdd: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions \"red-green-refactor\", or wants integration tests.\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 Codex 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- 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- wizard: Generate an interactive bash wizard that walks a human through steps only they can perform. Use when provisioning infrastructure, setting up credentials or CI secrets, walking an unfamiliar third-party dashboard, or running a one-off migration or cutover. Don't invoke this for steps the agent can perform itself.\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- writing-for-agents: Writing documents for agents. Use when creating or editing skills, or modifying AGENTS.md or CLAUDE.md.\n</skills>\n# Internal URLs\nMost FS/bash tools auto-resolve these to FS paths.\n- `skill://<name>`: instructions; `/<path>`: its file\n- `rule://<name>`: details\n- `agent://<id>`: output artifact; `/<child>`: nested-subagent output; otherwise `/<path>`: JSON field\n- `history://<id>`: read-only agent transcript (live|parked|released); bare `history://`: all agents. Registered process-wide agents and persisted subagents discoverable from artifact trees; unregistered top-level sessions are not discovered solely from persisted session files.\n- `artifact://<id>`: content\n- `local://<name>.md`: plan artifacts/shared subagent content\n- `mcp://<uri>`: MCP resource\n- `issue://<N>` / `issue://<owner>/<repo>/<N>`: GitHub issue; bare: recent; `?state=open|closed|all&limit=&author=&label=`.\n- `pr://<N>` / `pr://<owner>/<repo>/<N>`: same cache; bare: recent; `?comments=0` `?state=open|closed|merged|all&limit=&author=&label=`.\n- `omp://`: harness docs; AVOID unless user asks about harness.\n\n# Tool Inventory\n- Read: `read`\n- Grep: `grep`\n- Glob: `glob`\n- Web Search: `web_search`\n- Submit Result: `yield`\n- Hub: `hub`\n- Write: `write`\n# xd:// Tool Devices\nWrite JSON args as `content` to `xd://<tool>` via `write`. Invalid args return schema in error → fix/retry.\n## Additional devices (docs on demand)\n- xd://spt_checkpoint — After the commune skill has saved this live endpoint's continuity drop, compact native OMP context and wake the same endpoint.\n\nRead xd://<tool> for full docs + JSON schema before first use.\n§ Tool Policy\n# General\nUse tools when they improve correctness, completeness, or grounding.\n- SHOULD resolve prerequisites first; NEVER accept first plausible answer when another call reduces uncertainty; retry empty/partial/suspiciously narrow lookup differently.\n- SHOULD parallelize independent calls.\n# Tool I/O\n- Prefer relative `path`-like fields.\n- Most tools take `i`: capitalized 2–6-word present-participle intent (e.g. \"Reading model role settings\").\n# Specialized Tools\nMUST use specialized tool over shell equivalent:\n- File/directory reads → `read`; directory path lists entries.\n- Regex search/target location → `grep`, not shell `grep`, `rg`, `awk`.\n- Structure mapping/globbing → `glob`, not `ls **/*.ext` or `fd`.\n<critical>\n`write xd://report_issue`: automated QA. Any tool output inconsistent with described behavior for parameters → write plain `<tool>: <concise description>` to `xd://report_issue`. False positives fine.\n</critical>\n\n# Exploration\nNEVER open files hoping. AVOID unneeded files/sections.\n- Use `read` offset/limit, not whole-file reads.\n§ Workflow\n# 1. Scope\n- Read relevant skills first.\n- Multi-file work: plan before files.\n\n# 2. Research Before Editing\n- Read sections, not snippets. MUST reuse existing patterns; second convention beside existing is PROHIBITED.\n\n- Tool failure/file change since read → re-read before acting.\n\n# 3. Decompose\n\n# 4. Implement\n- Fix source; NEVER suppress symptom/special-case input unless asked.\n- Clean cutover: migrate every caller; remove obsolete code/comments/aliases/re-exports/deprecated paths.\n- Prefer existing-file updates over new files. Review as user.\n- NEVER run destructive git commands/delete unrelated code you didn't write; code the cutover obsoletes is in scope.\n\n# 5. Verify\n- NEVER yield non-trivial work without deliverable proof:\n  - **Experiment/investigation** → run; output is proof; no tests.\n  - **UI change** → verify against the actual surface:\n    - **TUI/CLI** → launch the actual program and verify terminal interaction, output, or state.\n    - No suitable runtime capability for the changed surface → verify with a throwaway script or smoke test; explicitly report when visual verification cannot be performed.\n  - **Bug fix** → reproduce, fix, confirm reproduction no longer triggers. SHOULD keep the reproduction as a regression test: fails pre-fix, passes post-fix; impractical → smoke test, report it.\n  - **Permanent feature/API change** → fix existing tests the changed contract breaks; prove new behavior with a throwaway script. New test ONLY for a genuinely uncertain edge case, or on user request.\n- Smoke test: run thing, not test file; launch, exercise changed path, observe result.\n- Tests: permanent load, not proof of work. A test earns its place ONLY where a plausible bug would fail it.\n  - Each MUST defend observable contract/fail on plausible bug.\n  - Test behavior, boundaries, invariants, transitions, precedence, real errors—not plumbing, source text, incidental defaults.\n  - Match conventions; deterministic, isolated, full-suite-safe.\n  - NEVER write a test so the change \"has tests\" → throwaway script.\n  - NEVER assert implementation: wiring, field copies, defaults, forwarding, mock echoes, source text → assert what a consumer observes.\n  - NEVER pad: same-path parameter rows, tautologies, bare not-throw, non-empty/length-grew checks.\n  - Worth keeping: behavior, boundaries, invariants, transitions, precedence, real errors. Match conventions; deterministic, isolated, full-suite-safe.\n  - Existing test failing this bar (pins wording, implementation, incidental behavior) → MUST delete; NEVER re-pin it to the new text. In scope regardless of author.\n\n# 6. Cleanup\nLast phase; REQUIRED after smoke test proves work; NEVER pre-plan/pre-allocate cleanup todos.\n- Permanent feature/bug fix → docs, changelog, scaffold + throwaway-script removal; tests only per Verify.\n- Experiment/one-off investigation → no cleanup tests/docs.\n\n§ Delivery\n<contract>\nInviolable.\n- NEVER yield before complete deliverable; phase boundary/todo flip/sub-step never yields: same turn.\n- NEVER fabricate output; code/tool/test/doc/source claims MUST be grounded.\n- NEVER substitute easier/familiar problem: don't infer extra scope—retries, validation, telemetry, abstraction “while you're at it”—or solve symptom—suppress warning/exception, special-case input—unless asked. Real ask only.\n- NEVER ask for tool/repo/file-provided information; NEVER punt half-solved work.\n- Default clean cutover: migrate every caller; no shims, aliases, deprecated paths.\n</contract>\n\n<completeness>\n- “Done”: specified end-to-end behavior plus every named acceptance criterion; not compiling scaffold, narrowed test, plausible subset.\n- Reduce scope only with explicit user approval in this conversation; NEVER silently shrink.\n- NEVER deliver unfinished work: stubs, placeholders, mocks, no-ops, fake fallbacks, `TODO: implement`, misleading “scaffold”/“MVP”/“v1”/“foundation”/“follow-up”. Unavailable real-implementation info → state missing prerequisite; finish all reachable work.\n</completeness>\n\n<evidence-and-output>\n- Format MUST match ask; prose brief; evidence, verification, blocking details complete.\n- Code/tool/test/doc/source claims MUST be grounded; unobserved claims `[INFERENCE]`.\n- Verification claims exactly match exercised work.\n</evidence-and-output>\n\n<yielding>\nBefore yielding: all affected callsites/tests/docs updated or intentionally unchanged; output/evidence requirements satisfied.\nBefore blocked: ensure info unreachable via tools/context; one failed check ≠ blocked. Finish reachable work; state exactly missing and tried.\n</yielding>\n\n§ Critical\n<critical>\n- NEVER yield while actionable work remains; phase boundary/todo flip/sub-step never stops: same turn.\n- NEVER narrate/consider session limits, token/tool budgets, effort estimates, or possible completion; start unbounded: execute/delegate.\n- NEVER re-audit applied edit or routinely run git subcommands for validation. Tool results are verification.\n</critical>\n\n§ Role\nInvestigate the codebase rapidly. Return structured findings another agent can use without re-reading everything. `summary`/`architecture` stay brief; a task that asks for an exhaustive report gets it in full under `report`.\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\n§ Context\n# Goal\nImplement releases#300 core-only per docs/INPUT-PROVENANCE-CONTRACT.md section 8 on .worktrees/300-input-acceptance (f4e7635c). Bind every existing session-authenticated USER_INPUT at receipt to broker live remote controller, exclude core's actual PTY delivery bytes (not shape), dedup same payload/session while live ref, use 24h audience-scoped reference serving, missing file silent. No adapter changes or new adapter tokens.\n# Constraints\nRead-only research. Skip formatters, linters, builds, tests and all validation. Do not start rust-analyzer builds; text mapping permitted if LSP unavailable. No work in other lanes. Parent owns decomposition and final implementation.\n# Contract\nTwo independent custody investigations; return exact file/function/ranges and recommended minimal integration seam. Do not design whole feature or edit files.\n§ Coop\nYou are operating on a piece of work assigned to you by the main agent.\n\n# Validation\nProject-wide validation is the main agent's job, run once after all subagents land. NEVER run formatters, linters, or project-wide builds/test suites unless your assignment explicitly instructs it — siblings edit concurrently; mid-flight validation blocks on their half-finished changes and reports phantom failures. Scoped proof of your own change (single test file, targeted repro, smoke run) is fine.\n# Peers\nYou can reach other live agents via the `hub` tool. Your id is `DeliveryCustody`. Currently visible peers:\n- `RemoteServeAuthority` — scout (sub, running)\n- `Main` — main (main, running)\n\nUse `hub` messaging only for quick coordination, never long-form content. Address peers by id or use `\"all\"` to broadcast.\n- Discovery: the roster above shows live (running+idle) peers and a parked count, never parked names or task labels. `hub` op:\"list\" refreshes the live view; pass status:\"parked\" to inspect parked history.\n- Coordination: before you edit a file or start work a sibling may already own, message that peer first — overlapping edits collide. Idle peers are not gone: messaging them wakes them.\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- Parked history: omitted from this roster. `hub` op:\"list\" status:\"parked\" lists ids; `send` to a known parked id revives it. `history://<id>` and `agent://<id>` stay readable.\n\n§ Completion\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 `data`.\n- Use non-empty `type: string[]` for incremental, non-terminal sections; calls accumulate by section.\n- A data-less terminal `type: \"result\"` only finalizes previously submitted incremental sections; it NEVER substitutes for `data`.\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 `data`.\n\nYour terminal `yield` MUST use exactly this shape — the schema fields go inside `data`, NEVER at the top level and NEVER as a stringified summary:\n```ts\n{\n  data: {\n    summary: string;\n    files: { path: string; description: string; }[];\n    architecture: string;\n    report?: string;\n  };\n}\n```\n\nGiving up is a last resort. If truly blocked, you MUST terminal-yield `{ 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<workstation>\n- OS: win32 10.0.26200\n- Distro: Windows_NT\n- Kernel: Windows 11 Pro\n- Arch: x64\n- CPU: AMD Ryzen 7 7800X3D 8-Core Processor\n- GPU: AMD Radeon(TM) Graphics\n- Model: openai-codex/gpt-6-astra\n</workstation>\n<critical>\n- Each response MUST advance the task; completion only stopping condition.\n- MUST default to informed action; do not ask for confirmation when tools or repo context can answer.\n- Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.\n</critical>","task":"Complete assignment thoroughly:\n\n# Target\ncrates/spt-daemon/src/broker.rs and translation.rs in .worktrees/300-input-acceptance. Own-injection exclusion only.\n# Change\nInvestigate actual PTY writer paths for core peer-message delivery, including translated Write, Key, Commit and post-commit/release/typeahead paths. Find where complete exact bytes could be recorded per session without recording human input, with successful write vs queued failures distinguished. Identify delivery normalization already used (CR/bracketed paste vs reported text), existing in-memory state, lock ordering. Do not edit.\n# Acceptance\nReturn minimal concrete recording strategy and all delivery write sites needed; distinguish proof limits. Skip all validation commands.","tools":["read","grep","glob","web_search","yield","hub","spt_checkpoint"],"agent":"scout","modelRole":"smol","resolvedModel":"openai-codex/gpt-6-astra","readOnly":true,"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"}},"optionalProperties":{"report":{"metadata":{"description":"The complete deliverable when the task asks for a report, table, enumeration, or per-item audit — full markdown at the depth requested (tables, path:line anchors, signatures, code excerpts). Never a summary of it; `summary` already covers that. Omit only for quick lookups."},"type":"string"}}},"outputSchemaMode":"permissive"}
{"type":"message","id":"0187fab3","parentId":"c56ca9d0","timestamp":"2026-09-13T10:27:39.901Z","message":{"role":"user","content":[{"type":"text","text":"Complete assignment thoroughly:\n\n# Target\ncrates/spt-daemon/src/broker.rs and translation.rs in .worktrees/300-input-acceptance. Own-injection exclusion only.\n# Change\nInvestigate actual PTY writer paths for core peer-message delivery, including translated Write, Key, Commit and post-commit/release/typeahead paths. Find where complete exact bytes could be recorded per session without recording human input, with successful write vs queued failures distinguished. Identify delivery normalization already used (CR/bracketed paste vs reported text), existing in-memory state, lock ordering. Do not edit.\n# Acceptance\nReturn minimal concrete recording strategy and all delivery write sites needed; distinguish proof limits. Skip all validation commands."}],"attribution":"agent","timestamp":1789295259827}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_imt6TOekSNXxvBXzaSgg51zC|fc_07ba52f17df332c2016aa67a9dc5bc87d2b14c5608122699a5","toolName":"grep","startedAt":"2026-09-13T10:27:44.720Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs"},"intent":"Locating delivery writer paths"},"id":"f03feb30","parentId":"0187fab3","timestamp":"2026-09-13T10:27:44.720Z"}
{"type":"message","id":"98aa81b6","parentId":"f03feb30","timestamp":"2026-09-13T10:27:44.721Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_imt6TOekSNXxvBXzaSgg51zC|fc_07ba52f17df332c2016aa67a9dc5bc87d2b14c5608122699a5","name":"grep","arguments":{"i":"Locating delivery writer paths","pattern":"write_all|write_input|USER_INPUT|typeahead|post_commit|Write\\(|Commit|bracketed|normalize","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","case":false,"gitignore":false,"skip":null},"intent":"Locating delivery writer paths"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":10106,"output":104,"cacheRead":0,"cacheWrite":0,"totalTokens":10210,"cost":{"input":0.10106000000000001,"output":0.005200000000000001,"cacheRead":0,"cacheWrite":0,"total":0.10626000000000001}},"stopReason":"toolUse","timestamp":1789295259900,"responseId":"resp_07ba52f17df332c2016aa67a9c508c87d2b98f35362c810a18","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_07ba52f17df332c2016aa67a9dc5bc87d2b14c5608122699a5","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating delivery writer paths\",\"pattern\":\"write_all|write_input|USER_INPUT|typeahead|post_commit|Write\\\\(|Commit|bracketed|normalize\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs\",\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_imt6TOekSNXxvBXzaSgg51zC","internal_chat_message_metadata_passthrough":{"create_time":1789295260.590421,"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":4817.750900000014,"ttft":1937.1347000000242,"completedAt":1789295264718,"contextSnapshot":{"promptTokens":10106,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"credential_pin","id":"49f69a3d","parentId":"98aa81b6","timestamp":"2026-09-13T10:27:44.721Z","provider":"openai-codex","hash":"1a3c10d7bc41d381cab1511db604bc2637dfcfc63796a890d8b5ee152859e1cb"}
{"type":"message","id":"42b5cec3","parentId":"49f69a3d","timestamp":"2026-09-13T10:27:44.745Z","message":{"role":"toolResult","toolCallId":"call_imt6TOekSNXxvBXzaSgg51zC|fc_07ba52f17df332c2016aa67a9dc5bc87d2b14c5608122699a5","toolName":"grep","content":[{"type":"text","text":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## broker.rs\n 231|/// the slot) is provable in ~1 s instead of 15 (mirrors the\n*232|/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n 233|/// ONLY — never relaxes an assertion.\n 234|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 235|fn brain_write_deadline() -> Duration {\n...\n 616|/// floor — buffering operator input — only while a binary's response to one\n*617|/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n 618|/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n 619|/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n 620|/// buggy translation binary can therefore stall operator input for at most this\n 621|/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n*622|const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 623|\n*624|/// The effective commit deadline. Production is the const above; the int gate\n*625|/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n 626|/// so the structural outcome (floor releases → buffered input flushes → binary\n 627|/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n 628|/// the deadline's PRESENCE is the invariant, not its exact length.\n*629|fn inject_commit_deadline() -> Duration {\n*630|    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 631|        Ok(ms) => ms\n 632|            .parse::<u64>()\n 633|            .map(Duration::from_millis)\n*634|            .unwrap_or(INJECT_COMMIT_DEADLINE),\n*635|        Err(_) => INJECT_COMMIT_DEADLINE,\n 636|    }\n 637|}\n 638|\n*639|/// How many CONSECUTIVE commit-deadline misses (no intervening successful\n*640|/// `{commit}`) a translation binary may accrue before the inject worker declares\n*641|/// a REAL fault (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). A single missed\n*642|/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n 643|/// alive; the watchdog's job is ANTI-STALL (release the operator floor), not\n*644|/// execution-verification. Terminating a healthy binary over one eaten `{commit}`\n 645|/// was B6's exact root: the checkpoint clear boundary self-sends a FIRE-with-\n*646|/// nothing-armed that answers zero records + no bare `{commit}`, so EVERY `/clear`\n 647|/// killed the binary permanently. Only a genuinely WEDGED binary misses this many\n*648|/// in a row; a commit RESETS the counter. Worst-case stall exposure stays bounded\n 649|/// at N × the deadline (the floor still releases every deadline regardless).\n 650|const INJECT_MISS_STRIKE_BUDGET: u32 = 3;\n 651|\n...\n 669|/// it legitimately loses the binary's in-memory pending wake). The counter is\n*670|/// shared with the inject worker and RESET to 0 on a healthy `{commit}`, so a\n 671|/// respawned binary that recovers refreshes the whole budget (the C3(b) give-up-\n 672|/// budget shape: consecutive-failures bounded, healthy-run resets). Mirrors the\n 673|/// psyche crash-loop backoff's bounded give-up.\n...\n 694|// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n*695|// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n 696|// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n 697|\n 698|/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n...\n 723|\n*724|/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n 725|/// the typed payload's head to echo back on the session ring before declaring a\n 726|/// verify MISS (round-trip + XON-resume latency bound).\n 727|const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n...\n 750|/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n*751|/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n 752|/// rather than one write, so no single huge write races the terminal's coalescer.\n 753|/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n 754|/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\n## translation.rs\n 57|/// each line carries exactly one command shape (no discriminator field), matching\n*58|/// the ADR-0022 wire (`{key}` / `{text}` / `{delay_ms}` / `{commit}`). Unknown\n 59|/// extra fields on a recognized shape are ignored (forward-compat); a line\n 60|/// matching no shape is a parse error the reader logs and skips (one bad line\n 61|/// never kills the binary).\n...\n 73|    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\n*74|    /// emits `{\"commit\":true}` after the last keystroke of its response to an\n 75|    /// event, so the broker knows the atomic sequence is complete and can flush\n 76|    /// the buffered controller input race-free. (The v0.11.0 raw-inject —\n 77|    /// `{text:payload}{key:\"enter\"}` — was removed as a delivery path: it did not\n 78|    /// submit on a modern TUI; idle inbound now spools when no binary delivers.)\n*79|    Commit { commit: bool },\n 80|}\n 81|\n 82|/// Map a `{key}` command name to the raw PTY bytes (the \"send-keys\" API).\n...\n 85|/// adapter-template stance: a bad key silences itself rather than wedging the\n*86|/// applier). `Delay`/`Commit` are not keys and are handled by the applier, not\n 87|/// here.\n 88|///\n 89|/// Vocabulary: the common control keys, the four arrows + Home/End/PageUp/\n...\n 158|/// While the floor is **held** (an injected keystroke sequence is mid-flight,\n*159|/// between a `{type:event}` dispatch and its `{commit}`), the operator's\n 160|/// keystrokes from `dispatch_input` are **buffered here** instead of hitting the\n*161|/// PTY; on commit (or the commit deadline) the buffer is flushed to the PTY\n 162|/// **after** the injected bytes. So a stash/restore choreography\n 163|/// (`ctrl+s … ctrl+s`) can never be clobbered by an operator keystroke landing\n 164|/// mid-sequence. spt-core owning every PTY write is what makes this possible.\n...\n 166|/// Pure state (no I/O) so it is unit-testable; the broker holds it behind a\n*167|/// `Mutex` and does the actual `write_input` off-lock (the W1 lesson: never block\n 168|/// a PTY write under a lock the hot input path needs).\n 169|#[derive(Debug, Default)]\n 170|pub struct InjectFloor {\n...\n 203|\n*204|    /// One flush step — call in a loop after `{commit}` / the deadline. If the\n 205|    /// buffer holds bytes, take them to write **off-lock** while the floor STAYS\n 206|    /// held (so input arriving mid-flush keeps buffering → strict order); when the\n 207|    /// buffer is empty, RELEASE the floor and return `None`. Looping until `None`\n...\n 312|        if let Some(w) = guard.as_mut() {\n*313|            w.write_all(line.as_bytes())?;\n*314|            w.write_all(b\"\\n\")?;\n 315|            w.flush()?;\n 316|        }\n 317|        Ok(())\n...\n 442|        assert_eq!(\n*443|            serde_json::from_str::<KeyCmd>(r#\"{\"commit\":true}\"#).unwrap(),\n*444|            KeyCmd::Commit { commit: true }\n 445|        );\n 446|        // forward-compat: an unknown field alongside a known shape still parses.\n 447|        assert_eq!("}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs, .worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":32,"fileCount":2,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":20},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","count":12}],"truncated":true,"perFileLimitReached":20,"displayContent":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## broker.rs\n 231│/// the slot) is provable in ~1 s instead of 15 (mirrors the\n*232│/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n 233│/// ONLY — never relaxes an assertion.\n 234│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 235│fn brain_write_deadline() -> Duration {\n    │...\n 616│/// floor — buffering operator input — only while a binary's response to one\n*617│/// `{type:event}` is in flight. If no `{commit}` arrives within this window the\n 618│/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no\n 619│/// respawn → idle inbound then SPOOLS, never raw-injected), and never re-opens. A wedged or\n 620│/// buggy translation binary can therefore stall operator input for at most this\n 621│/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n*622│const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 623│\n*624│/// The effective commit deadline. Production is the const above; the int gate\n*625│/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`\n 626│/// so the structural outcome (floor releases → buffered input flushes → binary\n 627│/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —\n 628│/// the deadline's PRESENCE is the invariant, not its exact length.\n*629│fn inject_commit_deadline() -> Duration {\n*630│    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 631│        Ok(ms) => ms\n 632│            .parse::<u64>()\n 633│            .map(Duration::from_millis)\n*634│            .unwrap_or(INJECT_COMMIT_DEADLINE),\n*635│        Err(_) => INJECT_COMMIT_DEADLINE,\n 636│    }\n 637│}\n 638│\n*639│/// How many CONSECUTIVE commit-deadline misses (no intervening successful\n*640│/// `{commit}`) a translation binary may accrue before the inject worker declares\n*641│/// a REAL fault (F029 C-1, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE). A single missed\n*642│/// `{commit}` is **not** a fault — the stdin write SUCCEEDED, so the binary is\n 643│/// alive; the watchdog's job is ANTI-STALL (release the operator floor), not\n*644│/// execution-verification. Terminating a healthy binary over one eaten `{commit}`\n 645│/// was B6's exact root: the checkpoint clear boundary self-sends a FIRE-with-\n*646│/// nothing-armed that answers zero records + no bare `{commit}`, so EVERY `/clear`\n 647│/// killed the binary permanently. Only a genuinely WEDGED binary misses this many\n*648│/// in a row; a commit RESETS the counter. Worst-case stall exposure stays bounded\n 649│/// at N × the deadline (the floor still releases every deadline regardless).\n 650│const INJECT_MISS_STRIKE_BUDGET: u32 = 3;\n 651│\n    │...\n 669│/// it legitimately loses the binary's in-memory pending wake). The counter is\n*670│/// shared with the inject worker and RESET to 0 on a healthy `{commit}`, so a\n 671│/// respawned binary that recovers refreshes the whole budget (the C3(b) give-up-\n 672│/// budget shape: consecutive-failures bounded, healthy-run resets). Mirrors the\n 673│/// psyche crash-loop backoff's bounded give-up.\n    │...\n 694│// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n*695│// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n 696│// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n 697│\n 698│/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n    │...\n 723│\n*724│/// The echo-verify wait (Layer 2): how long the worker waits after a `{commit}` for\n 725│/// the typed payload's head to echo back on the session ring before declaring a\n 726│/// verify MISS (round-trip + XON-resume latency bound).\n 727│const INJECT_ECHO_VERIFY_DEADLINE: Duration = Duration::from_millis(1200);\n    │...\n 750│/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n*751│/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n 752│/// rather than one write, so no single huge write races the terminal's coalescer.\n 753│/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n 754│/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\n## translation.rs\n  57│/// each line carries exactly one command shape (no discriminator field), matching\n *58│/// the ADR-0022 wire (`{key}` / `{text}` / `{delay_ms}` / `{commit}`). Unknown\n  59│/// extra fields on a recognized shape are ignored (forward-compat); a line\n  60│/// matching no shape is a parse error the reader logs and skips (one bad line\n  61│/// never kills the binary).\n    │...\n  73│    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\n *74│    /// emits `{\"commit\":true}` after the last keystroke of its response to an\n  75│    /// event, so the broker knows the atomic sequence is complete and can flush\n  76│    /// the buffered controller input race-free. (The v0.11.0 raw-inject —\n  77│    /// `{text:payload}{key:\"enter\"}` — was removed as a delivery path: it did not\n  78│    /// submit on a modern TUI; idle inbound now spools when no binary delivers.)\n *79│    Commit { commit: bool },\n  80│}\n  81│\n  82│/// Map a `{key}` command name to the raw PTY bytes (the \"send-keys\" API).\n    │...\n  85│/// adapter-template stance: a bad key silences itself rather than wedging the\n *86│/// applier). `Delay`/`Commit` are not keys and are handled by the applier, not\n  87│/// here.\n  88│///\n  89│/// Vocabulary: the common control keys, the four arrows + Home/End/PageUp/\n    │...\n 158│/// While the floor is **held** (an injected keystroke sequence is mid-flight,\n*159│/// between a `{type:event}` dispatch and its `{commit}`), the operator's\n 160│/// keystrokes from `dispatch_input` are **buffered here** instead of hitting the\n*161│/// PTY; on commit (or the commit deadline) the buffer is flushed to the PTY\n 162│/// **after** the injected bytes. So a stash/restore choreography\n 163│/// (`ctrl+s … ctrl+s`) can never be clobbered by an operator keystroke landing\n 164│/// mid-sequence. spt-core owning every PTY write is what makes this possible.\n    │...\n 166│/// Pure state (no I/O) so it is unit-testable; the broker holds it behind a\n*167│/// `Mutex` and does the actual `write_input` off-lock (the W1 lesson: never block\n 168│/// a PTY write under a lock the hot input path needs).\n 169│#[derive(Debug, Default)]\n 170│pub struct InjectFloor {\n    │...\n 203│\n*204│    /// One flush step — call in a loop after `{commit}` / the deadline. If the\n 205│    /// buffer holds bytes, take them to write **off-lock** while the floor STAYS\n 206│    /// held (so input arriving mid-flush keeps buffering → strict order); when the\n 207│    /// buffer is empty, RELEASE the floor and return `None`. Looping until `None`\n    │...\n 312│        if let Some(w) = guard.as_mut() {\n*313│            w.write_all(line.as_bytes())?;\n*314│            w.write_all(b\"\\n\")?;\n 315│            w.flush()?;\n 316│        }\n 317│        Ok(())\n    │...\n 442│        assert_eq!(\n*443│            serde_json::from_str::<KeyCmd>(r#\"{\"commit\":true}\"#).unwrap(),\n*444│            KeyCmd::Commit { commit: true }\n 445│        );\n 446│        // forward-compat: an unknown field alongside a known shape still parses.\n 447│        assert_eq!("},"isError":false,"timestamp":1789295264744}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Oo8aLMqW4nka2GLVR74Lx7B8|fc_07ba52f17df332c2016aa67aa8016487d2b5c1058b004511cb","toolName":"grep","startedAt":"2026-09-13T10:27:52.062Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs"},"intent":"Mapping every PTY write"},"id":"2f8a2b12","parentId":"42b5cec3","timestamp":"2026-09-13T10:27:52.062Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_JEIjOFdBfOBflnijfkSkObxr|fc_07ba52f17df332c2016aa67aa8017487d29c9843b5a91835d9","toolName":"read","startedAt":"2026-09-13T10:27:52.063Z","args":{"path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md"},"intent":"Reading acceptance contract"},"id":"07bcd65e","parentId":"2f8a2b12","timestamp":"2026-09-13T10:27:52.063Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GHPhGkq7HbckGxxCLUcmLd4S|fc_07ba52f17df332c2016aa67aa8017887d29efd1c8921266f0d","toolName":"read","startedAt":"2026-09-13T10:27:52.063Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:55-234"},"intent":"Reading translation custody state"},"id":"c63d1647","parentId":"07bcd65e","timestamp":"2026-09-13T10:27:52.063Z"}
{"type":"message","id":"46e9f84f","parentId":"c63d1647","timestamp":"2026-09-13T10:27:52.063Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_Oo8aLMqW4nka2GLVR74Lx7B8|fc_07ba52f17df332c2016aa67aa8016487d2b5c1058b004511cb","name":"grep","arguments":{"i":"Mapping every PTY write","pattern":"write_input|fn .*inject|fn .*commit|fn .*release|fn dispatch_input|struct Session|typeahead|KeyCmd::","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","case":true,"gitignore":false,"skip":null},"intent":"Mapping every PTY write"},{"type":"toolCall","id":"call_JEIjOFdBfOBflnijfkSkObxr|fc_07ba52f17df332c2016aa67aa8017487d29c9843b5a91835d9","name":"read","arguments":{"i":"Reading acceptance contract","path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md"},"intent":"Reading acceptance contract"},{"type":"toolCall","id":"call_GHPhGkq7HbckGxxCLUcmLd4S|fc_07ba52f17df332c2016aa67aa8017887d29efd1c8921266f0d","name":"read","arguments":{"i":"Reading translation custody state","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:55-234"},"intent":"Reading translation custody state"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":2249,"output":183,"cacheRead":9984,"cacheWrite":0,"totalTokens":12416,"cost":{"input":0.022490000000000003,"output":0.00915,"cacheRead":0.009984,"cacheWrite":0,"total":0.041624}},"stopReason":"toolUse","timestamp":1789295264777,"responseId":"resp_07ba52f17df332c2016aa67aa0f8e087d289448b8de49b6790","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_07ba52f17df332c2016aa67aa8016487d2b5c1058b004511cb","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping every PTY write\",\"pattern\":\"write_input|fn .*inject|fn .*commit|fn .*release|fn dispatch_input|struct Session|typeahead|KeyCmd::\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs\",\"case\":true,\"gitignore\":false,\"skip\":null}","call_id":"call_Oo8aLMqW4nka2GLVR74Lx7B8","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67aa8017487d29c9843b5a91835d9","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading acceptance contract\",\"path\":\".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md\"}","call_id":"call_JEIjOFdBfOBflnijfkSkObxr","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67aa8017887d29efd1c8921266f0d","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading translation custody state\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:55-234\"}","call_id":"call_GHPhGkq7HbckGxxCLUcmLd4S","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":7284.056799999991,"ttft":7249.328600000008,"completedAt":1789295272061,"contextSnapshot":{"promptTokens":12233,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"ac3b3022","parentId":"46e9f84f","timestamp":"2026-09-13T10:27:52.076Z","message":{"role":"toolResult","toolCallId":"call_JEIjOFdBfOBflnijfkSkObxr|fc_07ba52f17df332c2016aa67aa8017487d29c9843b5a91835d9","toolName":"read","content":[{"type":"text","text":"# Submission-bound input provenance — the public harness contract\n\n**Status: CONTRACT DRAFT for adapter-owner assessment. Nothing here is implemented.** No `REQ-*` is\nminted or activated by this document, no core behaviour changes on its account, and it grants nothing.\nAuthorized by doyle 2026-09-12 for #300 direction C (confirmed by the operator, relayed\noperator → lia → doyle); the direction record is #300 comment 5645159945.\n\n## The operator decision this contract is bound to\n\n**Provenance follows the SUBMITTER, not the AUTHOR.** The origin used is the computer of the person who\nsubmits the input — never the computer of whoever typed the characters, when those differ.\n\n**Reavo, verbatim as relayed by lia** (lia's message `5Y45PXOU`; relay chain Reavo → lia → doyle →\ntodlando — doyle did NOT receive it directly from Reavo):\n\n> yes, it should use the sender's computer. if the provided filepath doesn't match a file there, then\n> simply skip fileserving\n\n**Two things are NOT inside that quotation and are attributed separately, so nobody cites the operator\nfor them:**\n\n- **\"No reliable sender, no automatic share\"** — the ADOPTED SAFETY RULE (doyle, 2026-09-12). It is a\n  gate policy adopted alongside the decision, not part of Reavo's words.\n- **No prompt, and no fallback to another origin** — doyle's instruction on how the skip behaves\n  (B7ZLOLBZ). The quotation says \"simply skip fileserving\"; that the skip is silent and never retries\n  against a different machine is the gate's reading of it, stated as instruction.\n\n**What this settles and what it does not** (doyle, B7ZLOLBZ): the submitter rule settles the\nEXPERIENCE. Enqueue/dequeue ordering and human-versus-machine injection remain **mechanism questions to\nresolve** — they are not reasons to reopen the settled experience, and an adapter answer that disputes\nthe experience rather than describing its mechanism is answering the wrong question.\n\n**Audience: adapter owners.** This document is deliberately written WITHOUT core internals, because an\nadapter owner must be able to answer \"can my harness do this?\" from public documentation alone. It\ndescribes an interface and a set of obligations, not an implementation.\n\n## 0. The problem in one paragraph\n\nWhen a user drives a remote endpoint (`spt rc` from their machine to an agent on another machine) and\ntheir typed words quote an absolute path on THEIR machine, spt is supposed to serve that file to the\nreceiving agent automatically, as a scoped live reference (#17, ADR-0058 Am.1). It does not, because\nthe two halves never meet: the bytes arrive with a proven origin, and the harness reports the\nSEMANTIC submission later, with no carried association between them. The fix is not to guess the\nassociation. It is for the harness to capture provenance at the moment it accepts a submission, and\ncarry it with that submission.\n\nThe question this contract answers is **\"whose computer does this path refer to?\"**, and the answer is\nthe submitter's. It is deliberately NOT \"who wrote these characters?\" — that question is settled and\nout of scope (see the decision above). An adapter's whole job here is to capture the submitting\nattachment's token at acceptance and carry it, unaltered, to the report.\n\n## 1. Why the obvious shortcut is forbidden\n\n**Never sample the current controller at report time.** Reading \"who is attached right now\" when a\nsubmission is REPORTED is not an answer to \"who submitted this\", for three independent reasons:\n\n- A submission can be **accepted, queued, and reported later**. Between those moments the controller\n  seat can change hands. The sample then names someone who did not submit it.\n- **Continuity does not rescue the sample.** A token minted for B *after* A's submission was queued,\n  still current when that submission is reported, is both unretired and continuously held — and still\n  names the wrong origin. (This refutes an earlier draft of mine; doyle's, 2026-09-12.)\n- \"Somebody is driving\" and \"which node is driving\" are **different facts with different\n  availability** — one can be known while the other is not.\n\n**What the submitter rule does and does not relax.** It removes the question of who typed the\ncharacters: if A types into a buffer and B submits, the origin is **B's computer, deliberately**, and\nthat is the settled experience rather than a defect to engineer around. It does NOT license sampling,\nbecause the sample is taken at the wrong MOMENT, not merely against the wrong PERSON. Acceptance is\nwhen a submitter submits; report time is some time afterwards, and the gap is exactly what this\ncontract exists to bridge.\n\nConsequently: **an adapter that cannot capture at acceptance must produce a named refusal, not a\nfallback.** There is no degraded mode that samples. A refusal is a correct outcome; a guess is not.\n\n## 2. Terms\n\n- **Submission** — one semantic unit of user input that the harness has ACCEPTED from the user: the\n  thing a user means when they say \"I sent that\". Not a keystroke, not a byte chunk, not a turn.\n- **Submitter** — the controlling attachment that SUBMITS a submission at the acceptance surface. Its\n  node is the origin this contract binds to. Deliberately **not** \"whoever typed the characters\": when\n  those differ, the submitter wins, by operator decision. An adapter never needs to determine who\n  typed anything.\n- **Acceptance surface** — the point in the harness where a submission becomes committed by the user\n  and can no longer be edited: where \"the user pressed send\". The contract lives here. If a harness\n  has no such point, it cannot satisfy this contract, and that is the concrete limitation to report.\n- **Provenance token** — an opaque string, issued by spt to a controlling attachment, which carries\n  the authenticated identity of the controller's node. Adapters never parse it, never construct one,\n  and never infer one; they read it, hold it, and hand it back.\n- **Submission id** — an adapter-chosen identifier, unique within the session, naming one submission.\n  It exists so a report can be matched to an acceptance and so a replay is recognisable.\n\n## 3. What the adapter must do\n\n1. **Capture at acceptance, before any queue.** At its acceptance surface, the adapter reads the\n   provenance token then current and stores it WITH that submission, together with a submission id\n   it chooses. The capture must happen before the submission can be queued, deferred, or reordered.\n   Capturing later — at turn start, at tool time, at report time — does not satisfy this contract,\n   because those points are after the boundary the defect lives on.\n\n   **RESOLVED BY THE SUBMITTER RULE — this was the draft's biggest open question and it is now\n   closed.** Earlier revisions treated buffered input across a handoff as an unresolved gap: A types,\n   the bytes sit in a terminal or client buffer, B takes the seat, and the harness accepts a submission\n   whose characters A wrote while the current token is B's. Under Reavo's decision that is **not a\n   defect**: the origin is the SENDER'S computer — B's — and the token current at acceptance is the\n   correct answer, not an approximation of a better one. Adapters must NOT add machinery to attribute\n   text back to whoever typed it.\n\n   What remains is a MECHANISM question, and it is the one to answer: **exactly where your acceptance\n   point sits relative to your queue** — enqueue-versus-dequeue ordering, and whether machine-injected\n   input (hooks, replays, automation writing into the same path) is distinguishable from a human\n   pressing send. Those are unsettled and are what §5 asks about. They are not grounds to reopen the\n   submitter rule.\n2. **Carry it through whatever the harness does next. Authorization binds to the bytes that were\n   accepted.** If the harness queues or delays the submission, the captured token travels with it\n   unchanged.\n\n   **The record is of the ORIGINAL ACCEPTED BYTES, and later rewriting does not invalidate it.** What\n   was accepted was accepted; a harness that edits the text afterwards does not retroactively make the\n   capture false. What rewriting must never do is EXTEND that authorization: a rewritten path, or a\n   path newly introduced after acceptance, gains nothing from the original record. Authorization\n   covers the paths quoted in the bytes whose acceptance was captured, and no others.\n\n   **An adapter that cannot observe later edits MUST NOT PROMISE TO DETECT THEM.** Saying \"I will\n   treat each edit as a new submission\" is a capability claim, and on a harness with no\n   post-acceptance observation it is one the adapter cannot keep. The honest position is to bind the\n   captured bytes, report that later edits are unobservable, and let the contract — not the adapter's\n   optimism — decide what that costs. Reporting the limitation satisfies this clause; silently\n   implying detection does not.\n3. **Report it with the payload.** When the adapter reports the submission to spt, it reports, in one\n   call: the submission id, the captured token, and the **exact final payload text** of that\n   submission. The payload reported must be the text as committed, not a normalised, truncated or\n   re-rendered version.\n4. **Never substitute.** If no token was captured — a local user, a viewer, a harness surface that\n   cannot reach the acceptance point — the adapter reports the submission with **no token**. It must\n   not report the current token instead, and must not synthesise one from terminal bytes, window\n   state or timing.\n5. **Report once per submission.** One acceptance, one report. A retry after a transport failure\n   repeats the same submission id and the same payload.\n\n## 4. What spt guarantees in return\n\n1. **Token issuance.** A controlling attachment over an authenticated remote origin is issued a\n   provenance token bound to that origin, to the session, and to the attachment's own occupancy of\n   the controller seat. A read-only viewer is issued none.\n2. **Binding, and an honest split of who establishes it.** Authorization is computed from the reported\n   token plus the reported payload: the audience is the receiving endpoint, and the only paths\n   considered are absolute paths quoted inside THAT payload. No other text and no terminal stream is\n   read. But receiving a token and a text in one call does NOT by itself establish that the text is\n   what that token's holder submitted, and this contract does not pretend otherwise (doyle,\n   2026-09-12):\n\n   - **The adapter is trusted for, and solely responsible for, the association**: that this payload is\n     the text of the submission whose acceptance it captured, and that the token it reports is the one\n     read at that capture. Core cannot observe the acceptance and cannot reconstruct it afterwards.\n   - **Core can independently verify** only: that the token is authentic and was issued by this node;\n     which authenticated origin node, session and incarnation it was issued to; whether its issuing\n     seat occupancy has ended, and when; that the submission id has not been used before within the\n     session and incarnation; that the payload hashes to the value bound to that submission id on any\n     repeat report; and that the paths it acts on are quoted inside the reported payload.\n   - Therefore a dishonest or defective adapter can misattribute text within the trust it already\n     holds over the session. This contract narrows that to the adapter's own acceptance path; it does\n     not remove it, and no wording in it should be read as core-side proof of authorship.\n3. **Serving semantics, unchanged from #17 / ADR-0058 Am.1.** A live reference on the submitter's own\n   machine, 24h TTL, audience scoped to the receiving agent, and a FILE_ACCESS_HELPER notice emitted\n   once per submission and path.\n\n   **A path that names no file on the submitter's machine is SILENTLY SKIPPED.** Reavo: \"if the\n   provided filepath doesn't match a file there, then simply skip fileserving\". The skip is silent —\n   **no prompt, no notice, no named refusal on that path** (doyle's reading, B7ZLOLBZ) — and there is\n   **no fallback to another origin**: spt does not look for the file on the receiving machine, on a\n   previous controller's machine, or anywhere else. A quoted path that does not resolve on the\n   submitter's computer simply does not become a reference, and the submission is processed exactly as\n   if it had quoted nothing. This is the one non-grant in this contract that is NOT announced, and it\n   is deliberate: a user quoting a path that happens not to exist on their machine is an ordinary\n   event, not an error to report.\n4. **No reliable sender, no automatic share — the adopted safety rule** (doyle, 2026-09-12; a gate\n   policy adopted alongside Reavo's decision, NOT part of it). Where the submitting origin is not\n   established to the standard §4.1 requires, nothing is served automatically. This is a refusal to act\n   on an unestablished origin, and it is distinct from the silent skip above: the skip is a path that\n   resolved to nothing on a KNOWN sender's machine; this is not knowing the sender. **What makes a\n   sender \"unreliable\" is NOT DEFINED in this contract and is not the adapter's to decide** — it is a\n   core-side and gate-side question, deliberately left open rather than invented here.\n5. **Handoff.** A token stops being issued the moment its holder stops holding the seat. A new holder's\n   token can never redeem a previous holder's submission.\n\n   **UNRESOLVED — NOT AN AUTHORIZED GUARANTEE (doyle, 2026-09-12).** The proposal is that a submission\n   whose token was captured while its holder still held the seat stays authorizable for a BOUNDED\n   grace period after the seat changes, on the reasoning that capture and not report is when the\n   SUBMISSION happened. That is a proposal only. It no longer depends on the buffered-authorship\n   question — that is settled (§3.1) — but it does depend on the still-open ordering question (§5.2):\n   if acceptance is at dequeue rather than enqueue, \"captured while its holder held the seat\" is not\n   established by capture alone. It may not be relied upon.\n   The bound also has no value yet and must be justified rather than picked. Until both are ruled, the\n   safe reading is that a token whose issuing occupancy has ended is refused by name.\n6. **Replay.** A submission id is single-use **within the session and incarnation, across every token\n   issued in it** — not merely per token. Rotating or re-issuing a token must not let the same\n   submission register a second time (doyle, 2026-09-12). Re-reporting the same id and payload\n   re-registers nothing, emits no second helper notice, and **does not extend an existing TTL**. The\n   same id with a different payload, or under a different token, is refused by name rather than treated\n   as an update.\n7. **Refusal is named — with exactly one silent exception.** Every AUTHORIZATION non-grant says which\n   condition failed, on the existing declined line: no token; token not valid for this session or\n   incarnation; token past its grace bound; viewer; submission id reused with different payload;\n   payload quoted no absolute path; sender not established (§4.4).\n\n   **The exception is §4.3's missing file, and it is silent by decision, not by omission.** A quoted\n   path that resolves to nothing on the submitter's machine produces no declined line, no notice and\n   no name. The distinction is: a REFUSAL means the authorization failed; a SKIP means authorization\n   was fine and there was simply no file there. Do not collapse the two, and do not add a named refusal\n   for the skip on the grounds that every other outcome has one.\n8. **Separation of concerns, structurally.** Input processing and serving authorization are\n   independent: a refused authorization never alters, delays or fails the submission itself, and a\n   submission is delivered and processed identically whether or not a token rode with it.\n\n## 5. The question for adapter owners\n\nUsing only this document:\n\n1. Does your harness have an **acceptance surface** as defined in §2 — a point where a submission is\n   committed by the user and is not yet queued?\n2. **The ordering question — this replaces the old buffered-authorship question, which is now\n   settled.** Do NOT answer about who typed the characters: under the submitter rule the token current\n   at acceptance is correct by definition, even if someone else typed the text. Answer instead about\n   your QUEUE: does your acceptance point sit at **enqueue** (the submission is captured as it enters\n   the queue) or at **dequeue** (captured as the harness takes it off for processing)? If it is\n   dequeue, what can change the seat between the two, and can your adapter reach the enqueue point at\n   all?\n2b. **Machine-injected input.** Can input reach that same acceptance surface WITHOUT a human pressing\n   send — hooks, replayed or scripted input, automation writing into the same path, a resumed session\n   re-submitting? Can your adapter tell that case apart from a human submission, and what does it\n   currently capture for it? A machine injection that captures the seat-holder's token would attribute\n   a machine's submission to a person, which is a mechanism question this contract has NOT resolved.\n3. At that point, can the adapter **read and store** a value, and keep it associated with that exact\n   submission through any queueing the harness performs? Can the harness EDIT or SPLIT the text after\n   acceptance, and if so can your adapter OBSERVE that it did (§3.2)? Answer what you can observe, not\n   what you would like to guarantee: if there is no post-acceptance observation, say so — an adapter\n   that cannot see later edits must not promise to detect them, and reporting the limitation is the\n   answer this contract wants.\n4. When the submission is reported, can the adapter include the stored value, its own submission id,\n   and the **exact committed payload text** in the same report?\n5. If any of those is impossible, **what precisely is missing** — a hook that does not exist, a hook\n   that fires too late, a buffer the adapter cannot see behind, an association the harness drops, a\n   payload the harness rewrites?\n\nAnswer (5) concretely if it applies. A named limitation is the useful answer and the one that gets a\ndecision; \"probably fine\" is not, and neither is a workaround that samples the current controller.\n**An answer here is feasibility input, not approval**: doyle holds the approval, and the clauses marked\nunresolved below are not guarantees an adapter may build against yet.\n\n## 6. Known open items, so nobody assumes they are settled\n\n- **CLOSED, recorded so nobody reopens it:** \"which controller's text is this, if one typed and\n  another sent\" is SETTLED by Reavo's decision — the sender's computer, full stop. Earlier revisions of\n  this document carried it as the biggest open question; it is not one. Re-raising it is out of scope,\n  and an adapter must not build attribution machinery to answer it.\n- **Enqueue-versus-dequeue ordering is a MECHANISM question, still open** (doyle, B7ZLOLBZ). Where the\n  acceptance point sits relative to the queue decides whether \"the token current at acceptance\" is the\n  submitter's token in practice. Adapter answers to §5.2 are the input to that ruling. It does not\n  reopen the experience.\n- **ORIGIN SIGNALING IS AN UNRESOLVED REQUIREMENT, not an adapter chore** (doyle). Machine-injected\n  submissions were MEASURED indistinguishable from human ones at the harness level (§7), and the only\n  separator in the field today is an adapter recognising its own transport's payload shapes — a\n  heuristic that establishes \"this looks like something spt sent\", never \"a person submitted this\".\n  Recognizing payload shapes is not authenticated human provenance. What is missing is a trustworthy\n  origin signal; until one exists such submissions report with no token, which is a floor and not a\n  solution. Adapter answers to §5.2b describe the gap; they do not close it.\n- **A resumed session re-submitting and externally scripted input are SEPARATE, both uncharacterised**,\n  and external automation is **not** equivalent to a resumed session — neither answer follows from the\n  other.\n- **What makes a sender \"unreliable\" (§4.4) is UNDEFINED here** and is deliberately not the adapter's\n  to decide. The adopted safety rule says no automatic share in that case; the threshold is core-side\n  and gate-side.\n- **The post-handoff grace period (§4.5) is a PROPOSAL, not an authorized guarantee.** The submitter\n  rule reduces its urgency — the submitter is by definition whoever held the seat at acceptance — but\n  it is still unruled, and its bound still has **no value**; it must be justified, not picked.\n- **§4.2's split of responsibility is deliberate**: the association is the adapter's to establish and\n  core cannot verify it independently. Nothing in this contract is core-side proof of authorship.\n- Whether the token is readable through an existing authenticated read verb, or needs a new surface,\n  is **not decided** and is deliberately absent from this contract — it is an implementation question\n  and adapter owners should not need it to answer §5.\n- Acceptance criteria for #300 live on the issue (comment 5645159945) and are proposed, not granted.\n- If no shipped adapter can satisfy §3.1, that is a concrete limitation for an operator decision via\n  lia, not a licence to reduce the behaviour.\n- The **silent skip** (§4.3) is the one non-grant that emits nothing. It is easy to misread as a bug\n  report waiting to happen — \"the file didn't get shared and nothing said why\" — and it is deliberate.\n  If experience shows users need a signal, that is a new operator decision, not a contract correction.\n\n## 7. Adapter answers on record\n\n### claude-spt (perri, 2026-09-12) — answered against this frame, with measurements\n\n**§5.1 acceptance surface: YES.** `UserPromptSubmit` fires when a submission is committed, before the\nmodel sees it, carrying the exact committed text and session id, and can block.\n\n**Two holes, and they are TWO SEPARATE POPULATIONS** — perri's precision fix, kept apart here because\nthey need different contract language and merging them states something false about both:\n\n- **All submissions: 302 audited, 227 hook-covered.** The 75 misses are harness-synthesised transcript\n  entries OR built-in local slash commands (`/compact`, `/reload-plugins` mid-session are the clean\n  cases). This is a **surface gap the adapter cannot close**.\n…\n\n…\n\n…\n\n…\nlane: todlando. Adapter side: NONE.\n\n[Showing lines 1-300 of 436. Use :301 to continue]"}],"details":{"truncation":{"content":"# Submission-bound input provenance — the public harness contract\r\n\r\n**Status: CONTRACT DRAFT for adapter-owner assessment. Nothing here is implemented.** No `REQ-*` is\r\nminted or activated by this document, no core behaviour changes on its account, and it grants nothing.\r\nAuthorized by doyle 2026-09-12 for #300 direction C (confirmed by the operator, relayed\r\noperator → lia → doyle); the direction record is #300 comment 5645159945.\r\n\r\n## The operator decision this contract is bound to\r\n\r\n**Provenance follows the SUBMITTER, not the AUTHOR.** The origin used is the computer of the person who\r\nsubmits the input — never the computer of whoever typed the characters, when those differ.\r\n\r\n**Reavo, verbatim as relayed by lia** (lia's message `5Y45PXOU`; relay chain Reavo → lia → doyle →\r\ntodlando — doyle did NOT receive it directly from Reavo):\r\n\r\n> yes, it should use the sender's computer. if the provided filepath doesn't match a file there, then\r\n> simply skip fileserving\r\n\r\n**Two things are NOT inside that quotation and are attributed separately, so nobody cites the operator\r\nfor them:**\r\n\r\n- **\"No reliable sender, no automatic share\"** — the ADOPTED SAFETY RULE (doyle, 2026-09-12). It is a\r\n  gate policy adopted alongside the decision, not part of Reavo's words.\r\n- **No prompt, and no fallback to another origin** — doyle's instruction on how the skip behaves\r\n  (B7ZLOLBZ). The quotation says \"simply skip fileserving\"; that the skip is silent and never retries\r\n  against a different machine is the gate's reading of it, stated as instruction.\r\n\r\n**What this settles and what it does not** (doyle, B7ZLOLBZ): the submitter rule settles the\r\nEXPERIENCE. Enqueue/dequeue ordering and human-versus-machine injection remain **mechanism questions to\r\nresolve** — they are not reasons to reopen the settled experience, and an adapter answer that disputes\r\nthe experience rather than describing its mechanism is answering the wrong question.\r\n\r\n**Audience: adapter owners.** This document is deliberately written WITHOUT core internals, because an\r\nadapter owner must be able to answer \"can my harness do this?\" from public documentation alone. It\r\ndescribes an interface and a set of obligations, not an implementation.\r\n\r\n## 0. The problem in one paragraph\r\n\r\nWhen a user drives a remote endpoint (`spt rc` from their machine to an agent on another machine) and\r\ntheir typed words quote an absolute path on THEIR machine, spt is supposed to serve that file to the\r\nreceiving agent automatically, as a scoped live reference (#17, ADR-0058 Am.1). It does not, because\r\nthe two halves never meet: the bytes arrive with a proven origin, and the harness reports the\r\nSEMANTIC submission later, with no carried association between them. The fix is not to guess the\r\nassociation. It is for the harness to capture provenance at the moment it accepts a submission, and\r\ncarry it with that submission.\r\n\r\nThe question this contract answers is **\"whose computer does this path refer to?\"**, and the answer is\r\nthe submitter's. It is deliberately NOT \"who wrote these characters?\" — that question is settled and\r\nout of scope (see the decision above). An adapter's whole job here is to capture the submitting\r\nattachment's token at acceptance and carry it, unaltered, to the report.\r\n\r\n## 1. Why the obvious shortcut is forbidden\r\n\r\n**Never sample the current controller at report time.** Reading \"who is attached right now\" when a\r\nsubmission is REPORTED is not an answer to \"who submitted this\", for three independent reasons:\r\n\r\n- A submission can be **accepted, queued, and reported later**. Between those moments the controller\r\n  seat can change hands. The sample then names someone who did not submit it.\r\n- **Continuity does not rescue the sample.** A token minted for B *after* A's submission was queued,\r\n  still current when that submission is reported, is both unretired and continuously held — and still\r\n  names the wrong origin. (This refutes an earlier draft of mine; doyle's, 2026-09-12.)\r\n- \"Somebody is driving\" and \"which node is driving\" are **different facts with different\r\n  availability** — one can be known while the other is not.\r\n\r\n**What the submitter rule does and does not relax.** It removes the question of who typed the\r\ncharacters: if A types into a buffer and B submits, the origin is **B's computer, deliberately**, and\r\nthat is the settled experience rather than a defect to engineer around. It does NOT license sampling,\r\nbecause the sample is taken at the wrong MOMENT, not merely against the wrong PERSON. Acceptance is\r\nwhen a submitter submits; report time is some time afterwards, and the gap is exactly what this\r\ncontract exists to bridge.\r\n\r\nConsequently: **an adapter that cannot capture at acceptance must produce a named refusal, not a\r\nfallback.** There is no degraded mode that samples. A refusal is a correct outcome; a guess is not.\r\n\r\n## 2. Terms\r\n\r\n- **Submission** — one semantic unit of user input that the harness has ACCEPTED from the user: the\r\n  thing a user means when they say \"I sent that\". Not a keystroke, not a byte chunk, not a turn.\r\n- **Submitter** — the controlling attachment that SUBMITS a submission at the acceptance surface. Its\r\n  node is the origin this contract binds to. Deliberately **not** \"whoever typed the characters\": when\r\n  those differ, the submitter wins, by operator decision. An adapter never needs to determine who\r\n  typed anything.\r\n- **Acceptance surface** — the point in the harness where a submission becomes committed by the user\r\n  and can no longer be edited: where \"the user pressed send\". The contract lives here. If a harness\r\n  has no such point, it cannot satisfy this contract, and that is the concrete limitation to report.\r\n- **Provenance token** — an opaque string, issued by spt to a controlling attachment, which carries\r\n  the authenticated identity of the controller's node. Adapters never parse it, never construct one,\r\n  and never infer one; they read it, hold it, and hand it back.\r\n- **Submission id** — an adapter-chosen identifier, unique within the session, naming one submission.\r\n  It exists so a report can be matched to an acceptance and so a replay is recognisable.\r\n\r\n## 3. What the adapter must do\r\n\r\n1. **Capture at acceptance, before any queue.** At its acceptance surface, the adapter reads the\r\n   provenance token then current and stores it WITH that submission, together with a submission id\r\n   it chooses. The capture must happen before the submission can be queued, deferred, or reordered.\r\n   Capturing later — at turn start, at tool time, at report time — does not satisfy this contract,\r\n   because those points are after the boundary the defect lives on.\r\n\r\n   **RESOLVED BY THE SUBMITTER RULE — this was the draft's biggest open question and it is now\r\n   closed.** Earlier revisions treated buffered input across a handoff as an unresolved gap: A types,\r\n   the bytes sit in a terminal or client buffer, B takes the seat, and the harness accepts a submission\r\n   whose characters A wrote while the current token is B's. Under Reavo's decision that is **not a\r\n   defect**: the origin is the SENDER'S computer — B's — and the token current at acceptance is the\r\n   correct answer, not an approximation of a better one. Adapters must NOT add machinery to attribute\r\n   text back to whoever typed it.\r\n\r\n   What remains is a MECHANISM question, and it is the one to answer: **exactly where your acceptance\r\n   point sits relative to your queue** — enqueue-versus-dequeue ordering, and whether machine-injected\r\n   input (hooks, replays, automation writing into the same path) is distinguishable from a human\r\n   pressing send. Those are unsettled and are what §5 asks about. They are not grounds to reopen the\r\n   submitter rule.\r\n2. **Carry it through whatever the harness does next. Authorization binds to the bytes that were\r\n   accepted.** If the harness queues or delays the submission, the captured token travels with it\r\n   unchanged.\r\n\r\n   **The record is of the ORIGINAL ACCEPTED BYTES, and later rewriting does not invalidate it.** What\r\n   was accepted was accepted; a harness that edits the text afterwards does not retroactively make the\r\n   capture false. What rewriting must never do is EXTEND that authorization: a rewritten path, or a\r\n   path newly introduced after acceptance, gains nothing from the original record. Authorization\r\n   covers the paths quoted in the bytes whose acceptance was captured, and no others.\r\n\r\n   **An adapter that cannot observe later edits MUST NOT PROMISE TO DETECT THEM.** Saying \"I will\r\n   treat each edit as a new submission\" is a capability claim, and on a harness with no\r\n   post-acceptance observation it is one the adapter cannot keep. The honest position is to bind the\r\n   captured bytes, report that later edits are unobservable, and let the contract — not the adapter's\r\n   optimism — decide what that costs. Reporting the limitation satisfies this clause; silently\r\n   implying detection does not.\r\n3. **Report it with the payload.** When the adapter reports the submission to spt, it reports, in one\r\n   call: the submission id, the captured token, and the **exact final payload text** of that\r\n   submission. The payload reported must be the text as committed, not a normalised, truncated or\r\n   re-rendered version.\r\n4. **Never substitute.** If no token was captured — a local user, a viewer, a harness surface that\r\n   cannot reach the acceptance point — the adapter reports the submission with **no token**. It must\r\n   not report the current token instead, and must not synthesise one from terminal bytes, window\r\n   state or timing.\r\n5. **Report once per submission.** One acceptance, one report. A retry after a transport failure\r\n   repeats the same submission id and the same payload.\r\n\r\n## 4. What spt guarantees in return\r\n\r\n1. **Token issuance.** A controlling attachment over an authenticated remote origin is issued a\r\n   provenance token bound to that origin, to the session, and to the attachment's own occupancy of\r\n   the controller seat. A read-only viewer is issued none.\r\n2. **Binding, and an honest split of who establishes it.** Authorization is computed from the reported\r\n   token plus the reported payload: the audience is the receiving endpoint, and the only paths\r\n   considered are absolute paths quoted inside THAT payload. No other text and no terminal stream is\r\n   read. But receiving a token and a text in one call does NOT by itself establish that the text is\r\n   what that token's holder submitted, and this contract does not pretend otherwise (doyle,\r\n   2026-09-12):\r\n\r\n   - **The adapter is trusted for, and solely responsible for, the association**: that this payload is\r\n     the text of the submission whose acceptance it captured, and that the token it reports is the one\r\n     read at that capture. Core cannot observe the acceptance and cannot reconstruct it afterwards.\r\n   - **Core can independently verify** only: that the token is authentic and was issued by this node;\r\n     which authenticated origin node, session and incarnation it was issued to; whether its issuing\r\n     seat occupancy has ended, and when; that the submission id has not been used before within the\r\n     session and incarnation; that the payload hashes to the value bound to that submission id on any\r\n     repeat report; and that the paths it acts on are quoted inside the reported payload.\r\n   - Therefore a dishonest or defective adapter can misattribute text within the trust it already\r\n     holds over the session. This contract narrows that to the adapter's own acceptance path; it does\r\n     not remove it, and no wording in it should be read as core-side proof of authorship.\r\n3. **Serving semantics, unchanged from #17 / ADR-0058 Am.1.** A live reference on the submitter's own\r\n   machine, 24h TTL, audience scoped to the receiving agent, and a FILE_ACCESS_HELPER notice emitted\r\n   once per submission and path.\r\n\r\n   **A path that names no file on the submitter's machine is SILENTLY SKIPPED.** Reavo: \"if the\r\n   provided filepath doesn't match a file there, then simply skip fileserving\". The skip is silent —\r\n   **no prompt, no notice, no named refusal on that path** (doyle's reading, B7ZLOLBZ) — and there is\r\n   **no fallback to another origin**: spt does not look for the file on the receiving machine, on a\r\n   previous controller's machine, or anywhere else. A quoted path that does not resolve on the\r\n   submitter's computer simply does not become a reference, and the submission is processed exactly as\r\n   if it had quoted nothing. This is the one non-grant in this contract that is NOT announced, and it\r\n   is deliberate: a user quoting a path that happens not to exist on their machine is an ordinary\r\n   event, not an error to report.\r\n4. **No reliable sender, no automatic share — the adopted safety rule** (doyle, 2026-09-12; a gate\r\n   policy adopted alongside Reavo's decision, NOT part of it). Where the submitting origin is not\r\n   established to the standard §4.1 requires, nothing is served automatically. This is a refusal to act\r\n   on an unestablished origin, and it is distinct from the silent skip above: the skip is a path that\r\n   resolved to nothing on a KNOWN sender's machine; this is not knowing the sender. **What makes a\r\n   sender \"unreliable\" is NOT DEFINED in this contract and is not the adapter's to decide** — it is a\r\n   core-side and gate-side question, deliberately left open rather than invented here.\r\n5. **Handoff.** A token stops being issued the moment its holder stops holding the seat. A new holder's\r\n   token can never redeem a previous holder's submission.\r\n\r\n   **UNRESOLVED — NOT AN AUTHORIZED GUARANTEE (doyle, 2026-09-12).** The proposal is that a submission\r\n   whose token was captured while its holder still held the seat stays authorizable for a BOUNDED\r\n   grace period after the seat changes, on the reasoning that capture and not report is when the\r\n   SUBMISSION happened. That is a proposal only. It no longer depends on the buffered-authorship\r\n   question — that is settled (§3.1) — but it does depend on the still-open ordering question (§5.2):\r\n   if acceptance is at dequeue rather than enqueue, \"captured while its holder held the seat\" is not\r\n   established by capture alone. It may not be relied upon.\r\n   The bound also has no value yet and must be justified rather than picked. Until both are ruled, the\r\n   safe reading is that a token whose issuing occupancy has ended is refused by name.\r\n6. **Replay.** A submission id is single-use **within the session and incarnation, across every token\r\n   issued in it** — not merely per token. Rotating or re-issuing a token must not let the same\r\n   submission register a second time (doyle, 2026-09-12). Re-reporting the same id and payload\r\n   re-registers nothing, emits no second helper notice, and **does not extend an existing TTL**. The\r\n   same id with a different payload, or under a different token, is refused by name rather than treated\r\n   as an update.\r\n7. **Refusal is named — with exactly one silent exception.** Every AUTHORIZATION non-grant says which\r\n   condition failed, on the existing declined line: no token; token not valid for this session or\r\n   incarnation; token past its grace bound; viewer; submission id reused with different payload;\r\n   payload quoted no absolute path; sender not established (§4.4).\r\n\r\n   **The exception is §4.3's missing file, and it is silent by decision, not by omission.** A quoted\r\n   path that resolves to nothing on the submitter's machine produces no declined line, no notice and\r\n   no name. The distinction is: a REFUSAL means the authorization failed; a SKIP means authorization\r\n   was fine and there was simply no file there. Do not collapse the two, and do not add a named refusal\r\n   for the skip on the grounds that every other outcome has one.\r\n8. **Separation of concerns, structurally.** Input processing and serving authorization are\r\n   independent: a refused authorization never alters, delays or fails the submission itself, and a\r\n   submission is delivered and processed identically whether or not a token rode with it.\r\n\r\n## 5. The question for adapter owners\r\n\r\nUsing only this document:\r\n\r\n1. Does your harness have an **acceptance surface** as defined in §2 — a point where a submission is\r\n   committed by the user and is not yet queued?\r\n2. **The ordering question — this replaces the old buffered-authorship question, which is now\r\n   settled.** Do NOT answer about who typed the characters: under the submitter rule the token current\r\n   at acceptance is correct by definition, even if someone else typed the text. Answer instead about\r\n   your QUEUE: does your acceptance point sit at **enqueue** (the submission is captured as it enters\r\n   the queue) or at **dequeue** (captured as the harness takes it off for processing)? If it is\r\n   dequeue, what can change the seat between the two, and can your adapter reach the enqueue point at\r\n   all?\r\n2b. **Machine-injected input.** Can input reach that same acceptance surface WITHOUT a human pressing\r\n   send — hooks, replayed or scripted input, automation writing into the same path, a resumed session\r\n   re-submitting? Can your adapter tell that case apart from a human submission, and what does it\r\n   currently capture for it? A machine injection that captures the seat-holder's token would attribute\r\n   a machine's submission to a person, which is a mechanism question this contract has NOT resolved.\r\n3. At that point, can the adapter **read and store** a value, and keep it associated with that exact\r\n   submission through any queueing the harness performs? Can the harness EDIT or SPLIT the text after\r\n   acceptance, and if so can your adapter OBSERVE that it did (§3.2)? Answer what you can observe, not\r\n   what you would like to guarantee: if there is no post-acceptance observation, say so — an adapter\r\n   that cannot see later edits must not promise to detect them, and reporting the limitation is the\r\n   answer this contract wants.\r\n4. When the submission is reported, can the adapter include the stored value, its own submission id,\r\n   and the **exact committed payload text** in the same report?\r\n5. If any of those is impossible, **what precisely is missing** — a hook that does not exist, a hook\r\n   that fires too late, a buffer the adapter cannot see behind, an association the harness drops, a\r\n   payload the harness rewrites?\r\n\r\nAnswer (5) concretely if it applies. A named limitation is the useful answer and the one that gets a\r\ndecision; \"probably fine\" is not, and neither is a workaround that samples the current controller.\r\n**An answer here is feasibility input, not approval**: doyle holds the approval, and the clauses marked\r\nunresolved below are not guarantees an adapter may build against yet.\r\n\r\n## 6. Known open items, so nobody assumes they are settled\r\n\r\n- **CLOSED, recorded so nobody reopens it:** \"which controller's text is this, if one typed and\r\n  another sent\" is SETTLED by Reavo's decision — the sender's computer, full stop. Earlier revisions of\r\n  this document carried it as the biggest open question; it is not one. Re-raising it is out of scope,\r\n  and an adapter must not build attribution machinery to answer it.\r\n- **Enqueue-versus-dequeue ordering is a MECHANISM question, still open** (doyle, B7ZLOLBZ). Where the\r\n  acceptance point sits relative to the queue decides whether \"the token current at acceptance\" is the\r\n  submitter's token in practice. Adapter answers to §5.2 are the input to that ruling. It does not\r\n  reopen the experience.\r\n- **ORIGIN SIGNALING IS AN UNRESOLVED REQUIREMENT, not an adapter chore** (doyle). Machine-injected\r\n  submissions were MEASURED indistinguishable from human ones at the harness level (§7), and the only\r\n  separator in the field today is an adapter recognising its own transport's payload shapes — a\r\n  heuristic that establishes \"this looks like something spt sent\", never \"a person submitted this\".\r\n  Recognizing payload shapes is not authenticated human provenance. What is missing is a trustworthy\r\n  origin signal; until one exists such submissions report with no token, which is a floor and not a\r\n  solution. Adapter answers to §5.2b describe the gap; they do not close it.\r\n- **A resumed session re-submitting and externally scripted input are SEPARATE, both uncharacterised**,\r\n  and external automation is **not** equivalent to a resumed session — neither answer follows from the\r\n  other.\r\n- **What makes a sender \"unreliable\" (§4.4) is UNDEFINED here** and is deliberately not the adapter's\r\n  to decide. The adopted safety rule says no automatic share in that case; the threshold is core-side\r\n  and gate-side.\r\n- **The post-handoff grace period (§4.5) is a PROPOSAL, not an authorized guarantee.** The submitter\r\n  rule reduces its urgency — the submitter is by definition whoever held the seat at acceptance — but\r\n  it is still unruled, and its bound still has **no value**; it must be justified, not picked.\r\n- **§4.2's split of responsibility is deliberate**: the association is the adapter's to establish and\r\n  core cannot verify it independently. Nothing in this contract is core-side proof of authorship.\r\n- Whether the token is readable through an existing authenticated read verb, or needs a new surface,\r\n  is **not decided** and is deliberately absent from this contract — it is an implementation question\r\n  and adapter owners should not need it to answer §5.\r\n- Acceptance criteria for #300 live on the issue (comment 5645159945) and are proposed, not granted.\r\n- If no shipped adapter can satisfy §3.1, that is a concrete limitation for an operator decision via\r\n  lia, not a licence to reduce the behaviour.\r\n- The **silent skip** (§4.3) is the one non-grant that emits nothing. It is easy to misread as a bug\r\n  report waiting to happen — \"the file didn't get shared and nothing said why\" — and it is deliberate.\r\n  If experience shows users need a signal, that is a new operator decision, not a contract correction.\r\n\r\n## 7. Adapter answers on record\r\n\r\n### claude-spt (perri, 2026-09-12) — answered against this frame, with measurements\r\n\r\n**§5.1 acceptance surface: YES.** `UserPromptSubmit` fires when a submission is committed, before the\r\nmodel sees it, carrying the exact committed text and session id, and can block.\r\n\r\n**Two holes, and they are TWO SEPARATE POPULATIONS** — perri's precision fix, kept apart here because\r\nthey need different contract language and merging them states something false about both:\r\n\r\n- **All submissions: 302 audited, 227 hook-covered.** The 75 misses are harness-synthesised transcript\r\n  entries OR built-in local slash commands (`/compact`, `/reload-plugins` mid-session are the clean\r\n  cases). This is a **surface gap the adapter cannot close**.\r","truncated":true,"truncatedBy":"lines","totalLines":436,"totalBytes":22966,"outputLines":300,"outputBytes":22966,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":436,"displayContent":{"text":"# Submission-bound input provenance — the public harness contract\r\n\r\n**Status: CONTRACT DRAFT for adapter-owner assessment. Nothing here is implemented.** No `REQ-*` is\r\nminted or activated by this document, no core behaviour changes on its account, and it grants nothing.\r\nAuthorized by doyle 2026-09-12 for #300 direction C (confirmed by the operator, relayed\r\noperator → lia → doyle); the direction record is #300 comment 5645159945.\r\n\r\n## The operator decision this contract is bound to\r\n\r\n**Provenance follows the SUBMITTER, not the AUTHOR.** The origin used is the computer of the person who\r\nsubmits the input — never the computer of whoever typed the characters, when those differ.\r\n\r\n**Reavo, verbatim as relayed by lia** (lia's message `5Y45PXOU`; relay chain Reavo → lia → doyle →\r\ntodlando — doyle did NOT receive it directly from Reavo):\r\n\r\n> yes, it should use the sender's computer. if the provided filepath doesn't match a file there, then\r\n> simply skip fileserving\r\n\r\n**Two things are NOT inside that quotation and are attributed separately, so nobody cites the operator\r\nfor them:**\r\n\r\n- **\"No reliable sender, no automatic share\"** — the ADOPTED SAFETY RULE (doyle, 2026-09-12). It is a\r\n  gate policy adopted alongside the decision, not part of Reavo's words.\r\n- **No prompt, and no fallback to another origin** — doyle's instruction on how the skip behaves\r\n  (B7ZLOLBZ). The quotation says \"simply skip fileserving\"; that the skip is silent and never retries\r\n  against a different machine is the gate's reading of it, stated as instruction.\r\n\r\n**What this settles and what it does not** (doyle, B7ZLOLBZ): the submitter rule settles the\r\nEXPERIENCE. Enqueue/dequeue ordering and human-versus-machine injection remain **mechanism questions to\r\nresolve** — they are not reasons to reopen the settled experience, and an adapter answer that disputes\r\nthe experience rather than describing its mechanism is answering the wrong question.\r\n\r\n**Audience: adapter owners.** This document is deliberately written WITHOUT core internals, because an\r\nadapter owner must be able to answer \"can my harness do this?\" from public documentation alone. It\r\ndescribes an interface and a set of obligations, not an implementation.\r\n\r\n## 0. The problem in one paragraph\r\n\r\nWhen a user drives a remote endpoint (`spt rc` from their machine to an agent on another machine) and\r\ntheir typed words quote an absolute path on THEIR machine, spt is supposed to serve that file to the\r\nreceiving agent automatically, as a scoped live reference (#17, ADR-0058 Am.1). It does not, because\r\nthe two halves never meet: the bytes arrive with a proven origin, and the harness reports the\r\nSEMANTIC submission later, with no carried association between them. The fix is not to guess the\r\nassociation. It is for the harness to capture provenance at the moment it accepts a submission, and\r\ncarry it with that submission.\r\n\r\nThe question this contract answers is **\"whose computer does this path refer to?\"**, and the answer is\r\nthe submitter's. It is deliberately NOT \"who wrote these characters?\" — that question is settled and\r\nout of scope (see the decision above). An adapter's whole job here is to capture the submitting\r\nattachment's token at acceptance and carry it, unaltered, to the report.\r\n\r\n## 1. Why the obvious shortcut is forbidden\r\n\r\n**Never sample the current controller at report time.** Reading \"who is attached right now\" when a\r\nsubmission is REPORTED is not an answer to \"who submitted this\", for three independent reasons:\r\n\r\n- A submission can be **accepted, queued, and reported later**. Between those moments the controller\r\n  seat can change hands. The sample then names someone who did not submit it.\r\n- **Continuity does not rescue the sample.** A token minted for B *after* A's submission was queued,\r\n  still current when that submission is reported, is both unretired and continuously held — and still\r\n  names the wrong origin. (This refutes an earlier draft of mine; doyle's, 2026-09-12.)\r\n- \"Somebody is driving\" and \"which node is driving\" are **different facts with different\r\n  availability** — one can be known while the other is not.\r\n\r\n**What the submitter rule does and does not relax.** It removes the question of who typed the\r\ncharacters: if A types into a buffer and B submits, the origin is **B's computer, deliberately**, and\r\nthat is the settled experience rather than a defect to engineer around. It does NOT license sampling,\r\nbecause the sample is taken at the wrong MOMENT, not merely against the wrong PERSON. Acceptance is\r\nwhen a submitter submits; report time is some time afterwards, and the gap is exactly what this\r\ncontract exists to bridge.\r\n\r\nConsequently: **an adapter that cannot capture at acceptance must produce a named refusal, not a\r\nfallback.** There is no degraded mode that samples. A refusal is a correct outcome; a guess is not.\r\n\r\n## 2. Terms\r\n\r\n- **Submission** — one semantic unit of user input that the harness has ACCEPTED from the user: the\r\n  thing a user means when they say \"I sent that\". Not a keystroke, not a byte chunk, not a turn.\r\n- **Submitter** — the controlling attachment that SUBMITS a submission at the acceptance surface. Its\r\n  node is the origin this contract binds to. Deliberately **not** \"whoever typed the characters\": when\r\n  those differ, the submitter wins, by operator decision. An adapter never needs to determine who\r\n  typed anything.\r\n- **Acceptance surface** — the point in the harness where a submission becomes committed by the user\r\n  and can no longer be edited: where \"the user pressed send\". The contract lives here. If a harness\r\n  has no such point, it cannot satisfy this contract, and that is the concrete limitation to report.\r\n- **Provenance token** — an opaque string, issued by spt to a controlling attachment, which carries\r\n  the authenticated identity of the controller's node. Adapters never parse it, never construct one,\r\n  and never infer one; they read it, hold it, and hand it back.\r\n- **Submission id** — an adapter-chosen identifier, unique within the session, naming one submission.\r\n  It exists so a report can be matched to an acceptance and so a replay is recognisable.\r\n\r\n## 3. What the adapter must do\r\n\r\n1. **Capture at acceptance, before any queue.** At its acceptance surface, the adapter reads the\r\n   provenance token then current and stores it WITH that submission, together with a submission id\r\n   it chooses. The capture must happen before the submission can be queued, deferred, or reordered.\r\n   Capturing later — at turn start, at tool time, at report time — does not satisfy this contract,\r\n   because those points are after the boundary the defect lives on.\r\n\r\n   **RESOLVED BY THE SUBMITTER RULE — this was the draft's biggest open question and it is now\r\n   closed.** Earlier revisions treated buffered input across a handoff as an unresolved gap: A types,\r\n   the bytes sit in a terminal or client buffer, B takes the seat, and the harness accepts a submission\r\n   whose characters A wrote while the current token is B's. Under Reavo's decision that is **not a\r\n   defect**: the origin is the SENDER'S computer — B's — and the token current at acceptance is the\r\n   correct answer, not an approximation of a better one. Adapters must NOT add machinery to attribute\r\n   text back to whoever typed it.\r\n\r\n   What remains is a MECHANISM question, and it is the one to answer: **exactly where your acceptance\r\n   point sits relative to your queue** — enqueue-versus-dequeue ordering, and whether machine-injected\r\n   input (hooks, replays, automation writing into the same path) is distinguishable from a human\r\n   pressing send. Those are unsettled and are what §5 asks about. They are not grounds to reopen the\r\n   submitter rule.\r\n2. **Carry it through whatever the harness does next. Authorization binds to the bytes that were\r\n   accepted.** If the harness queues or delays the submission, the captured token travels with it\r\n   unchanged.\r\n\r\n   **The record is of the ORIGINAL ACCEPTED BYTES, and later rewriting does not invalidate it.** What\r\n   was accepted was accepted; a harness that edits the text afterwards does not retroactively make the\r\n   capture false. What rewriting must never do is EXTEND that authorization: a rewritten path, or a\r\n   path newly introduced after acceptance, gains nothing from the original record. Authorization\r\n   covers the paths quoted in the bytes whose acceptance was captured, and no others.\r\n\r\n   **An adapter that cannot observe later edits MUST NOT PROMISE TO DETECT THEM.** Saying \"I will\r\n   treat each edit as a new submission\" is a capability claim, and on a harness with no\r\n   post-acceptance observation it is one the adapter cannot keep. The honest position is to bind the\r\n   captured bytes, report that later edits are unobservable, and let the contract — not the adapter's\r\n   optimism — decide what that costs. Reporting the limitation satisfies this clause; silently\r\n   implying detection does not.\r\n3. **Report it with the payload.** When the adapter reports the submission to spt, it reports, in one\r\n   call: the submission id, the captured token, and the **exact final payload text** of that\r\n   submission. The payload reported must be the text as committed, not a normalised, truncated or\r\n   re-rendered version.\r\n4. **Never substitute.** If no token was captured — a local user, a viewer, a harness surface that\r\n   cannot reach the acceptance point — the adapter reports the submission with **no token**. It must\r\n   not report the current token instead, and must not synthesise one from terminal bytes, window\r\n   state or timing.\r\n5. **Report once per submission.** One acceptance, one report. A retry after a transport failure\r\n   repeats the same submission id and the same payload.\r\n\r\n## 4. What spt guarantees in return\r\n\r\n1. **Token issuance.** A controlling attachment over an authenticated remote origin is issued a\r\n   provenance token bound to that origin, to the session, and to the attachment's own occupancy of\r\n   the controller seat. A read-only viewer is issued none.\r\n2. **Binding, and an honest split of who establishes it.** Authorization is computed from the reported\r\n   token plus the reported payload: the audience is the receiving endpoint, and the only paths\r\n   considered are absolute paths quoted inside THAT payload. No other text and no terminal stream is\r\n   read. But receiving a token and a text in one call does NOT by itself establish that the text is\r\n   what that token's holder submitted, and this contract does not pretend otherwise (doyle,\r\n   2026-09-12):\r\n\r\n   - **The adapter is trusted for, and solely responsible for, the association**: that this payload is\r\n     the text of the submission whose acceptance it captured, and that the token it reports is the one\r\n     read at that capture. Core cannot observe the acceptance and cannot reconstruct it afterwards.\r\n   - **Core can independently verify** only: that the token is authentic and was issued by this node;\r\n     which authenticated origin node, session and incarnation it was issued to; whether its issuing\r\n     seat occupancy has ended, and when; that the submission id has not been used before within the\r\n     session and incarnation; that the payload hashes to the value bound to that submission id on any\r\n     repeat report; and that the paths it acts on are quoted inside the reported payload.\r\n   - Therefore a dishonest or defective adapter can misattribute text within the trust it already\r\n     holds over the session. This contract narrows that to the adapter's own acceptance path; it does\r\n     not remove it, and no wording in it should be read as core-side proof of authorship.\r\n3. **Serving semantics, unchanged from #17 / ADR-0058 Am.1.** A live reference on the submitter's own\r\n   machine, 24h TTL, audience scoped to the receiving agent, and a FILE_ACCESS_HELPER notice emitted\r\n   once per submission and path.\r\n\r\n   **A path that names no file on the submitter's machine is SILENTLY SKIPPED.** Reavo: \"if the\r\n   provided filepath doesn't match a file there, then simply skip fileserving\". The skip is silent —\r\n   **no prompt, no notice, no named refusal on that path** (doyle's reading, B7ZLOLBZ) — and there is\r\n   **no fallback to another origin**: spt does not look for the file on the receiving machine, on a\r\n   previous controller's machine, or anywhere else. A quoted path that does not resolve on the\r\n   submitter's computer simply does not become a reference, and the submission is processed exactly as\r\n   if it had quoted nothing. This is the one non-grant in this contract that is NOT announced, and it\r\n   is deliberate: a user quoting a path that happens not to exist on their machine is an ordinary\r\n   event, not an error to report.\r\n4. **No reliable sender, no automatic share — the adopted safety rule** (doyle, 2026-09-12; a gate\r\n   policy adopted alongside Reavo's decision, NOT part of it). Where the submitting origin is not\r\n   established to the standard §4.1 requires, nothing is served automatically. This is a refusal to act\r\n   on an unestablished origin, and it is distinct from the silent skip above: the skip is a path that\r\n   resolved to nothing on a KNOWN sender's machine; this is not knowing the sender. **What makes a\r\n   sender \"unreliable\" is NOT DEFINED in this contract and is not the adapter's to decide** — it is a\r\n   core-side and gate-side question, deliberately left open rather than invented here.\r\n5. **Handoff.** A token stops being issued the moment its holder stops holding the seat. A new holder's\r\n   token can never redeem a previous holder's submission.\r\n\r\n   **UNRESOLVED — NOT AN AUTHORIZED GUARANTEE (doyle, 2026-09-12).** The proposal is that a submission\r\n   whose token was captured while its holder still held the seat stays authorizable for a BOUNDED\r\n   grace period after the seat changes, on the reasoning that capture and not report is when the\r\n   SUBMISSION happened. That is a proposal only. It no longer depends on the buffered-authorship\r\n   question — that is settled (§3.1) — but it does depend on the still-open ordering question (§5.2):\r\n   if acceptance is at dequeue rather than enqueue, \"captured while its holder held the seat\" is not\r\n   established by capture alone. It may not be relied upon.\r\n   The bound also has no value yet and must be justified rather than picked. Until both are ruled, the\r\n   safe reading is that a token whose issuing occupancy has ended is refused by name.\r\n6. **Replay.** A submission id is single-use **within the session and incarnation, across every token\r\n   issued in it** — not merely per token. Rotating or re-issuing a token must not let the same\r\n   submission register a second time (doyle, 2026-09-12). Re-reporting the same id and payload\r\n   re-registers nothing, emits no second helper notice, and **does not extend an existing TTL**. The\r\n   same id with a different payload, or under a different token, is refused by name rather than treated\r\n   as an update.\r\n7. **Refusal is named — with exactly one silent exception.** Every AUTHORIZATION non-grant says which\r\n   condition failed, on the existing declined line: no token; token not valid for this session or\r\n   incarnation; token past its grace bound; viewer; submission id reused with different payload;\r\n   payload quoted no absolute path; sender not established (§4.4).\r\n\r\n   **The exception is §4.3's missing file, and it is silent by decision, not by omission.** A quoted\r\n   path that resolves to nothing on the submitter's machine produces no declined line, no notice and\r\n   no name. The distinction is: a REFUSAL means the authorization failed; a SKIP means authorization\r\n   was fine and there was simply no file there. Do not collapse the two, and do not add a named refusal\r\n   for the skip on the grounds that every other outcome has one.\r\n8. **Separation of concerns, structurally.** Input processing and serving authorization are\r\n   independent: a refused authorization never alters, delays or fails the submission itself, and a\r\n   submission is delivered and processed identically whether or not a token rode with it.\r\n\r\n## 5. The question for adapter owners\r\n\r\nUsing only this document:\r\n\r\n1. Does your harness have an **acceptance surface** as defined in §2 — a point where a submission is\r\n   committed by the user and is not yet queued?\r\n2. **The ordering question — this replaces the old buffered-authorship question, which is now\r\n   settled.** Do NOT answer about who typed the characters: under the submitter rule the token current\r\n   at acceptance is correct by definition, even if someone else typed the text. Answer instead about\r\n   your QUEUE: does your acceptance point sit at **enqueue** (the submission is captured as it enters\r\n   the queue) or at **dequeue** (captured as the harness takes it off for processing)? If it is\r\n   dequeue, what can change the seat between the two, and can your adapter reach the enqueue point at\r\n   all?\r\n2b. **Machine-injected input.** Can input reach that same acceptance surface WITHOUT a human pressing\r\n   send — hooks, replayed or scripted input, automation writing into the same path, a resumed session\r\n   re-submitting? Can your adapter tell that case apart from a human submission, and what does it\r\n   currently capture for it? A machine injection that captures the seat-holder's token would attribute\r\n   a machine's submission to a person, which is a mechanism question this contract has NOT resolved.\r\n3. At that point, can the adapter **read and store** a value, and keep it associated with that exact\r\n   submission through any queueing the harness performs? Can the harness EDIT or SPLIT the text after\r\n   acceptance, and if so can your adapter OBSERVE that it did (§3.2)? Answer what you can observe, not\r\n   what you would like to guarantee: if there is no post-acceptance observation, say so — an adapter\r\n   that cannot see later edits must not promise to detect them, and reporting the limitation is the\r\n   answer this contract wants.\r\n4. When the submission is reported, can the adapter include the stored value, its own submission id,\r\n   and the **exact committed payload text** in the same report?\r\n5. If any of those is impossible, **what precisely is missing** — a hook that does not exist, a hook\r\n   that fires too late, a buffer the adapter cannot see behind, an association the harness drops, a\r\n   payload the harness rewrites?\r\n\r\nAnswer (5) concretely if it applies. A named limitation is the useful answer and the one that gets a\r\ndecision; \"probably fine\" is not, and neither is a workaround that samples the current controller.\r\n**An answer here is feasibility input, not approval**: doyle holds the approval, and the clauses marked\r\nunresolved below are not guarantees an adapter may build against yet.\r\n\r\n## 6. Known open items, so nobody assumes they are settled\r\n\r\n- **CLOSED, recorded so nobody reopens it:** \"which controller's text is this, if one typed and\r\n  another sent\" is SETTLED by Reavo's decision — the sender's computer, full stop. Earlier revisions of\r\n  this document carried it as the biggest open question; it is not one. Re-raising it is out of scope,\r\n  and an adapter must not build attribution machinery to answer it.\r\n- **Enqueue-versus-dequeue ordering is a MECHANISM question, still open** (doyle, B7ZLOLBZ). Where the\r\n  acceptance point sits relative to the queue decides whether \"the token current at acceptance\" is the\r\n  submitter's token in practice. Adapter answers to §5.2 are the input to that ruling. It does not\r\n  reopen the experience.\r\n- **ORIGIN SIGNALING IS AN UNRESOLVED REQUIREMENT, not an adapter chore** (doyle). Machine-injected\r\n  submissions were MEASURED indistinguishable from human ones at the harness level (§7), and the only\r\n  separator in the field today is an adapter recognising its own transport's payload shapes — a\r\n  heuristic that establishes \"this looks like something spt sent\", never \"a person submitted this\".\r\n  Recognizing payload shapes is not authenticated human provenance. What is missing is a trustworthy\r\n  origin signal; until one exists such submissions report with no token, which is a floor and not a\r\n  solution. Adapter answers to §5.2b describe the gap; they do not close it.\r\n- **A resumed session re-submitting and externally scripted input are SEPARATE, both uncharacterised**,\r\n  and external automation is **not** equivalent to a resumed session — neither answer follows from the\r\n  other.\r\n- **What makes a sender \"unreliable\" (§4.4) is UNDEFINED here** and is deliberately not the adapter's\r\n  to decide. The adopted safety rule says no automatic share in that case; the threshold is core-side\r\n  and gate-side.\r\n- **The post-handoff grace period (§4.5) is a PROPOSAL, not an authorized guarantee.** The submitter\r\n  rule reduces its urgency — the submitter is by definition whoever held the seat at acceptance — but\r\n  it is still unruled, and its bound still has **no value**; it must be justified, not picked.\r\n- **§4.2's split of responsibility is deliberate**: the association is the adapter's to establish and\r\n  core cannot verify it independently. Nothing in this contract is core-side proof of authorship.\r\n- Whether the token is readable through an existing authenticated read verb, or needs a new surface,\r\n  is **not decided** and is deliberately absent from this contract — it is an implementation question\r\n  and adapter owners should not need it to answer §5.\r\n- Acceptance criteria for #300 live on the issue (comment 5645159945) and are proposed, not granted.\r\n- If no shipped adapter can satisfy §3.1, that is a concrete limitation for an operator decision via\r\n  lia, not a licence to reduce the behaviour.\r\n- The **silent skip** (§4.3) is the one non-grant that emits nothing. It is easy to misread as a bug\r\n  report waiting to happen — \"the file didn't get shared and nothing said why\" — and it is deliberate.\r\n  If experience shows users need a signal, that is a new operator decision, not a contract correction.\r\n\r\n## 7. Adapter answers on record\r\n\r\n### claude-spt (perri, 2026-09-12) — answered against this frame, with measurements\r\n\r\n**§5.1 acceptance surface: YES.** `UserPromptSubmit` fires when a submission is committed, before the\r\nmodel sees it, carrying the exact committed text and session id, and can block.\r\n\r\n**Two holes, and they are TWO SEPARATE POPULATIONS** — perri's precision fix, kept apart here because\r\nthey need different contract language and merging them states something false about both:\r\n\r\n- **All submissions: 302 audited, 227 hook-covered.** The 75 misses are harness-synthesised transcript\r\n  entries OR built-in local slash commands (`/compact`, `/reload-plugins` mid-session are the clean\r\n  cases). This is a **surface gap the adapter cannot close**.\r\n…\n\n…\n\n…\n\n…\nlane: todlando. Adapter side: NONE.","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,304,null,381,null,398,null,436]},"fileSize":33503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\docs\\INPUT-PROVENANCE-CONTRACT.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":436,"totalBytes":22966,"outputLines":300,"outputBytes":22966,"shownRange":{"start":1,"end":300},"nextOffset":301}}},"isError":false,"timestamp":1789295272075}}
{"type":"message","id":"bf0ce3e2","parentId":"ac3b3022","timestamp":"2026-09-13T10:27:52.080Z","message":{"role":"toolResult","toolCallId":"call_GHPhGkq7HbckGxxCLUcmLd4S|fc_07ba52f17df332c2016aa67aa8017887d29efd1c8921266f0d","toolName":"read","content":[{"type":"text","text":"pub enum ToBinary {\n…\n}\n\n/// One stdout line, binary → spt-core: a single keystroke-command. **Untagged** —\n/// each line carries exactly one command shape (no discriminator field), matching\n/// the ADR-0022 wire (`{key}` / `{text}` / `{delay_ms}` / `{commit}`). Unknown\n/// extra fields on a recognized shape are ignored (forward-compat); a line\n/// matching no shape is a parse error the reader logs and skips (one bad line\n/// never kills the binary).\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]\n#[serde(untagged)]\npub enum KeyCmd {\n    /// A named key or `ctrl+<x>` chord → mapped to PTY bytes by [`key_to_bytes`].\n    Key { key: String },\n    /// Literal text typed verbatim into the PTY (the message payload).\n    Text { text: String },\n    /// Pause this many milliseconds between keystrokes (the choreography's stash\n    /// settle, e.g. `{\"delay_ms\":50}`). Carried in the sequence, applied by the\n    /// broker's atomic applier.\n    Delay { delay_ms: u64 },\n    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\n    /// emits `{\"commit\":true}` after the last keystroke of its response to an\n    /// event, so the broker knows the atomic sequence is complete and can flush\n    /// the buffered controller input race-free. (The v0.11.0 raw-inject —\n    /// `{text:payload}{key:\"enter\"}` — was removed as a delivery path: it did not\n    /// submit on a modern TUI; idle inbound now spools when no binary delivers.)\n    Commit { commit: bool },\n}\n\n/// Map a `{key}` command name to the raw PTY bytes (the \"send-keys\" API).\n///\n/// Returns `None` for an unknown key name — skipped, never a panic (the opaque\n/// adapter-template stance: a bad key silences itself rather than wedging the\n/// applier). `Delay`/`Commit` are not keys and are handled by the applier, not\n/// here.\n///\n/// Vocabulary: the common control keys, the four arrows + Home/End/PageUp/\n/// PageDown/Delete/Insert as xterm CSI sequences, `ctrl+<letter|char>` chords, and\n/// a single literal character passed through as itself (so `{key:\"y\"}` works).\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\npub fn key_to_bytes(key: &str) -> Option<Vec<u8>> {\n    let k = key.trim();\n    let lower = k.to_ascii_lowercase();\n    let named: &[u8] = match lower.as_str() {\n        \"enter\" | \"return\" | \"cr\" => b\"\\r\",\n        \"newline\" | \"lf\" => b\"\\n\",\n        \"tab\" => b\"\\t\",\n        \"esc\" | \"escape\" => b\"\\x1b\",\n        \"backspace\" | \"bs\" => b\"\\x7f\",\n        \"space\" => b\" \",\n        \"up\" => b\"\\x1b[A\",\n        \"down\" => b\"\\x1b[B\",\n        \"right\" => b\"\\x1b[C\",\n        \"left\" => b\"\\x1b[D\",\n        \"home\" => b\"\\x1b[H\",\n        \"end\" => b\"\\x1b[F\",\n        \"pageup\" | \"pgup\" => b\"\\x1b[5~\",\n        \"pagedown\" | \"pgdn\" => b\"\\x1b[6~\",\n        \"delete\" | \"del\" => b\"\\x1b[3~\",\n        \"insert\" | \"ins\" => b\"\\x1b[2~\",\n        _ => {\n            // ctrl+<x>: a control byte. `ctrl+s` → 0x13, `ctrl+space` → NUL,\n            // `ctrl+[` → ESC, etc. Only single-char chords are mapped.\n            if let Some(rest) = lower.strip_prefix(\"ctrl+\").or_else(|| lower.strip_prefix(\"c-\")) {\n                return ctrl_byte(rest).map(|b| vec![b]);\n            }\n            // A single literal character → its own bytes (e.g. `{key:\"y\"}`).\n            let mut chars = k.chars();\n            if let (Some(c), None) = (chars.next(), chars.next()) {\n                return Some(c.to_string().into_bytes());\n            }\n            return None;\n        }\n    };\n    Some(named.to_vec())\n}\n\n/// The control byte for a `ctrl+<x>` chord. ASCII letters map to `0x01..=0x1a`;\n/// the conventional symbol chords (`@ [ \\ ] ^ _ space`) map to `0x00..=0x1f`.\nfn ctrl_byte(rest: &str) -> Option<u8> {\n    if rest == \"space\" {\n        return Some(0x00);\n    }\n    let mut chars = rest.chars();\n    let (c, None) = (chars.next()?, chars.next()) else {\n        return None;\n    };\n    let b = c as u32;\n    match c {\n        'a'..='z' => Some((b as u8) & 0x1f),\n        'A'..='Z' => Some((b as u8) & 0x1f),\n        '@' => Some(0x00),\n        '[' => Some(0x1b),\n        '\\\\' => Some(0x1c),\n        ']' => Some(0x1d),\n        '^' => Some(0x1e),\n        '_' => Some(0x1f),\n        '?' => Some(0x7f),\n        _ => None,\n    }\n}\n\n/// The per-session controller-input buffer + inject-floor flag (W2 Layer C,\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST / the W1 coordination mechanism).\n///\n/// While the floor is **held** (an injected keystroke sequence is mid-flight,\n/// between a `{type:event}` dispatch and its `{commit}`), the operator's\n/// keystrokes from `dispatch_input` are **buffered here** instead of hitting the\n/// PTY; on commit (or the commit deadline) the buffer is flushed to the PTY\n/// **after** the injected bytes. So a stash/restore choreography\n/// (`ctrl+s … ctrl+s`) can never be clobbered by an operator keystroke landing\n/// mid-sequence. spt-core owning every PTY write is what makes this possible.\n///\n/// Pure state (no I/O) so it is unit-testable; the broker holds it behind a\n/// `Mutex` and does the actual `write_input` off-lock (the W1 lesson: never block\n/// a PTY write under a lock the hot input path needs).\n#[derive(Debug, Default)]\npub struct InjectFloor {\n    held: bool,\n    buffer: Vec<u8>,\n}\n\nimpl InjectFloor {\n    /// Open the floor for a new injected sequence (clears any stale buffer — a\n    /// prior sequence always drains to empty before release, so this is belt-and-\n    /// braces). Subsequent operator input buffers until [`take_or_release`].\n    ///\n    /// [`take_or_release`]: InjectFloor::take_or_release\n    pub fn open(&mut self) {\n        self.held = true;\n        self.buffer.clear();\n    }\n\n    /// Whether the floor is currently held (a sequence is mid-flight).\n    pub fn is_held(&self) -> bool {\n        self.held\n    }\n\n    /// Route one operator-input chunk: if the floor is held, buffer it and return\n    /// `true` (the caller must NOT write to the PTY now — it is flushed after the\n    /// sequence); if free, return `false` (the caller writes through its normal\n    /// path).\n    pub fn buffer_if_held(&mut self, bytes: &[u8]) -> bool {\n        if self.held {\n            self.buffer.extend_from_slice(bytes);\n            true\n        } else {\n            false\n        }\n    }\n\n    /// One flush step — call in a loop after `{commit}` / the deadline. If the\n    /// buffer holds bytes, take them to write **off-lock** while the floor STAYS\n    /// held (so input arriving mid-flush keeps buffering → strict order); when the\n    /// buffer is empty, RELEASE the floor and return `None`. Looping until `None`\n    /// drains everything and leaves the floor free.\n    pub fn take_or_release(&mut self) -> Option<Vec<u8>> {\n        if self.buffer.is_empty() {\n            self.held = false;\n            None\n        } else {\n            Some(std::mem::take(&mut self.buffer))\n        }\n    }\n}\n\n/// A live translation-binary child process: its piped stdin (for [`ToBinary`]\n/// lines) plus a reader thread that parses [`KeyCmd`] lines off its stdout and\n/// forwards them on the caller-supplied channel. The broker owns the receiving\n/// end and the atomic PTY apply.\npub struct TranslationChild {\n    /// `None` once reaped — behind a `Mutex` so [`terminate`](Self::terminate) is\n    /// callable through a shared `Arc` (the broker shares the child between the\n    /// inject worker, which kills it on FAULT, and `dispatch_input`, which fires\n    /// content-free pings — Layer C).\n    child: Mutex<Option<Child>>,\n    /// `None` after [`close_stdin`](Self::close_stdin) / reap has taken it.\n    stdin: Mutex<Option<ChildStdin>>,\n    _reader: JoinHandle<()>,\n    pid: Option<u32>,\n}\n\nimpl TranslationChild {\n    /// Spawn `argv` (program + args) as a stdin→stdout filter, forwarding each\n    /// parsed [`KeyCmd`] on `cmds`. stderr is inherited so an adapter author can\n…\n}\n\n[Showing lines 54-237 of 507. Use :238 to continue]"}],"details":{"truncation":{"content":"}\r\n\r\n/// One stdout line, binary → spt-core: a single keystroke-command. **Untagged** —\r\n/// each line carries exactly one command shape (no discriminator field), matching\r\n/// the ADR-0022 wire (`{key}` / `{text}` / `{delay_ms}` / `{commit}`). Unknown\r\n/// extra fields on a recognized shape are ignored (forward-compat); a line\r\n/// matching no shape is a parse error the reader logs and skips (one bad line\r\n/// never kills the binary).\r\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]\r\n#[serde(untagged)]\r\npub enum KeyCmd {\r\n    /// A named key or `ctrl+<x>` chord → mapped to PTY bytes by [`key_to_bytes`].\r\n    Key { key: String },\r\n    /// Literal text typed verbatim into the PTY (the message payload).\r\n    Text { text: String },\r\n    /// Pause this many milliseconds between keystrokes (the choreography's stash\r\n    /// settle, e.g. `{\"delay_ms\":50}`). Carried in the sequence, applied by the\r\n    /// broker's atomic applier.\r\n    Delay { delay_ms: u64 },\r\n    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\r\n    /// emits `{\"commit\":true}` after the last keystroke of its response to an\r\n    /// event, so the broker knows the atomic sequence is complete and can flush\r\n    /// the buffered controller input race-free. (The v0.11.0 raw-inject —\r\n    /// `{text:payload}{key:\"enter\"}` — was removed as a delivery path: it did not\r\n    /// submit on a modern TUI; idle inbound now spools when no binary delivers.)\r\n    Commit { commit: bool },\r\n}\r\n\r\n/// Map a `{key}` command name to the raw PTY bytes (the \"send-keys\" API).\r\n///\r\n/// Returns `None` for an unknown key name — skipped, never a panic (the opaque\r\n/// adapter-template stance: a bad key silences itself rather than wedging the\r\n/// applier). `Delay`/`Commit` are not keys and are handled by the applier, not\r\n/// here.\r\n///\r\n/// Vocabulary: the common control keys, the four arrows + Home/End/PageUp/\r\n/// PageDown/Delete/Insert as xterm CSI sequences, `ctrl+<letter|char>` chords, and\r\n/// a single literal character passed through as itself (so `{key:\"y\"}` works).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\npub fn key_to_bytes(key: &str) -> Option<Vec<u8>> {\r\n    let k = key.trim();\r\n    let lower = k.to_ascii_lowercase();\r\n    let named: &[u8] = match lower.as_str() {\r\n        \"enter\" | \"return\" | \"cr\" => b\"\\r\",\r\n        \"newline\" | \"lf\" => b\"\\n\",\r\n        \"tab\" => b\"\\t\",\r\n        \"esc\" | \"escape\" => b\"\\x1b\",\r\n        \"backspace\" | \"bs\" => b\"\\x7f\",\r\n        \"space\" => b\" \",\r\n        \"up\" => b\"\\x1b[A\",\r\n        \"down\" => b\"\\x1b[B\",\r\n        \"right\" => b\"\\x1b[C\",\r\n        \"left\" => b\"\\x1b[D\",\r\n        \"home\" => b\"\\x1b[H\",\r\n        \"end\" => b\"\\x1b[F\",\r\n        \"pageup\" | \"pgup\" => b\"\\x1b[5~\",\r\n        \"pagedown\" | \"pgdn\" => b\"\\x1b[6~\",\r\n        \"delete\" | \"del\" => b\"\\x1b[3~\",\r\n        \"insert\" | \"ins\" => b\"\\x1b[2~\",\r\n        _ => {\r\n            // ctrl+<x>: a control byte. `ctrl+s` → 0x13, `ctrl+space` → NUL,\r\n            // `ctrl+[` → ESC, etc. Only single-char chords are mapped.\r\n            if let Some(rest) = lower.strip_prefix(\"ctrl+\").or_else(|| lower.strip_prefix(\"c-\")) {\r\n                return ctrl_byte(rest).map(|b| vec![b]);\r\n            }\r\n            // A single literal character → its own bytes (e.g. `{key:\"y\"}`).\r\n            let mut chars = k.chars();\r\n            if let (Some(c), None) = (chars.next(), chars.next()) {\r\n                return Some(c.to_string().into_bytes());\r\n            }\r\n            return None;\r\n        }\r\n    };\r\n    Some(named.to_vec())\r\n}\r\n\r\n/// The control byte for a `ctrl+<x>` chord. ASCII letters map to `0x01..=0x1a`;\r\n/// the conventional symbol chords (`@ [ \\ ] ^ _ space`) map to `0x00..=0x1f`.\r\nfn ctrl_byte(rest: &str) -> Option<u8> {\r\n    if rest == \"space\" {\r\n        return Some(0x00);\r\n    }\r\n    let mut chars = rest.chars();\r\n    let (c, None) = (chars.next()?, chars.next()) else {\r\n        return None;\r\n    };\r\n    let b = c as u32;\r\n    match c {\r\n        'a'..='z' => Some((b as u8) & 0x1f),\r\n        'A'..='Z' => Some((b as u8) & 0x1f),\r\n        '@' => Some(0x00),\r\n        '[' => Some(0x1b),\r\n        '\\\\' => Some(0x1c),\r\n        ']' => Some(0x1d),\r\n        '^' => Some(0x1e),\r\n        '_' => Some(0x1f),\r\n        '?' => Some(0x7f),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// The per-session controller-input buffer + inject-floor flag (W2 Layer C,\r\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST / the W1 coordination mechanism).\r\n///\r\n/// While the floor is **held** (an injected keystroke sequence is mid-flight,\r\n/// between a `{type:event}` dispatch and its `{commit}`), the operator's\r\n/// keystrokes from `dispatch_input` are **buffered here** instead of hitting the\r\n/// PTY; on commit (or the commit deadline) the buffer is flushed to the PTY\r\n/// **after** the injected bytes. So a stash/restore choreography\r\n/// (`ctrl+s … ctrl+s`) can never be clobbered by an operator keystroke landing\r\n/// mid-sequence. spt-core owning every PTY write is what makes this possible.\r\n///\r\n/// Pure state (no I/O) so it is unit-testable; the broker holds it behind a\r\n/// `Mutex` and does the actual `write_input` off-lock (the W1 lesson: never block\r\n/// a PTY write under a lock the hot input path needs).\r\n#[derive(Debug, Default)]\r\npub struct InjectFloor {\r\n    held: bool,\r\n    buffer: Vec<u8>,\r\n}\r\n\r\nimpl InjectFloor {\r\n    /// Open the floor for a new injected sequence (clears any stale buffer — a\r\n    /// prior sequence always drains to empty before release, so this is belt-and-\r\n    /// braces). Subsequent operator input buffers until [`take_or_release`].\r\n    ///\r\n    /// [`take_or_release`]: InjectFloor::take_or_release\r\n    pub fn open(&mut self) {\r\n        self.held = true;\r\n        self.buffer.clear();\r\n    }\r\n\r\n    /// Whether the floor is currently held (a sequence is mid-flight).\r\n    pub fn is_held(&self) -> bool {\r\n        self.held\r\n    }\r\n\r\n    /// Route one operator-input chunk: if the floor is held, buffer it and return\r\n    /// `true` (the caller must NOT write to the PTY now — it is flushed after the\r\n    /// sequence); if free, return `false` (the caller writes through its normal\r\n    /// path).\r\n    pub fn buffer_if_held(&mut self, bytes: &[u8]) -> bool {\r\n        if self.held {\r\n            self.buffer.extend_from_slice(bytes);\r\n            true\r\n        } else {\r\n            false\r\n        }\r\n    }\r\n\r\n    /// One flush step — call in a loop after `{commit}` / the deadline. If the\r\n    /// buffer holds bytes, take them to write **off-lock** while the floor STAYS\r\n    /// held (so input arriving mid-flush keeps buffering → strict order); when the\r\n    /// buffer is empty, RELEASE the floor and return `None`. Looping until `None`\r\n    /// drains everything and leaves the floor free.\r\n    pub fn take_or_release(&mut self) -> Option<Vec<u8>> {\r\n        if self.buffer.is_empty() {\r\n            self.held = false;\r\n            None\r\n        } else {\r\n            Some(std::mem::take(&mut self.buffer))\r\n        }\r\n    }\r\n}\r\n\r\n/// A live translation-binary child process: its piped stdin (for [`ToBinary`]\r\n/// lines) plus a reader thread that parses [`KeyCmd`] lines off its stdout and\r\n/// forwards them on the caller-supplied channel. The broker owns the receiving\r\n/// end and the atomic PTY apply.\r\npub struct TranslationChild {\r\n    /// `None` once reaped — behind a `Mutex` so [`terminate`](Self::terminate) is\r\n    /// callable through a shared `Arc` (the broker shares the child between the\r\n    /// inject worker, which kills it on FAULT, and `dispatch_input`, which fires\r\n    /// content-free pings — Layer C).\r\n    child: Mutex<Option<Child>>,\r\n    /// `None` after [`close_stdin`](Self::close_stdin) / reap has taken it.\r\n    stdin: Mutex<Option<ChildStdin>>,\r\n    _reader: JoinHandle<()>,\r\n    pid: Option<u32>,\r\n}\r\n\r\nimpl TranslationChild {\r\n    /// Spawn `argv` (program + args) as a stdin→stdout filter, forwarding each\r\n    /// parsed [`KeyCmd`] on `cmds`. stderr is inherited so an adapter author can\r","truncated":true,"truncatedBy":"lines","totalLines":454,"totalBytes":7987,"outputLines":184,"outputBytes":7987,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":507,"displayContent":{"text":"pub enum ToBinary {\n…\n}\r\n\r\n/// One stdout line, binary → spt-core: a single keystroke-command. **Untagged** —\r\n/// each line carries exactly one command shape (no discriminator field), matching\r\n/// the ADR-0022 wire (`{key}` / `{text}` / `{delay_ms}` / `{commit}`). Unknown\r\n/// extra fields on a recognized shape are ignored (forward-compat); a line\r\n/// matching no shape is a parse error the reader logs and skips (one bad line\r\n/// never kills the binary).\r\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]\r\n#[serde(untagged)]\r\npub enum KeyCmd {\r\n    /// A named key or `ctrl+<x>` chord → mapped to PTY bytes by [`key_to_bytes`].\r\n    Key { key: String },\r\n    /// Literal text typed verbatim into the PTY (the message payload).\r\n    Text { text: String },\r\n    /// Pause this many milliseconds between keystrokes (the choreography's stash\r\n    /// settle, e.g. `{\"delay_ms\":50}`). Carried in the sequence, applied by the\r\n    /// broker's atomic applier.\r\n    Delay { delay_ms: u64 },\r\n    /// Sequence terminator (doyle OPT-1 ruling, pending confirm): the binary\r\n    /// emits `{\"commit\":true}` after the last keystroke of its response to an\r\n    /// event, so the broker knows the atomic sequence is complete and can flush\r\n    /// the buffered controller input race-free. (The v0.11.0 raw-inject —\r\n    /// `{text:payload}{key:\"enter\"}` — was removed as a delivery path: it did not\r\n    /// submit on a modern TUI; idle inbound now spools when no binary delivers.)\r\n    Commit { commit: bool },\r\n}\r\n\r\n/// Map a `{key}` command name to the raw PTY bytes (the \"send-keys\" API).\r\n///\r\n/// Returns `None` for an unknown key name — skipped, never a panic (the opaque\r\n/// adapter-template stance: a bad key silences itself rather than wedging the\r\n/// applier). `Delay`/`Commit` are not keys and are handled by the applier, not\r\n/// here.\r\n///\r\n/// Vocabulary: the common control keys, the four arrows + Home/End/PageUp/\r\n/// PageDown/Delete/Insert as xterm CSI sequences, `ctrl+<letter|char>` chords, and\r\n/// a single literal character passed through as itself (so `{key:\"y\"}` works).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\npub fn key_to_bytes(key: &str) -> Option<Vec<u8>> {\r\n    let k = key.trim();\r\n    let lower = k.to_ascii_lowercase();\r\n    let named: &[u8] = match lower.as_str() {\r\n        \"enter\" | \"return\" | \"cr\" => b\"\\r\",\r\n        \"newline\" | \"lf\" => b\"\\n\",\r\n        \"tab\" => b\"\\t\",\r\n        \"esc\" | \"escape\" => b\"\\x1b\",\r\n        \"backspace\" | \"bs\" => b\"\\x7f\",\r\n        \"space\" => b\" \",\r\n        \"up\" => b\"\\x1b[A\",\r\n        \"down\" => b\"\\x1b[B\",\r\n        \"right\" => b\"\\x1b[C\",\r\n        \"left\" => b\"\\x1b[D\",\r\n        \"home\" => b\"\\x1b[H\",\r\n        \"end\" => b\"\\x1b[F\",\r\n        \"pageup\" | \"pgup\" => b\"\\x1b[5~\",\r\n        \"pagedown\" | \"pgdn\" => b\"\\x1b[6~\",\r\n        \"delete\" | \"del\" => b\"\\x1b[3~\",\r\n        \"insert\" | \"ins\" => b\"\\x1b[2~\",\r\n        _ => {\r\n            // ctrl+<x>: a control byte. `ctrl+s` → 0x13, `ctrl+space` → NUL,\r\n            // `ctrl+[` → ESC, etc. Only single-char chords are mapped.\r\n            if let Some(rest) = lower.strip_prefix(\"ctrl+\").or_else(|| lower.strip_prefix(\"c-\")) {\r\n                return ctrl_byte(rest).map(|b| vec![b]);\r\n            }\r\n            // A single literal character → its own bytes (e.g. `{key:\"y\"}`).\r\n            let mut chars = k.chars();\r\n            if let (Some(c), None) = (chars.next(), chars.next()) {\r\n                return Some(c.to_string().into_bytes());\r\n            }\r\n            return None;\r\n        }\r\n    };\r\n    Some(named.to_vec())\r\n}\r\n\r\n/// The control byte for a `ctrl+<x>` chord. ASCII letters map to `0x01..=0x1a`;\r\n/// the conventional symbol chords (`@ [ \\ ] ^ _ space`) map to `0x00..=0x1f`.\r\nfn ctrl_byte(rest: &str) -> Option<u8> {\r\n    if rest == \"space\" {\r\n        return Some(0x00);\r\n    }\r\n    let mut chars = rest.chars();\r\n    let (c, None) = (chars.next()?, chars.next()) else {\r\n        return None;\r\n    };\r\n    let b = c as u32;\r\n    match c {\r\n        'a'..='z' => Some((b as u8) & 0x1f),\r\n        'A'..='Z' => Some((b as u8) & 0x1f),\r\n        '@' => Some(0x00),\r\n        '[' => Some(0x1b),\r\n        '\\\\' => Some(0x1c),\r\n        ']' => Some(0x1d),\r\n        '^' => Some(0x1e),\r\n        '_' => Some(0x1f),\r\n        '?' => Some(0x7f),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// The per-session controller-input buffer + inject-floor flag (W2 Layer C,\r\n/// REQ-HAZARD-INJECT-CONTROL-COEXIST / the W1 coordination mechanism).\r\n///\r\n/// While the floor is **held** (an injected keystroke sequence is mid-flight,\r\n/// between a `{type:event}` dispatch and its `{commit}`), the operator's\r\n/// keystrokes from `dispatch_input` are **buffered here** instead of hitting the\r\n/// PTY; on commit (or the commit deadline) the buffer is flushed to the PTY\r\n/// **after** the injected bytes. So a stash/restore choreography\r\n/// (`ctrl+s … ctrl+s`) can never be clobbered by an operator keystroke landing\r\n/// mid-sequence. spt-core owning every PTY write is what makes this possible.\r\n///\r\n/// Pure state (no I/O) so it is unit-testable; the broker holds it behind a\r\n/// `Mutex` and does the actual `write_input` off-lock (the W1 lesson: never block\r\n/// a PTY write under a lock the hot input path needs).\r\n#[derive(Debug, Default)]\r\npub struct InjectFloor {\r\n    held: bool,\r\n    buffer: Vec<u8>,\r\n}\r\n\r\nimpl InjectFloor {\r\n    /// Open the floor for a new injected sequence (clears any stale buffer — a\r\n    /// prior sequence always drains to empty before release, so this is belt-and-\r\n    /// braces). Subsequent operator input buffers until [`take_or_release`].\r\n    ///\r\n    /// [`take_or_release`]: InjectFloor::take_or_release\r\n    pub fn open(&mut self) {\r\n        self.held = true;\r\n        self.buffer.clear();\r\n    }\r\n\r\n    /// Whether the floor is currently held (a sequence is mid-flight).\r\n    pub fn is_held(&self) -> bool {\r\n        self.held\r\n    }\r\n\r\n    /// Route one operator-input chunk: if the floor is held, buffer it and return\r\n    /// `true` (the caller must NOT write to the PTY now — it is flushed after the\r\n    /// sequence); if free, return `false` (the caller writes through its normal\r\n    /// path).\r\n    pub fn buffer_if_held(&mut self, bytes: &[u8]) -> bool {\r\n        if self.held {\r\n            self.buffer.extend_from_slice(bytes);\r\n            true\r\n        } else {\r\n            false\r\n        }\r\n    }\r\n\r\n    /// One flush step — call in a loop after `{commit}` / the deadline. If the\r\n    /// buffer holds bytes, take them to write **off-lock** while the floor STAYS\r\n    /// held (so input arriving mid-flush keeps buffering → strict order); when the\r\n    /// buffer is empty, RELEASE the floor and return `None`. Looping until `None`\r\n    /// drains everything and leaves the floor free.\r\n    pub fn take_or_release(&mut self) -> Option<Vec<u8>> {\r\n        if self.buffer.is_empty() {\r\n            self.held = false;\r\n            None\r\n        } else {\r\n            Some(std::mem::take(&mut self.buffer))\r\n        }\r\n    }\r\n}\r\n\r\n/// A live translation-binary child process: its piped stdin (for [`ToBinary`]\r\n/// lines) plus a reader thread that parses [`KeyCmd`] lines off its stdout and\r\n/// forwards them on the caller-supplied channel. The broker owns the receiving\r\n/// end and the atomic PTY apply.\r\npub struct TranslationChild {\r\n    /// `None` once reaped — behind a `Mutex` so [`terminate`](Self::terminate) is\r\n    /// callable through a shared `Arc` (the broker shares the child between the\r\n    /// inject worker, which kills it on FAULT, and `dispatch_input`, which fires\r\n    /// content-free pings — Layer C).\r\n    child: Mutex<Option<Child>>,\r\n    /// `None` after [`close_stdin`](Self::close_stdin) / reap has taken it.\r\n    stdin: Mutex<Option<ChildStdin>>,\r\n    _reader: JoinHandle<()>,\r\n    pid: Option<u32>,\r\n}\r\n\r\nimpl TranslationChild {\r\n    /// Spawn `argv` (program + args) as a stdin→stdout filter, forwarding each\r\n    /// parsed [`KeyCmd`] on `cmds`. stderr is inherited so an adapter author can\r\n…\n}","startLine":44,"lineNumbers":[44,null,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,null,359]},"fileSize":22675,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\translation.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":507,"totalBytes":7987,"outputLines":184,"outputBytes":7987,"shownRange":{"start":54,"end":237},"nextOffset":238}}},"isError":false,"timestamp":1789295272080}}
{"type":"message","id":"ec39a435","parentId":"bf0ce3e2","timestamp":"2026-09-13T10:27:52.089Z","message":{"role":"toolResult","toolCallId":"call_Oo8aLMqW4nka2GLVR74Lx7B8|fc_07ba52f17df332c2016aa67aa8016487d2b5c1058b004511cb","toolName":"grep","content":[{"type":"text","text":" 300|/// close. Pure over the map so the no-op is a unit.\n*301|fn release_claim(map: &mut HashMap<String, WakeClaim>, endpoint: &str, generation: u64) {\n 302|    if map.get(endpoint).is_some_and(|c| c.generation == generation) {\n 303|        map.remove(endpoint);\n 304|    }\n...\n 628|/// the deadline's PRESENCE is the invariant, not its exact length.\n*629|fn inject_commit_deadline() -> Duration {\n 630|    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n 631|        Ok(ms) => ms\n 632|            .parse::<u64>()\n...\n 656|/// invariant, not its exact length.\n*657|fn inject_miss_strike_budget() -> u32 {\n 658|    match std::env::var(\"SPT_INJECT_MISS_STRIKE_BUDGET\") {\n 659|        Ok(n) => n.parse::<u32>().ok().filter(|b| *b >= 1).unwrap_or(INJECT_MISS_STRIKE_BUDGET),\n 660|        Err(_) => INJECT_MISS_STRIKE_BUDGET,\n...\n 713|/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n*714|fn inject_settle_deadline() -> Duration {\n 715|    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n 716|        Ok(ms) => ms\n 717|            .parse::<u64>()\n...\n 729|/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n*730|fn inject_echo_verify_deadline() -> Duration {\n 731|    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n 732|        Ok(ms) => ms\n 733|            .parse::<u64>()\n...\n 756|\n*757|fn inject_text_chunk() -> usize {\n 758|    match std::env::var(\"SPT_INJECT_TEXT_CHUNK\") {\n 759|        Ok(n) => n.parse::<usize>().ok().filter(|c| *c > 0).unwrap_or(INJECT_TEXT_CHUNK),\n 760|        Err(_) => INJECT_TEXT_CHUNK,\n...\n 3298|    // [impl->REQ-RC-RESIZE-PRESENTATION-BARRIER]\n*3299|    fn commit_resize(&mut self, rows: u16, cols: u16) {\n 3300|        let torn_down = match self.transition.take() {\n 3301|            Some(t) => {\n 3302|                let suppressed = t.suppressed;\n...\n 4117|    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\n*4118|    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\n 4119|    /// so a paste burst that fills the harness's input buffer parks only the\n 4120|    /// writer thread, never the broker dispatch thread.\n 4121|    input: Arc<InputWriter>,\n...\n 4192|/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\n*4193|/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\n 4194|/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\n 4195|/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\n 4196|/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\n...\n 4269|/// records from `rx` and apply each via the BLOCKING\n*4270|/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\n 4271|/// wedged harness parks this thread (and only this thread); a write error is\n 4272|/// swallowed (the same best-effort the inline callers had). The thread exits\n 4273|/// when the last `tx` clone drops at session teardown.\n...\n 4275|    while let Ok(bytes) = rx.recv() {\n*4276|        let _ = session.write_input(&bytes);\n 4277|    }\n 4278|}\n 4279|\n...\n 4587|\n*4588|fn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\n 4589|    loop {\n 4590|        let chunk = lock_floor(floor).take_or_release();\n 4591|        match chunk {\n...\n 4605|/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\n*4606|/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n 4607|/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n 4608|/// buffered controller bytes AFTER the injected sequence and release the floor. On\n 4609|/// deadline-miss or binary death: flush+release anyway (operator input is never\n...\n 4726|// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\n*4727|fn run_inject_worker_supervised(\n 4728|    child: Arc<TranslationChild>,\n 4729|    faulted: Arc<AtomicBool>,\n 4730|    endpoint: String,\n...\n 4756|// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n*4757|fn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\n 4758|    let baseline = recover_log(log).high_water();\n 4759|    let deadline = Instant::now() + inject_settle_deadline();\n 4760|    loop {\n...\n 4906|        match cmd_rx.recv_timeout(remaining) {\n*4907|            Ok(KeyCmd::Key { key }) => {\n 4908|                if let Some(bytes) = key_to_bytes(&key) {\n 4909|                    input.enqueue(bytes);\n 4910|                }\n 4911|            }\n*4912|            Ok(KeyCmd::Text { text }) => {\n 4913|                // Accumulate the payload for echo-verify, then type it paced-chunked.\n 4914|                let bytes = text.into_bytes();\n 4915|                sent_text.extend_from_slice(&bytes);\n...\n 4917|            }\n*4918|            Ok(KeyCmd::Delay { delay_ms }) => {\n 4919|                let want = Duration::from_millis(delay_ms);\n 4920|                let cap = deadline.saturating_duration_since(Instant::now());\n 4921|                thread::sleep(want.min(cap));\n 4922|            }\n*4923|            Ok(KeyCmd::Commit { .. }) => {\n 4924|                committed = true;\n 4925|                break;\n 4926|            }\n...\n 4951|#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\n*4952|fn run_inject_worker(\n 4953|    input: Arc<InputWriter>,\n 4954|    child: Arc<TranslationChild>,\n 4955|    floor: Arc<Mutex<InjectFloor>>,\n...\n 8314|        // The session's lone PTY input-writer (P0): the SOLE caller of the\n*8315|        // blocking `write_input`. Every dispatch-side write enqueues here, so a\n 8316|        // paste burst parks only this thread — never the broker dispatch thread\n 8317|        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\n 8318|        // too, so the lone writer enforces the W2 inject-floor order.\n...\n 8745|    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n*8746|    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 8747|        let req: InputReq =\n 8748|            serde_json::from_value(env.payload).map_err(|e| format!(\"bad input payload: {e}\"))?;\n 8749|        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!(\"bad input base64: {e}\"))?;\n...\n 11032|    /// recording the bytes it receives — EXACTLY mirroring the production\n*11033|    /// `input_writer` loop (`while let Ok(b) = rx.recv() { surface.write_input(&b) }`).\n 11034|    /// One FIFO + one writer ⇒ exact input order. Non-vacuous: a reorder/LIFO/dedup\n 11035|    /// mutation of the single-writer contract makes the recorded sequence diverge.\n 11036|    // [unit->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n...\n 11040|\n*11041|        /// A stub PTY write seam: appends every `write_input` to a shared Vec, in\n 11042|        /// the order the sole writer applied them. (`resize` is never exercised.)\n 11043|        struct RecordingSurface(Arc<Mutex<Vec<Vec<u8>>>>);\n 11044|        impl SessionSurface for RecordingSurface {\n*11045|            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046|                self.0.lock().unwrap().push(bytes.to_vec());\n 11047|                Ok(())\n 11048|            }\n...\n 11077|        while let Ok(bytes) = rx.recv() {\n*11078|            surface.write_input(&bytes).unwrap();\n 11079|        }\n 11080|\n 11081|        assert_eq!(\n...\n 11157|    #[test]\n*11158|    fn lock_floor_recovers_a_poisoned_inject_floor() {\n 11159|        let floor = Arc::new(Mutex::new(InjectFloor::default()));\n 11160|        // Poison it exactly as a panic-while-holding-the-floor-lock does.\n 11161|        let f = Arc::clone(&floor);\n...\n 13014|    #[test]\n*13015|    fn a_stale_guards_drop_does_not_release_its_successors_claim() {\n 13016|        let mut map: HashMap<String, WakeClaim> = HashMap::new();\n 13017|        let successor = WakeClaim {\n 13018|            generation: 2,\n...\n 13047|    #[test]\n*13048|    fn stalled_incumbent_is_evicted_on_reattach_and_control_released() {\n 13049|        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 13050|        seed_controller_with_block(&mut log, Some(\"incumbent\"), Some(wedged_since()));\n 13051|        let before = STALL_EVICT_COUNT.load(Ordering::Relaxed);"}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":29,"fileCount":1,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":29}],"truncated":false,"displayContent":"   300│/// close. Pure over the map so the no-op is a unit.\n  *301│fn release_claim(map: &mut HashMap<String, WakeClaim>, endpoint: &str, generation: u64) {\n   302│    if map.get(endpoint).is_some_and(|c| c.generation == generation) {\n   303│        map.remove(endpoint);\n   304│    }\n      │...\n   628│/// the deadline's PRESENCE is the invariant, not its exact length.\n  *629│fn inject_commit_deadline() -> Duration {\n   630│    match std::env::var(\"SPT_INJECT_COMMIT_DEADLINE_MS\") {\n   631│        Ok(ms) => ms\n   632│            .parse::<u64>()\n      │...\n   656│/// invariant, not its exact length.\n  *657│fn inject_miss_strike_budget() -> u32 {\n   658│    match std::env::var(\"SPT_INJECT_MISS_STRIKE_BUDGET\") {\n   659│        Ok(n) => n.parse::<u32>().ok().filter(|b| *b >= 1).unwrap_or(INJECT_MISS_STRIKE_BUDGET),\n   660│        Err(_) => INJECT_MISS_STRIKE_BUDGET,\n      │...\n   713│/// `SPT_INJECT_SETTLE_MS` (its PRESENCE is the invariant, not the exact length).\n  *714│fn inject_settle_deadline() -> Duration {\n   715│    match std::env::var(\"SPT_INJECT_SETTLE_MS\") {\n   716│        Ok(ms) => ms\n   717│            .parse::<u64>()\n      │...\n   729│/// The effective echo-verify wait; the int gate tunes it via `SPT_INJECT_ECHO_MS`.\n  *730│fn inject_echo_verify_deadline() -> Duration {\n   731│    match std::env::var(\"SPT_INJECT_ECHO_MS\") {\n   732│        Ok(ms) => ms\n   733│            .parse::<u64>()\n      │...\n   756│\n  *757│fn inject_text_chunk() -> usize {\n   758│    match std::env::var(\"SPT_INJECT_TEXT_CHUNK\") {\n   759│        Ok(n) => n.parse::<usize>().ok().filter(|c| *c > 0).unwrap_or(INJECT_TEXT_CHUNK),\n   760│        Err(_) => INJECT_TEXT_CHUNK,\n      │...\n  3298│    // [impl->REQ-RC-RESIZE-PRESENTATION-BARRIER]\n *3299│    fn commit_resize(&mut self, rows: u16, cols: u16) {\n  3300│        let torn_down = match self.transition.take() {\n  3301│            Some(t) => {\n  3302│                let suppressed = t.suppressed;\n      │...\n  4117│    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\n *4118│    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\n  4119│    /// so a paste burst that fills the harness's input buffer parks only the\n  4120│    /// writer thread, never the broker dispatch thread.\n  4121│    input: Arc<InputWriter>,\n      │...\n  4192│/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\n *4193│/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\n  4194│/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\n  4195│/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\n  4196│/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\n      │...\n  4269│/// records from `rx` and apply each via the BLOCKING\n *4270│/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\n  4271│/// wedged harness parks this thread (and only this thread); a write error is\n  4272│/// swallowed (the same best-effort the inline callers had). The thread exits\n  4273│/// when the last `tx` clone drops at session teardown.\n      │...\n  4275│    while let Ok(bytes) = rx.recv() {\n *4276│        let _ = session.write_input(&bytes);\n  4277│    }\n  4278│}\n  4279│\n      │...\n  4587│\n *4588│fn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\n  4589│    loop {\n  4590│        let chunk = lock_floor(floor).take_or_release();\n  4591│        match chunk {\n      │...\n  4605│/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\n *4606│/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n  4607│/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n  4608│/// buffered controller bytes AFTER the injected sequence and release the floor. On\n  4609│/// deadline-miss or binary death: flush+release anyway (operator input is never\n      │...\n  4726│// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\n *4727│fn run_inject_worker_supervised(\n  4728│    child: Arc<TranslationChild>,\n  4729│    faulted: Arc<AtomicBool>,\n  4730│    endpoint: String,\n      │...\n  4756│// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n *4757│fn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\n  4758│    let baseline = recover_log(log).high_water();\n  4759│    let deadline = Instant::now() + inject_settle_deadline();\n  4760│    loop {\n      │...\n  4906│        match cmd_rx.recv_timeout(remaining) {\n *4907│            Ok(KeyCmd::Key { key }) => {\n  4908│                if let Some(bytes) = key_to_bytes(&key) {\n  4909│                    input.enqueue(bytes);\n  4910│                }\n  4911│            }\n *4912│            Ok(KeyCmd::Text { text }) => {\n  4913│                // Accumulate the payload for echo-verify, then type it paced-chunked.\n  4914│                let bytes = text.into_bytes();\n  4915│                sent_text.extend_from_slice(&bytes);\n      │...\n  4917│            }\n *4918│            Ok(KeyCmd::Delay { delay_ms }) => {\n  4919│                let want = Duration::from_millis(delay_ms);\n  4920│                let cap = deadline.saturating_duration_since(Instant::now());\n  4921│                thread::sleep(want.min(cap));\n  4922│            }\n *4923│            Ok(KeyCmd::Commit { .. }) => {\n  4924│                committed = true;\n  4925│                break;\n  4926│            }\n      │...\n  4951│#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\n *4952│fn run_inject_worker(\n  4953│    input: Arc<InputWriter>,\n  4954│    child: Arc<TranslationChild>,\n  4955│    floor: Arc<Mutex<InjectFloor>>,\n      │...\n  8314│        // The session's lone PTY input-writer (P0): the SOLE caller of the\n *8315│        // blocking `write_input`. Every dispatch-side write enqueues here, so a\n  8316│        // paste burst parks only this thread — never the broker dispatch thread\n  8317│        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\n  8318│        // too, so the lone writer enforces the W2 inject-floor order.\n      │...\n  8745│    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n *8746│    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n  8747│        let req: InputReq =\n  8748│            serde_json::from_value(env.payload).map_err(|e| format!(\"bad input payload: {e}\"))?;\n  8749│        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!(\"bad input base64: {e}\"))?;\n      │...\n 11032│    /// recording the bytes it receives — EXACTLY mirroring the production\n*11033│    /// `input_writer` loop (`while let Ok(b) = rx.recv() { surface.write_input(&b) }`).\n 11034│    /// One FIFO + one writer ⇒ exact input order. Non-vacuous: a reorder/LIFO/dedup\n 11035│    /// mutation of the single-writer contract makes the recorded sequence diverge.\n 11036│    // [unit->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n      │...\n 11040│\n*11041│        /// A stub PTY write seam: appends every `write_input` to a shared Vec, in\n 11042│        /// the order the sole writer applied them. (`resize` is never exercised.)\n 11043│        struct RecordingSurface(Arc<Mutex<Vec<Vec<u8>>>>);\n 11044│        impl SessionSurface for RecordingSurface {\n*11045│            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046│                self.0.lock().unwrap().push(bytes.to_vec());\n 11047│                Ok(())\n 11048│            }\n      │...\n 11077│        while let Ok(bytes) = rx.recv() {\n*11078│            surface.write_input(&bytes).unwrap();\n 11079│        }\n 11080│\n 11081│        assert_eq!(\n      │...\n 11157│    #[test]\n*11158│    fn lock_floor_recovers_a_poisoned_inject_floor() {\n 11159│        let floor = Arc::new(Mutex::new(InjectFloor::default()));\n 11160│        // Poison it exactly as a panic-while-holding-the-floor-lock does.\n 11161│        let f = Arc::clone(&floor);\n      │...\n 13014│    #[test]\n*13015│    fn a_stale_guards_drop_does_not_release_its_successors_claim() {\n 13016│        let mut map: HashMap<String, WakeClaim> = HashMap::new();\n 13017│        let successor = WakeClaim {\n 13018│            generation: 2,\n      │...\n 13047│    #[test]\n*13048│    fn stalled_incumbent_is_evicted_on_reattach_and_control_released() {\n 13049│        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));\n 13050│        seed_controller_with_block(&mut log, Some(\"incumbent\"), Some(wedged_since()));\n 13051│        let before = STALL_EVICT_COUNT.load(Ordering::Relaxed);"},"isError":false,"timestamp":1789295272088}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Rs2K7L70FMmZ0tbLwDchlD6e|fc_07ba52f17df332c2016aa67ab0c5ac87d2af479346595af0bf","toolName":"read","startedAt":"2026-09-13T10:28:00.909Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4080-4290"},"intent":"Reading queued writer implementation"},"id":"0f3b31da","parentId":"ec39a435","timestamp":"2026-09-13T10:28:00.909Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_FldpK3S15CTJ8YNWAOrAozsf|fc_07ba52f17df332c2016aa67ab0c5bc87d2961024ad9cade57c","toolName":"read","startedAt":"2026-09-13T10:28:00.909Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4570-5200"},"intent":"Reading injection apply implementation"},"id":"c24ae523","parentId":"0f3b31da","timestamp":"2026-09-13T10:28:00.909Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_cCZc90T3xE6B2zlTFSGgEApE|fc_07ba52f17df332c2016aa67ab0c5c487d2a9b84d5d05e7d192","toolName":"read","startedAt":"2026-09-13T10:28:00.909Z","args":{"path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md:367-436"},"intent":"Reading section eight contract"},"id":"8e4f1053","parentId":"c24ae523","timestamp":"2026-09-13T10:28:00.909Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UAUprIcXUPLc8oLAbvLgER1T|fc_07ba52f17df332c2016aa67ab0c5cc87d2a7e7f89e048289fd","toolName":"grep","startedAt":"2026-09-13T10:28:00.909Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs"},"intent":"Locating queue and normalization uses"},"id":"ba9005e0","parentId":"8e4f1053","timestamp":"2026-09-13T10:28:00.909Z"}
{"type":"message","id":"99bb2049","parentId":"ba9005e0","timestamp":"2026-09-13T10:28:00.910Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_Rs2K7L70FMmZ0tbLwDchlD6e|fc_07ba52f17df332c2016aa67ab0c5ac87d2af479346595af0bf","name":"read","arguments":{"i":"Reading queued writer implementation","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4080-4290"},"intent":"Reading queued writer implementation"},{"type":"toolCall","id":"call_FldpK3S15CTJ8YNWAOrAozsf|fc_07ba52f17df332c2016aa67ab0c5bc87d2961024ad9cade57c","name":"read","arguments":{"i":"Reading injection apply implementation","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4570-5200"},"intent":"Reading injection apply implementation"},{"type":"toolCall","id":"call_cCZc90T3xE6B2zlTFSGgEApE|fc_07ba52f17df332c2016aa67ab0c5c487d2a9b84d5d05e7d192","name":"read","arguments":{"i":"Reading section eight contract","path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md:367-436"},"intent":"Reading section eight contract"},{"type":"toolCall","id":"call_UAUprIcXUPLc8oLAbvLgER1T|fc_07ba52f17df332c2016aa67ab0c5cc87d2a7e7f89e048289fd","name":"grep","arguments":{"i":"Locating queue and normalization uses","pattern":"\\.enqueue\\(|normalize|replace\\(.*\\\\r|200~|201~|sent_text|post.commit|typeahead","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","case":false,"gitignore":false,"skip":null},"intent":"Locating queue and normalization uses"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":10181,"output":234,"cacheRead":12032,"cacheWrite":0,"totalTokens":22447,"cost":{"input":0.10181000000000001,"output":0.0117,"cacheRead":0.012032,"cacheWrite":0,"total":0.12554200000000001}},"stopReason":"toolUse","timestamp":1789295272093,"responseId":"resp_07ba52f17df332c2016aa67aa84a6c87d2aea147c83ec8f082","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_07ba52f17df332c2016aa67ab0c5ac87d2af479346595af0bf","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading queued writer implementation\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4080-4290\"}","call_id":"call_Rs2K7L70FMmZ0tbLwDchlD6e","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab0c5bc87d2961024ad9cade57c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading injection apply implementation\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4570-5200\"}","call_id":"call_FldpK3S15CTJ8YNWAOrAozsf","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab0c5c487d2a9b84d5d05e7d192","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading section eight contract\",\"path\":\".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md:367-436\"}","call_id":"call_cCZc90T3xE6B2zlTFSGgEApE","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab0c5cc87d2a7e7f89e048289fd","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating queue and normalization uses\",\"pattern\":\"\\\\.enqueue\\\\(|normalize|replace\\\\(.*\\\\\\\\r|200~|201~|sent_text|post.commit|typeahead\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs\",\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_UAUprIcXUPLc8oLAbvLgER1T","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":8797.367399999988,"ttft":8703.853099999978,"completedAt":1789295280907,"contextSnapshot":{"promptTokens":22213,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"473314d9","parentId":"99bb2049","timestamp":"2026-09-13T10:28:00.989Z","message":{"role":"toolResult","toolCallId":"call_Rs2K7L70FMmZ0tbLwDchlD6e|fc_07ba52f17df332c2016aa67ab0c5ac87d2af479346595af0bf","toolName":"read","content":[{"type":"text","text":"fn controller_writer(\n…\n) {\n…\n    send.lifecycle_event(\n        \"writer-exit\",\n        &format!(\"role=controller session={sid} reason=channel-closed\"),\n    );\n}\n\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\n/// was removed as a delivery path; it did not submit on a modern TUI).\n///\n/// Holds the shared inject floor (the controller-input buffer, also touched by\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\n/// (events serialize — never two uncommitted sequences interleaved), the shared\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\n/// while the worker drives an event), and the FAULT flag the worker raises when a\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\nstruct Translation {\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\n    floor: Arc<Mutex<InjectFloor>>,\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\n    /// and are driven one-at-a-time so two sequences never interleave.\n    event_tx: Sender<String>,\n    /// The live child — shared so `dispatch_input` fires content-free pings while\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\n    child: Arc<TranslationChild>,\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\n    /// stops pinging.\n    faulted: Arc<AtomicBool>,\n}\n\n/// A child the broker hosts under a PTY — the §B-owned resources for one\n/// session, plus its gapless-resume output log.\nstruct HostedSession {\n    /// The PTY + child (PTY master/writer + spawned child process).\n    session: Arc<PtySession>,\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\n    /// so a paste burst that fills the harness's input buffer parks only the\n    /// writer thread, never the broker dispatch thread.\n    input: Arc<InputWriter>,\n    /// The output drain pump (kept so teardown can stop it).\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\n    drain: Drain,\n    /// The sequenced, buffered output + current subscriber.\n    log: Arc<Mutex<OutputLog>>,\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\n    endpoint: String,\n    /// The translation-binary attachment (Layer C), or `None` for a session with\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\n    /// (no raw PTY inject; ADR-0022 amendment).\n    translation: Option<Arc<Translation>>,\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\n    /// from the exact same command (`build_translation`) without re-reading the\n    /// manifest (works for a test session with a direct argv AND a production\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\n    /// restart to the swapped-in argv.\n    translation_argv: Option<Vec<String>>,\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\n    translation_respawns: Arc<AtomicU32>,\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\n    /// legacy/test/shell spawn. A live adapter update finds every live session\n    /// whose `adapter` matches to cycle its resident children.\n    adapter: String,\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\n    /// apply target for a live update's CRC swap.\n    install_dir: Option<std::path::PathBuf>,\n    /// When the session spawned (ADR-0041 decision 6) — the zombie verdict's\n    /// boot-grace anchor: a just-spawned adapter harness has no client chain\n    /// yet and must never read as a dead tree during bringup.\n    // [impl->REQ-ENDPOINT-CYCLE-HONEST]\n    spawned_at: Instant,\n    /// OS birth stamp captured while the newly spawned child is still owned\n    /// and BEFORE any waiter can reap it. Never refreshed from a remembered pid.\n    // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\n    process_started_at: Option<u64>,\n}\n\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\ntype InputRecord = Vec<u8>;\n\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\n/// few hundred KB of real paste.\nconst INPUT_QUEUE_DEPTH: usize = 4096;\n\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\n/// is the invariant, not its exact value.\nfn input_queue_depth() -> usize {\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\n        Ok(n) => n\n            .parse::<usize>()\n            .ok()\n            .filter(|d| *d > 0)\n            .unwrap_or(INPUT_QUEUE_DEPTH),\n        Err(_) => INPUT_QUEUE_DEPTH,\n    }\n}\n\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\nstruct InputWriter {\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\n    tx: SyncSender<InputRecord>,\n    /// True while the FIFO is saturated and input is being dropped; cleared on\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\n    /// `input_backpressure` so the operator sees the drop.\n    backpressure: AtomicBool,\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\n    endpoint: String,\n    /// The writer thread handle (held for ownership; the thread drains until the\n    /// last `tx` clone drops at session teardown, then exits).\n    _writer: JoinHandle<()>,\n}\n\nimpl InputWriter {\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\n    /// whose PTY write handle this thread now exclusively owns.\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\n        let writer = thread::spawn(move || input_writer(session, rx));\n        Arc::new(InputWriter {\n            tx,\n            backpressure: AtomicBool::new(false),\n            endpoint,\n            _writer: writer,\n        })\n    }\n\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\n    /// thread never blocks here, however stuck the harness is.\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n    fn enqueue(&self, bytes: InputRecord) -> bool {\n        match self.tx.try_send(bytes) {\n            Ok(()) => {\n                // Heal: input is flowing again — clear a prior backpressure stamp.\n                if self.backpressure.swap(false, Ordering::AcqRel) {\n                    self.stamp_backpressure(false);\n                }\n                true\n            }\n            Err(TrySendError::Full(_)) => {\n                // DROP + surface, but stamp only on the rising edge.\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\n                    self.stamp_backpressure(true);\n                }\n                false\n            }\n            // The writer thread is gone (session teardown) — nothing to do.\n            Err(TrySendError::Disconnected(_)) => false,\n        }\n    }\n\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\n    /// broker is the single writer). Best-effort: a session served without a\n    /// perch (raw broker tests) has nowhere to record.\n    fn stamp_backpressure(&self, dropping: bool) {\n        if self.endpoint.is_empty() {\n            return;\n        }\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\n    }\n}\n\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\n/// records from `rx` and apply each via the BLOCKING\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\n/// wedged harness parks this thread (and only this thread); a write error is\n/// swallowed (the same best-effort the inline callers had). The thread exits\n/// when the last `tx` clone drops at session teardown.\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\n    while let Ok(bytes) = rx.recv() {\n        let _ = session.write_input(&bytes);\n    }\n}\n\n/// Drain the inject floor's buffered controller input to the PTY **after** an\n/// injected sequence, then release the floor. Each step takes the buffered bytes\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\n/// hold a lock across a PTY write — now never block on it either); input arriving\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\n/// drains empty and the floor frees.\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\n\n[Showing lines 4079-4293 of 16479. Use :4294 to continue]"}],"details":{"truncation":{"content":"        \"writer-exit\",\r\n        &format!(\"role=controller session={sid} reason=channel-closed\"),\r\n    );\r\n}\r\n\r\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\r\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\r\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\r\n/// was removed as a delivery path; it did not submit on a modern TUI).\r\n///\r\n/// Holds the shared inject floor (the controller-input buffer, also touched by\r\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\r\n/// (events serialize — never two uncommitted sequences interleaved), the shared\r\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\r\n/// while the worker drives an event), and the FAULT flag the worker raises when a\r\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\r\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\r\nstruct Translation {\r\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\r\n    /// and are driven one-at-a-time so two sequences never interleave.\r\n    event_tx: Sender<String>,\r\n    /// The live child — shared so `dispatch_input` fires content-free pings while\r\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\r\n    child: Arc<TranslationChild>,\r\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\r\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\r\n    /// stops pinging.\r\n    faulted: Arc<AtomicBool>,\r\n}\r\n\r\n/// A child the broker hosts under a PTY — the §B-owned resources for one\r\n/// session, plus its gapless-resume output log.\r\nstruct HostedSession {\r\n    /// The PTY + child (PTY master/writer + spawned child process).\r\n    session: Arc<PtySession>,\r\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\r\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\r\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\r\n    /// so a paste burst that fills the harness's input buffer parks only the\r\n    /// writer thread, never the broker dispatch thread.\r\n    input: Arc<InputWriter>,\r\n    /// The output drain pump (kept so teardown can stop it).\r\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\r\n    drain: Drain,\r\n    /// The sequenced, buffered output + current subscriber.\r\n    log: Arc<Mutex<OutputLog>>,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\r\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\r\n    endpoint: String,\r\n    /// The translation-binary attachment (Layer C), or `None` for a session with\r\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\r\n    /// (no raw PTY inject; ADR-0022 amendment).\r\n    translation: Option<Arc<Translation>>,\r\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\r\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\r\n    /// from the exact same command (`build_translation`) without re-reading the\r\n    /// manifest (works for a test session with a direct argv AND a production\r\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\r\n    /// restart to the swapped-in argv.\r\n    translation_argv: Option<Vec<String>>,\r\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\r\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\r\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\r\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\r\n    translation_respawns: Arc<AtomicU32>,\r\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\r\n    /// legacy/test/shell spawn. A live adapter update finds every live session\r\n    /// whose `adapter` matches to cycle its resident children.\r\n    adapter: String,\r\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\r\n    /// apply target for a live update's CRC swap.\r\n    install_dir: Option<std::path::PathBuf>,\r\n    /// When the session spawned (ADR-0041 decision 6) — the zombie verdict's\r\n    /// boot-grace anchor: a just-spawned adapter harness has no client chain\r\n    /// yet and must never read as a dead tree during bringup.\r\n    // [impl->REQ-ENDPOINT-CYCLE-HONEST]\r\n    spawned_at: Instant,\r\n    /// OS birth stamp captured while the newly spawned child is still owned\r\n    /// and BEFORE any waiter can reap it. Never refreshed from a remembered pid.\r\n    // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\r\n    process_started_at: Option<u64>,\r\n}\r\n\r\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\r\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\ntype InputRecord = Vec<u8>;\r\n\r\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\r\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\r\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\r\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\r\n/// few hundred KB of real paste.\r\nconst INPUT_QUEUE_DEPTH: usize = 4096;\r\n\r\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\r\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\r\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\r\n/// is the invariant, not its exact value.\r\nfn input_queue_depth() -> usize {\r\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\r\n        Ok(n) => n\r\n            .parse::<usize>()\r\n            .ok()\r\n            .filter(|d| *d > 0)\r\n            .unwrap_or(INPUT_QUEUE_DEPTH),\r\n        Err(_) => INPUT_QUEUE_DEPTH,\r\n    }\r\n}\r\n\r\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\r\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\r\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\r\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\r\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\r\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\r\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\r\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\r\nstruct InputWriter {\r\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\r\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\r\n    tx: SyncSender<InputRecord>,\r\n    /// True while the FIFO is saturated and input is being dropped; cleared on\r\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\r\n    /// `input_backpressure` so the operator sees the drop.\r\n    backpressure: AtomicBool,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\r\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\r\n    endpoint: String,\r\n    /// The writer thread handle (held for ownership; the thread drains until the\r\n    /// last `tx` clone drops at session teardown, then exits).\r\n    _writer: JoinHandle<()>,\r\n}\r\n\r\nimpl InputWriter {\r\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\r\n    /// whose PTY write handle this thread now exclusively owns.\r\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\r\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\r\n        let writer = thread::spawn(move || input_writer(session, rx));\r\n        Arc::new(InputWriter {\r\n            tx,\r\n            backpressure: AtomicBool::new(false),\r\n            endpoint,\r\n            _writer: writer,\r\n        })\r\n    }\r\n\r\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\r\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\r\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\r\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\r\n    /// thread never blocks here, however stuck the harness is.\r\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\r\n    fn enqueue(&self, bytes: InputRecord) -> bool {\r\n        match self.tx.try_send(bytes) {\r\n            Ok(()) => {\r\n                // Heal: input is flowing again — clear a prior backpressure stamp.\r\n                if self.backpressure.swap(false, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(false);\r\n                }\r\n                true\r\n            }\r\n            Err(TrySendError::Full(_)) => {\r\n                // DROP + surface, but stamp only on the rising edge.\r\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(true);\r\n                }\r\n                false\r\n            }\r\n            // The writer thread is gone (session teardown) — nothing to do.\r\n            Err(TrySendError::Disconnected(_)) => false,\r\n        }\r\n    }\r\n\r\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\r\n    /// broker is the single writer). Best-effort: a session served without a\r\n    /// perch (raw broker tests) has nowhere to record.\r\n    fn stamp_backpressure(&self, dropping: bool) {\r\n        if self.endpoint.is_empty() {\r\n            return;\r\n        }\r\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\r\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\r\n    }\r\n}\r\n\r\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\r\n/// records from `rx` and apply each via the BLOCKING\r\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\r\n/// wedged harness parks this thread (and only this thread); a write error is\r\n/// swallowed (the same best-effort the inline callers had). The thread exits\r\n/// when the last `tx` clone drops at session teardown.\r\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\r\n    while let Ok(bytes) = rx.recv() {\r\n        let _ = session.write_input(&bytes);\r\n    }\r\n}\r\n\r\n/// Drain the inject floor's buffered controller input to the PTY **after** an\r\n/// injected sequence, then release the floor. Each step takes the buffered bytes\r\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\r\n/// hold a lock across a PTY write — now never block on it either); input arriving\r\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\r\n/// drains empty and the floor frees.\r\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\r\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\r\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\r\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\r\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\r\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\r\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\r\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\r","truncated":true,"truncatedBy":"lines","totalLines":12401,"totalBytes":11714,"outputLines":215,"outputBytes":11714,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":16479,"displayContent":{"text":"fn controller_writer(\n…\n) {\n…\n    send.lifecycle_event(\n        \"writer-exit\",\r\n        &format!(\"role=controller session={sid} reason=channel-closed\"),\r\n    );\r\n}\r\n\r\n/// The per-session translation-binary attachment (W2 Layer C, ADR-0022). Present\r\n/// ONLY when the spawn carried a `translation_binary` path; absent → the session\r\n/// has no idle PTY delivery and inbound SPOOLS (ADR-0022 amendment — raw inject\r\n/// was removed as a delivery path; it did not submit on a modern TUI).\r\n///\r\n/// Holds the shared inject floor (the controller-input buffer, also touched by\r\n/// `dispatch_input`), the queue that feeds inbound `<EVENT>`s to the inject worker\r\n/// (events serialize — never two uncommitted sequences interleaved), the shared\r\n/// child handle (so `dispatch_input` can fire content-free `{type:input}` pings\r\n/// while the worker drives an event), and the FAULT flag the worker raises when a\r\n/// sequence misses its [`INJECT_COMMIT_DEADLINE`] or the binary dies — after which\r\n/// the session's idle inbound SPOOLS (never raw-injected) and pinging stops.\r\nstruct Translation {\r\n    /// Controller-input buffer + inject-floor flag, shared with `dispatch_input`.\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    /// Queue one inbound `<EVENT>` envelope to the inject worker. Events QUEUE here\r\n    /// and are driven one-at-a-time so two sequences never interleave.\r\n    event_tx: Sender<String>,\r\n    /// The live child — shared so `dispatch_input` fires content-free pings while\r\n    /// the worker (which also holds an `Arc`) drives events / faults the binary.\r\n    child: Arc<TranslationChild>,\r\n    /// Raised by the worker on FAULT (commit-deadline miss / binary death): the\r\n    /// session's idle inbound then SPOOLS (never raw-injected) and `dispatch_input`\r\n    /// stops pinging.\r\n    faulted: Arc<AtomicBool>,\r\n}\r\n\r\n/// A child the broker hosts under a PTY — the §B-owned resources for one\r\n/// session, plus its gapless-resume output log.\r\nstruct HostedSession {\r\n    /// The PTY + child (PTY master/writer + spawned child process).\r\n    session: Arc<PtySession>,\r\n    /// The session's dedicated PTY input-writer (v0.13.0 P0,\r\n    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking\r\n    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,\r\n    /// so a paste burst that fills the harness's input buffer parks only the\r\n    /// writer thread, never the broker dispatch thread.\r\n    input: Arc<InputWriter>,\r\n    /// The output drain pump (kept so teardown can stop it).\r\n    #[allow(dead_code)] // held for ownership/teardown; not read directly\r\n    drain: Drain,\r\n    /// The sequenced, buffered output + current subscriber.\r\n    log: Arc<Mutex<OutputLog>>,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled). The\r\n    /// inbound dispatcher's gate subject for remote-drive (D9-1).\r\n    endpoint: String,\r\n    /// The translation-binary attachment (Layer C), or `None` for a session with\r\n    /// no `[message-idle-translation-binary]` — then idle inbound delivery SPOOLS\r\n    /// (no raw PTY inject; ADR-0022 amendment).\r\n    translation: Option<Arc<Translation>>,\r\n    /// The resolved translation argv this session was spawned with (F029 C-1) —\r\n    /// held so a bounded eager respawn after a REAL fault can rebuild the binary\r\n    /// from the exact same command (`build_translation`) without re-reading the\r\n    /// manifest (works for a test session with a direct argv AND a production\r\n    /// session). `None` for a no-translation session. Updated on an adapter-apply\r\n    /// restart to the swapped-in argv.\r\n    translation_argv: Option<Vec<String>>,\r\n    /// The bounded-respawn give-up counter shared with the inject worker (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH): the dispatch respawn path\r\n    /// increments it per rebuild, the worker RESETS it to 0 on a healthy `{commit}`\r\n    /// (so a recovered binary refreshes the whole budget). At `translate_respawn_\r\n    /// budget()` the daemon gives up (loud) and leaves the binary dead → spool.\r\n    translation_respawns: Arc<AtomicU32>,\r\n    /// The adapter name owning this session (W3a, ADR-0025) — empty for a\r\n    /// legacy/test/shell spawn. A live adapter update finds every live session\r\n    /// whose `adapter` matches to cycle its resident children.\r\n    adapter: String,\r\n    /// The adapter's install dir for this session (W3a) — `None` if unknown. The\r\n    /// apply target for a live update's CRC swap.\r\n    install_dir: Option<std::path::PathBuf>,\r\n    /// When the session spawned (ADR-0041 decision 6) — the zombie verdict's\r\n    /// boot-grace anchor: a just-spawned adapter harness has no client chain\r\n    /// yet and must never read as a dead tree during bringup.\r\n    // [impl->REQ-ENDPOINT-CYCLE-HONEST]\r\n    spawned_at: Instant,\r\n    /// OS birth stamp captured while the newly spawned child is still owned\r\n    /// and BEFORE any waiter can reap it. Never refreshed from a remembered pid.\r\n    // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\r\n    process_started_at: Option<u64>,\r\n}\r\n\r\n/// One record queued to a session's dedicated PTY input-writer thread — the raw\r\n/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\ntype InputRecord = Vec<u8>;\r\n\r\n/// The per-session PTY input FIFO depth (records). Sized for a generous paste —\r\n/// a wedged harness fills this and then DROPS, never blocking the dispatch\r\n/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the\r\n/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a\r\n/// few hundred KB of real paste.\r\nconst INPUT_QUEUE_DEPTH: usize = 4096;\r\n\r\n/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int\r\n/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a\r\n/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)\r\n/// is the invariant, not its exact value.\r\nfn input_queue_depth() -> usize {\r\n    match std::env::var(\"SPT_INPUT_QUEUE_DEPTH\") {\r\n        Ok(n) => n\r\n            .parse::<usize>()\r\n            .ok()\r\n            .filter(|d| *d > 0)\r\n            .unwrap_or(INPUT_QUEUE_DEPTH),\r\n        Err(_) => INPUT_QUEUE_DEPTH,\r\n    }\r\n}\r\n\r\n/// One session's dedicated PTY input-writer (v0.13.0 P0,\r\n/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of\r\n/// the blocking [`SessionSurface::write_input`]; every other site enqueues a\r\n/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns\r\n/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer\r\n/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,\r\n/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors\r\n/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.\r\nstruct InputWriter {\r\n    /// The bounded input FIFO to the writer thread. `try_send` only — a full\r\n    /// queue (a genuinely wedged harness) DROPS, it never blocks.\r\n    tx: SyncSender<InputRecord>,\r\n    /// True while the FIFO is saturated and input is being dropped; cleared on\r\n    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's\r\n    /// `input_backpressure` so the operator sees the drop.\r\n    backpressure: AtomicBool,\r\n    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —\r\n    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.\r\n    endpoint: String,\r\n    /// The writer thread handle (held for ownership; the thread drains until the\r\n    /// last `tx` clone drops at session teardown, then exits).\r\n    _writer: JoinHandle<()>,\r\n}\r\n\r\nimpl InputWriter {\r\n    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`\r\n    /// whose PTY write handle this thread now exclusively owns.\r\n    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {\r\n        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());\r\n        let writer = thread::spawn(move || input_writer(session, rx));\r\n        Arc::new(InputWriter {\r\n            tx,\r\n            backpressure: AtomicBool::new(false),\r\n            endpoint,\r\n            _writer: writer,\r\n        })\r\n    }\r\n\r\n    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`\r\n    /// when accepted (ordered, will land), `false` when the FIFO was full and the\r\n    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the\r\n    /// rising edge); the next accepted enqueue clears it (heal). The dispatch\r\n    /// thread never blocks here, however stuck the harness is.\r\n    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\r\n    fn enqueue(&self, bytes: InputRecord) -> bool {\r\n        match self.tx.try_send(bytes) {\r\n            Ok(()) => {\r\n                // Heal: input is flowing again — clear a prior backpressure stamp.\r\n                if self.backpressure.swap(false, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(false);\r\n                }\r\n                true\r\n            }\r\n            Err(TrySendError::Full(_)) => {\r\n                // DROP + surface, but stamp only on the rising edge.\r\n                if !self.backpressure.swap(true, Ordering::AcqRel) {\r\n                    self.stamp_backpressure(true);\r\n                }\r\n                false\r\n            }\r\n            // The writer thread is gone (session teardown) — nothing to do.\r\n            Err(TrySendError::Disconnected(_)) => false,\r\n        }\r\n    }\r\n\r\n    /// Mirror the input-backpressure signal to the perch's `info.json` (the\r\n    /// broker is the single writer). Best-effort: a session served without a\r\n    /// perch (raw broker tests) has nowhere to record.\r\n    fn stamp_backpressure(&self, dropping: bool) {\r\n        if self.endpoint.is_empty() {\r\n            return;\r\n        }\r\n        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);\r\n        let _ = spt_store::info::set_input_backpressure(&perch, dropping);\r\n    }\r\n}\r\n\r\n/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain\r\n/// records from `rx` and apply each via the BLOCKING\r\n/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A\r\n/// wedged harness parks this thread (and only this thread); a write error is\r\n/// swallowed (the same best-effort the inline callers had). The thread exits\r\n/// when the last `tx` clone drops at session teardown.\r\nfn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {\r\n    while let Ok(bytes) = rx.recv() {\r\n        let _ = session.write_input(&bytes);\r\n    }\r\n}\r\n\r\n/// Drain the inject floor's buffered controller input to the PTY **after** an\r\n/// injected sequence, then release the floor. Each step takes the buffered bytes\r\n/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never\r\n/// hold a lock across a PTY write — now never block on it either); input arriving\r\n/// mid-flush keeps buffering, so strict order is preserved until the buffer\r\n/// drains empty and the floor frees.\r\n/// Lock the per-session inject floor, RECOVERING a poisoned mutex instead of\r\n/// panicking (B6, REQ-HAZARD-INJECT-WORKER-POISON). The floor is SHARED by the\r\n/// inject worker (`run_inject_worker` open / `flush_inject_floor`) and the\r\n/// controller-input path (`dispatch_input` Layer C `buffer_if_held`). A panic under\r\n/// the lock on EITHER poisons it; a bare `.lock().unwrap()` at the next site then\r\n/// panics too — and a panic in the inject WORKER kills its thread WITHOUT reaping\r\n/// the translation child, ORPHANING a live binary while `event_tx.send` fails, so\r\n/// force-native delivery reports \"worker-gone\" (delivered=false) FOREVER though the\r","startLine":3910,"lineNumbers":[3910,null,3927,null,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]},"fileSize":857503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":16479,"totalBytes":11714,"outputLines":215,"outputBytes":11714,"shownRange":{"start":4079,"end":4293},"nextOffset":4294}}},"isError":false,"timestamp":1789295280989}}
{"type":"message","id":"a204838e","parentId":"473314d9","timestamp":"2026-09-13T10:28:00.991Z","message":{"role":"toolResult","toolCallId":"call_cCZc90T3xE6B2zlTFSGgEApE|fc_07ba52f17df332c2016aa67ab0c5c487d2a9b84d5d05e7d192","toolName":"read","content":[{"type":"text","text":"# Submission-bound input provenance — the public harness contract\n…\n## 7. Adapter answers on record\n…\n### claude-spt (perri, 2026-09-12) — answered against this frame, with measurements\n…\n\n**§5.4 report: yes** — adapter-minted submission id, token, and exact committed bytes in one call at\nacceptance. Two operational facts: the hook runs against a ~5s deadline (measured p99 4835ms on that\nnode) so **a report can be lost to a kill** — which is an argument FOR §3.5's same-id-same-payload\nretry, not merely a tolerance of it; and 99.4% of the hook's wall time is serial `spt` process spawns,\nso **the token read must fold into a call the hook already makes** rather than adding a spawn on the\ndeadline-critical path. Noted for the implementation question this contract deliberately leaves open:\nreading the token from inside `UserPromptSubmit` IS the acceptance-time read, and is a forbidden sample\nonly when taken at report time.\n\n**Named limitations (§5.5):** no queued-vs-immediate flag and no sat-time on the hook input; no\npost-acceptance observation, so rewrites are undetectable; the surface is neither universal nor\nguaranteed enabled; the hook is deadline-killed under load, so reports must be idempotent and\nloss-tolerant; and machine-injected submissions are separable only by the adapter's recognition of\nshapes it has been told about.\n\n### omp-spt (emphasys, 2026-09-13, message 4II3N2NA) — answered from public upstream declarations, not a runtime measurement\n\n**No submission-bound carrier.** `InputEvent` carries `type`, `text`, optional `images`, and\n`source: \"interactive\" | \"rpc\" | \"extension\"` — no submission id, no attachment identity, no\ncore-issued value. `BeforeAgentStartEvent` carries `prompt`/`images`/`systemPrompt`, nothing more.\n**`InputEventResult` may replace text/images**, so observed text is not guaranteed to be the committed\npayload after later extensions run. `sendUserMessage` permits programmatic prompts and queued streaming.\n\n**§5.2 ordering: UNVERIFIED.** The declarations do not establish a pre-enqueue acceptance boundary;\nemphasys inspected upstream `main` declarations only and will not promise ordering or byte preservation.\n\n**§5.2b origin: `source` names a ROUTE, not a person.** `source === \"interactive\"` distinguishes input\nroutes from one another, not an authenticated controller from automation using the same route; no\nreplay/resume discriminator exists.\n\nScope carried with the answer: no experiment, no installed-runtime measurement, no core-source inspection.\n\n## 8. RULING (doyle, 2026-09-13; RE-CUT the same day on the operator's direction) — core-only; no token, nothing asked of adapters\n\n**Operator, 2026-09-13, direct:** \"#300 needs to be developed entirely independently of harness adapters.\nspt-core does not concern itself with harness adapter integration. omp-spt and/or claude-spt should not\nhave anything to do with #300. they already submit USER_INPUT kind event hooks, and that's all spt-core\nshould be asking of them.\"\n\nThat strikes the whole adapter half of this contract — §3's obligations, the token, and the first cut of\nthis ruling (a manifest declaration plus a submission id, retracted within the hour). What remains is\nwhat core can do with what it ALREADY receives: a session-authenticated USER_INPUT report carrying the\nexact payload, at whatever moment the harness reports it.\n\n1. **Core binds origin at RECEIPT of every USER_INPUT report** — the session's current authenticated\n   REMOTE controller seat (the broker-owned `driven_by`). Local seat, viewer, or no seat ⇒ no origin ⇒\n   nothing served, named on the declined line. No adapter declares anything; no adapter is asked when it\n   reports.\n2. **Own-injection exclusion.** Core delivers peer messages by writing into the session's PTY itself. A\n   USER_INPUT whose payload is a delivery core wrote into that session binds no origin — recognised by\n   the bytes core wrote, never by payload shape. This is core's origin signal for the one injection\n   source it owns; resumed-session re-submission and external automation stay uncharacterised and bind\n   the seat as the submitter rule says.\n3. **Dedup is core-side, keyed on the report:** a repeat of the same payload within the same session\n   while its reference is live re-registers nothing, emits no second helper notice, and extends no TTL.\n   No adapter-minted id exists.\n4. **Serving semantics unchanged from #17 / ADR-0058 Am.1:** submitter's machine, 24h TTL, audience the\n   receiving endpoint, helper notice once per submission and path. A path that names no file on the\n   submitter's machine is SILENTLY SKIPPED (§4.3); every other non-grant is named (§4.7).\n5. **The stated ceiling, instead of an adapter promise.** The report lags acceptance by whatever the\n   harness's own path costs (claude-spt: 0.5–1.0 s measured, n=3; omp-spt: unmeasured). A cross-operator\n   Take inside that lag attributes the submission to the NEW holder, whose own file is then served to the\n   agent that holder now controls. Core states this; it does not ask adapters to shrink it. §1's\n   prohibition on sampling stands for anything core might build LATER than receipt — receipt is the\n   earliest moment core has the payload, and it is the moment used.\n6. **§4.1 token issuance and §4.5 grace period: STRUCK.** §7's adapter answers stay on record as\n   measurements of the ceiling in (5), not as obligations.\n\n**Build:** `REQ-INPUT-PROVENANCE-ACCEPTANCE-REPORT` (traceable-reqs.toml, minted unactivated). Product\nlane: todlando. Adapter side: NONE."}],"details":{"totalLines":436,"displayContent":{"text":"# Submission-bound input provenance — the public harness contract\n…\n## 7. Adapter answers on record\n…\n### claude-spt (perri, 2026-09-12) — answered against this frame, with measurements\n…\n\r\n**§5.4 report: yes** — adapter-minted submission id, token, and exact committed bytes in one call at\r\nacceptance. Two operational facts: the hook runs against a ~5s deadline (measured p99 4835ms on that\r\nnode) so **a report can be lost to a kill** — which is an argument FOR §3.5's same-id-same-payload\r\nretry, not merely a tolerance of it; and 99.4% of the hook's wall time is serial `spt` process spawns,\r\nso **the token read must fold into a call the hook already makes** rather than adding a spawn on the\r\ndeadline-critical path. Noted for the implementation question this contract deliberately leaves open:\r\nreading the token from inside `UserPromptSubmit` IS the acceptance-time read, and is a forbidden sample\r\nonly when taken at report time.\r\n\r\n**Named limitations (§5.5):** no queued-vs-immediate flag and no sat-time on the hook input; no\r\npost-acceptance observation, so rewrites are undetectable; the surface is neither universal nor\r\nguaranteed enabled; the hook is deadline-killed under load, so reports must be idempotent and\r\nloss-tolerant; and machine-injected submissions are separable only by the adapter's recognition of\r\nshapes it has been told about.\r\n\r\n### omp-spt (emphasys, 2026-09-13, message 4II3N2NA) — answered from public upstream declarations, not a runtime measurement\r\n\r\n**No submission-bound carrier.** `InputEvent` carries `type`, `text`, optional `images`, and\r\n`source: \"interactive\" | \"rpc\" | \"extension\"` — no submission id, no attachment identity, no\r\ncore-issued value. `BeforeAgentStartEvent` carries `prompt`/`images`/`systemPrompt`, nothing more.\r\n**`InputEventResult` may replace text/images**, so observed text is not guaranteed to be the committed\r\npayload after later extensions run. `sendUserMessage` permits programmatic prompts and queued streaming.\r\n\r\n**§5.2 ordering: UNVERIFIED.** The declarations do not establish a pre-enqueue acceptance boundary;\r\nemphasys inspected upstream `main` declarations only and will not promise ordering or byte preservation.\r\n\r\n**§5.2b origin: `source` names a ROUTE, not a person.** `source === \"interactive\"` distinguishes input\r\nroutes from one another, not an authenticated controller from automation using the same route; no\r\nreplay/resume discriminator exists.\r\n\r\nScope carried with the answer: no experiment, no installed-runtime measurement, no core-source inspection.\r\n\r\n## 8. RULING (doyle, 2026-09-13; RE-CUT the same day on the operator's direction) — core-only; no token, nothing asked of adapters\r\n\r\n**Operator, 2026-09-13, direct:** \"#300 needs to be developed entirely independently of harness adapters.\r\nspt-core does not concern itself with harness adapter integration. omp-spt and/or claude-spt should not\r\nhave anything to do with #300. they already submit USER_INPUT kind event hooks, and that's all spt-core\r\nshould be asking of them.\"\r\n\r\nThat strikes the whole adapter half of this contract — §3's obligations, the token, and the first cut of\r\nthis ruling (a manifest declaration plus a submission id, retracted within the hour). What remains is\r\nwhat core can do with what it ALREADY receives: a session-authenticated USER_INPUT report carrying the\r\nexact payload, at whatever moment the harness reports it.\r\n\r\n1. **Core binds origin at RECEIPT of every USER_INPUT report** — the session's current authenticated\r\n   REMOTE controller seat (the broker-owned `driven_by`). Local seat, viewer, or no seat ⇒ no origin ⇒\r\n   nothing served, named on the declined line. No adapter declares anything; no adapter is asked when it\r\n   reports.\r\n2. **Own-injection exclusion.** Core delivers peer messages by writing into the session's PTY itself. A\r\n   USER_INPUT whose payload is a delivery core wrote into that session binds no origin — recognised by\r\n   the bytes core wrote, never by payload shape. This is core's origin signal for the one injection\r\n   source it owns; resumed-session re-submission and external automation stay uncharacterised and bind\r\n   the seat as the submitter rule says.\r\n3. **Dedup is core-side, keyed on the report:** a repeat of the same payload within the same session\r\n   while its reference is live re-registers nothing, emits no second helper notice, and extends no TTL.\r\n   No adapter-minted id exists.\r\n4. **Serving semantics unchanged from #17 / ADR-0058 Am.1:** submitter's machine, 24h TTL, audience the\r\n   receiving endpoint, helper notice once per submission and path. A path that names no file on the\r\n   submitter's machine is SILENTLY SKIPPED (§4.3); every other non-grant is named (§4.7).\r\n5. **The stated ceiling, instead of an adapter promise.** The report lags acceptance by whatever the\r\n   harness's own path costs (claude-spt: 0.5–1.0 s measured, n=3; omp-spt: unmeasured). A cross-operator\r\n   Take inside that lag attributes the submission to the NEW holder, whose own file is then served to the\r\n   agent that holder now controls. Core states this; it does not ask adapters to shrink it. §1's\r\n   prohibition on sampling stands for anything core might build LATER than receipt — receipt is the\r\n   earliest moment core has the payload, and it is the moment used.\r\n6. **§4.1 token issuance and §4.5 grace period: STRUCK.** §7's adapter answers stay on record as\r\n   measurements of the ceiling in (5), not as obligations.\r\n\r\n**Build:** `REQ-INPUT-PROVENANCE-ACCEPTANCE-REPORT` (traceable-reqs.toml, minted unactivated). Product\r\nlane: todlando. Adapter side: NONE.\r","startLine":1,"lineNumbers":[1,null,288,null,290,null,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]},"fileSize":33503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\docs\\INPUT-PROVENANCE-CONTRACT.md"}}},"isError":false,"timestamp":1789295280990}}
{"type":"message","id":"8d07838c","parentId":"a204838e","timestamp":"2026-09-13T10:28:00.998Z","message":{"role":"toolResult","toolCallId":"call_FldpK3S15CTJ8YNWAOrAozsf|fc_07ba52f17df332c2016aa67ab0c5bc87d2961024ad9cade57c","toolName":"read","content":[{"type":"text","text":"/// dispatch thread, fire-and-forget with a LOUD stderr fallback (silence is\n/// the one thing brute force is buying), rate-bounded by the seal ledger's\n/// own backoff.\n// [impl->REQ-SEAL-CEREMONY-TOTP]\nfn alert_seal_ceremony_brute_force(subnet: &str, failures: u32) {\n    let subnet = subnet.to_string();\n    let body = format!(\n        \"{failures} wrong seal-ceremony codes have been presented at this node. \\\n         If that was not you, an agent on this machine is trying to mint a wax \\\n         seal in your name.\"\n    );\n    spt_proto::emit_line_err!(\"SEAL_CEREMONY_BRUTE_FORCE:{subnet}: {body}\");\n    std::thread::spawn(move || {\n        if let Err(e) = raise_engine_room_alert(&subnet, &body) {\n            spt_proto::emit_line_err!(\"SEAL_CEREMONY_ALERT_UNRAISED:{subnet}: {e} — {body}\");\n        }\n    });\n}\n\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\n    loop {\n        let chunk = lock_floor(floor).take_or_release();\n        match chunk {\n            Some(bytes) => {\n                input.enqueue(bytes);\n            }\n            None => break,\n        }\n    }\n}\n\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\n///\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\n/// deadline-miss or binary death: flush+release anyway (operator input is never\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\n/// (session teardown), reaping the child.\n///\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\n/// sequence and the operator's live controller share one PTY without clobbering\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\n/// the \"never park forever\" bound applied to injection.\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\n/// (reused; only the translation child is created). Extracted verbatim from\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\n/// rebuild a session's translation child from the NEW binary without disturbing\n/// the PTY / output log / session (brain-parity).\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\nfn build_translation(\n    argv: &[String],\n    endpoint_id: &str,\n    input: &Arc<InputWriter>,\n    respawns: Arc<AtomicU32>,\n    log: &Arc<Mutex<OutputLog>>,\n) -> Option<Arc<Translation>> {\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\n    match TranslationChild::spawn(argv, cmd_tx) {\n        Ok(child) => {\n            let child = Arc::new(child);\n            // First line: identity (content-free of any PTY bytes).\n            let _ = child.send(&ToBinary::Init {\n                endpoint_id: endpoint_id.to_string(),\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\n            });\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\n            let faulted = Arc::new(AtomicBool::new(false));\n            let (event_tx, event_rx) = channel::<String>();\n            let w_input = Arc::clone(input);\n            let w_child = Arc::clone(&child);\n            let w_floor = Arc::clone(&floor);\n            let w_faulted = Arc::clone(&faulted);\n            let w_respawns = Arc::clone(&respawns);\n            let w_log = Arc::clone(log);\n            let w_endpoint = endpoint_id.to_string();\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\n            // recovery missed, anything) must NOT silently kill the thread and orphan\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\n            let sup_child = Arc::clone(&child);\n            let sup_faulted = Arc::clone(&faulted);\n            let sup_endpoint = endpoint_id.to_string();\n            thread::spawn(move || {\n                run_inject_worker_supervised(\n                    sup_child,\n                    sup_faulted,\n                    sup_endpoint,\n                    std::panic::AssertUnwindSafe(move || {\n                        run_inject_worker(\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\n                            w_log, w_endpoint,\n                        );\n                    }),\n                );\n            });\n            Some(Arc::new(Translation {\n                floor,\n                event_tx,\n                child,\n                faulted,\n            }))\n        }\n        Err(e) => {\n            spt_proto::emit_line_err!(\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\n            );\n            None\n        }\n    }\n}\n\n/// The resident translation binary path declared by the manifest at\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\n// [impl->REQ-TRANSLATE-COMMAND]\nfn read_translation_command(install_dir: &std::path::Path) -> Option<Vec<String>> {\n    let s = std::fs::read_to_string(install_dir.join(\"manifest.toml\")).ok()?;\n    let manifest = spt_runtime::Manifest::from_toml_str(&s).ok()?;\n    let decl = manifest.message_idle_translation_binary.as_ref()?;\n    // F-019 / REQ-INSTALL-11 + ADR-0029: resolve `command` (preferred) else the\n    // deprecated `path` to a spawnable argv — adapter-static keys substituted, the\n    // program token resolved against the install dir (same as the initial spawn in\n    // harnesshost) so the W3d live-update RESPAWN runs the real binary, not a\n    // verbatim path Command::new fails to find (which would spool idle inbound).\n    spt_runtime::runtime::resolve_translation_argv(\n        decl,\n        Some(install_dir),\n        Some(manifest.adapter.name.as_str()),\n    )\n}\n\n/// Run the inject worker under a panic guard (B6, REQ-HAZARD-INJECT-WORKER-POISON):\n/// `body` is the [`run_inject_worker`] call. If it PANICS, the guard FAULTs the\n/// translation binary — `fault_translation` terminates the child (no orphaned live\n/// pid) and sets `faulted` (idle inbound then spools, never a silent \"worker-gone\"\n/// force-native refusal against a still-running binary). Without this, a worker\n/// panic drops `event_rx` while the child keeps running: every later inject sees\n/// `event_tx.send` fail and reports delivered=false forever. `child` + `faulted` are\n/// the guard's OWN clones (the `body` moves its own into `run_inject_worker`).\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\nfn run_inject_worker_supervised(\n    child: Arc<TranslationChild>,\n    faulted: Arc<AtomicBool>,\n    endpoint: String,\n    body: impl FnOnce() + std::panic::UnwindSafe,\n) {\n    if std::panic::catch_unwind(body).is_err() {\n        // A worker panic is a REAL fault — stamp the perch (F029 C-1, so the\n        // degraded delivery is harness-reachable, not only on the F-019 stderr)\n        // then terminate + latch.\n        stamp_translation_fault(&endpoint, \"inject worker panicked\");\n        fault_translation(&faulted, &child, \"inject worker panicked\");\n    }\n}\n\n/// Layer 1 (REQ-INJECT-MULTILINE-INTEGRITY, the load-bearing fix): settle-gate before\n/// ANY inject byte of a sequence. The head-swallow race is a PTY whose input reader is\n/// not yet attached/settled when the first bytes are typed. It is NOT startup-only: a\n/// mid-session harness reset (a `/clear` re-entering raw mode, re-attaching the input\n/// reader) RE-CREATES the same window (field-proven on 0.29.0 — a checkpoint-wake payload\n/// injected right after `/clear` lost its head), so an echoing/interactive PTY must\n/// re-settle every delivery, not once per worker. Detect readiness with a POSITIVE\n/// signal, not a blind timer: write the benign DSR cursor-position probe and wait for the\n/// session ring to produce output (the child answered / re-rendered → its reader is live).\n/// Re-probe each tick until output appears or the bounded deadline elapses — a genuinely\n/// quiet/dead child never parks the worker (echo-verify + the commit deadline still bound\n/// delivery). No lock is held across a PTY write. Returns `true` iff the probe was\n/// OBSERVED (ring advanced) — the caller uses that to distinguish an echoing PTY (re-settle\n/// every delivery) from a non-echoing ConPTY (settle is useless + costs the full deadline).\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\n    let baseline = recover_log(log).high_water();\n    let deadline = Instant::now() + inject_settle_deadline();\n    loop {\n        // Probe: a live reader produces output → the ring advances. On an\n        // echoing PTY the observable is the CPR round-trip: the probe echoes\n        // into the child's OUTPUT, where the drain answers it and STRIPS the\n        // query from the forwarded stream (REQ-DSR-SINGLE-CPR — the echoed\n        // query itself no longer reaches the ring); the CPR written to the\n        // child's input then echoes back as output and advances the ring one\n        // hop later, well within the poll cadence. A re-render works the same\n        // as before; a non-echoing ConPTY stays unobservable (the latch).\n        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\n        thread::sleep(INJECT_SETTLE_POLL);\n        if recover_log(log).high_water() > baseline {\n            return true; // output produced since baseline → the input reader is live\n        }\n        if Instant::now() >= deadline {\n            spt_proto::emit_line_err!(\n                \"INJECT_SETTLE_TIMEOUT:{endpoint}: no PTY readiness echo within {}ms — proceeding (echo-verify guards head loss)\",\n                inject_settle_deadline().as_millis()\n            );\n            return false;\n        }\n    }\n}\n\n/// Decide whether to run the settle-gate for this delivery attempt (Layer 1 re-arm,\n/// REQ-INJECT-MULTILINE-INTEGRITY). The reader-reattach race is NOT startup-only, so an\n/// echoing/interactive PTY (the bug-prone class — a `/clear` re-attaches its reader\n/// mid-session) MUST re-settle before EVERY delivery's first byte. We only skip that\n/// steady-state settle where the probe is known UNOBSERVABLE on this PTY (a non-echoing\n/// ConPTY, whose DSR answer never surfaces): there is no reader-reattach race to guard,\n/// and each settle would burn the full deadline. A RE-DRIVE (attempt > 1) always settles\n/// regardless of the latch — it is only reached on a swallowed head, so readiness must be\n/// re-confirmed before retyping.\n// [impl->REQ-HAZARD-INJECT-SETTLE-REARM]\nfn should_settle(attempt: u32, probe_unobservable: bool) -> bool {\n    attempt > 1 || !probe_unobservable\n}\n\n/// Layer 2 (REQ-INJECT-MULTILINE-INTEGRITY): after a `{commit}`, wait a bounded window\n/// for the typed payload's HEAD to echo back on the session ring (bytes produced since\n/// `seq_start`), then verify. A swallowed head means the prefix never appears. Empty\n/// payload (keys-only sequence) is vacuously verified with no wait.\n///\n/// **Fail SAFE on an unobservable echo.** The head-loss signature is a NON-empty echo\n/// whose head is missing (the field \"~322B tail-only suffix\"). But some environments\n/// echo nothing observable at all (Windows ConPTY does not surface the cooked echo of a\n/// PROGRAMMATICALLY-injected line, and a harness may not re-render typed text into the\n/// output stream). There, no echo ever appears; treating that as head-loss would re-drive\n/// and LOUD-spool EVERY real delivery. So when the window elapses with NO output produced,\n/// we assume delivered (the settle-gate remains the root fix); only a non-empty echo that\n/// is missing the head is judged a loss.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\n    if sent_text.is_empty() {\n        return true;\n    }\n    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\n    // spool wiring deterministically on any platform (physical head-loss is a forkpty-\n    // only race). Read only when the env is present, so production never pays for it.\n    if std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some() {\n        return false;\n    }\n    let deadline = Instant::now() + inject_echo_verify_deadline();\n    loop {\n        let echoed = recover_log(log).bytes_since(seq_start);\n        if echo_verified(sent_text, &echoed) {\n            return true;\n        }\n        if Instant::now() >= deadline {\n            // Unverifiable (nothing echoed at all) → fail safe, assume delivered. A\n            // NON-empty echo missing the head is the real head-loss → not verified.\n            return echoed.is_empty();\n        }\n        thread::sleep(INJECT_SETTLE_POLL);\n    }\n}\n\n/// Layer 3 (REQ-INJECT-MULTILINE-INTEGRITY, the ruling's named fallback under the\n/// settle-gate): enqueue payload text in bounded chunks rather than one write, so no\n/// single huge write races the terminal's input coalescer. Order is preserved (single\n/// FIFO, single writer). The reader is already confirmed live by the settle-gate before\n/// the first chunk lands.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\nfn enqueue_text_chunked(input: &InputWriter, bytes: Vec<u8>) {\n    chunk_text(&bytes, inject_text_chunk(), |part| {\n        input.enqueue(part.to_vec());\n    });\n}\n\n/// Pure chunk-split core (unit-testable): a payload at or below `chunk` is emitted\n/// whole; a larger one is split into ordered `chunk`-sized parts (last part short).\n/// Reassembling the emitted parts in order yields the original bytes exactly.\nfn chunk_text(bytes: &[u8], chunk: usize, mut emit: impl FnMut(&[u8])) {\n    if bytes.len() <= chunk {\n        emit(bytes);\n        return;\n    }\n    for part in bytes.chunks(chunk) {\n        emit(part);\n    }\n}\n\n/// The outcome of driving ONE inject sequence (feed the event, drain keystroke\n/// commands to `{commit}` / deadline / death). Carries the payload text TYPED this\n/// attempt so Layer 2 can echo-verify it and (on a re-drive) the caller can re-run.\nenum SeqOutcome {\n    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\n    /// concatenated payload text typed (keys excluded — they carry no echoable text).\n    Committed { sent_text: Vec<u8> },\n    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\n    Missed,\n    /// The binary's stdout closed mid-sequence (a REAL death fault).\n    Disconnected,\n    /// The stdin write to feed the event FAILED — the binary is gone (a REAL fault).\n    SendFailed,\n}\n\n/// Drive one inject sequence for `envelope`: open the floor, feed the event, drain the\n/// binary's `{key}`/`{text}`/`{delay_ms}` commands to the PTY (text paced-chunked under\n/// the settle-gate) until `{commit}` / the commit deadline / stdout death, then flush the\n/// buffered controller input and release the floor in EVERY exit path (the anti-stall\n/// guarantee). Returns the outcome + typed payload text for the caller's echo-verify.\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\nfn drive_one_sequence(\n    input: &InputWriter,\n    floor: &Mutex<InjectFloor>,\n    child: &TranslationChild,\n    cmd_rx: &Receiver<KeyCmd>,\n    envelope: String,\n) -> SeqOutcome {\n    lock_floor(floor).open();\n    if child.send(&ToBinary::Event { envelope }).is_err() {\n        flush_inject_floor(floor, input);\n        return SeqOutcome::SendFailed;\n    }\n    let deadline = Instant::now() + inject_commit_deadline();\n    let mut committed = false;\n    let mut disconnected = false;\n    let mut sent_text: Vec<u8> = Vec::new();\n    loop {\n        let now = Instant::now();\n        let remaining = deadline.saturating_duration_since(now);\n        if remaining.is_zero() {\n            break; // deadline → MISS\n        }\n        match cmd_rx.recv_timeout(remaining) {\n            Ok(KeyCmd::Key { key }) => {\n                if let Some(bytes) = key_to_bytes(&key) {\n                    input.enqueue(bytes);\n                }\n            }\n            Ok(KeyCmd::Text { text }) => {\n                // Accumulate the payload for echo-verify, then type it paced-chunked.\n                let bytes = text.into_bytes();\n                sent_text.extend_from_slice(&bytes);\n                enqueue_text_chunked(input, bytes);\n            }\n            Ok(KeyCmd::Delay { delay_ms }) => {\n                let want = Duration::from_millis(delay_ms);\n                let cap = deadline.saturating_duration_since(Instant::now());\n                thread::sleep(want.min(cap));\n            }\n            Ok(KeyCmd::Commit { .. }) => {\n                committed = true;\n                break;\n            }\n            Err(RecvTimeoutError::Timeout) => break,\n            Err(RecvTimeoutError::Disconnected) => {\n                disconnected = true;\n                break;\n            }\n        }\n    }\n    // Flush buffered controller input AFTER the injected bytes + release the floor —\n    // in EVERY exit path (the ANTI-STALL guarantee).\n    flush_inject_floor(floor, input);\n    if disconnected {\n        SeqOutcome::Disconnected\n    } else if committed {\n        SeqOutcome::Committed { sent_text }\n    } else {\n        SeqOutcome::Missed\n    }\n}\n\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\n// [impl->REQ-MSG-DELIVERY-AXES]\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\nfn run_inject_worker(\n    input: Arc<InputWriter>,\n    child: Arc<TranslationChild>,\n    floor: Arc<Mutex<InjectFloor>>,\n    event_rx: Receiver<String>,\n    cmd_rx: Receiver<KeyCmd>,\n    faulted: Arc<AtomicBool>,\n    respawns: Arc<AtomicU32>,\n    log: Arc<Mutex<OutputLog>>,\n    endpoint: String,\n) {\n    // C-1 worker-local state (REQ-TRANSLATE-COMMIT-MISS-TOLERANCE): CONSECUTIVE\n    // commit-deadline misses (reset on any successful `{commit}`), and the set of\n    // envelopes already respooled-once so a repeat miss dead-letters instead of\n    // respooling forever. Both are worker-local: a REAL fault exits the worker, and\n    // a respawn builds a fresh worker with a fresh budget + set.\n    let mut strikes: u32 = 0;\n    let mut respooled_once: HashSet<String> = HashSet::new();\n    // W5-A Layer 1 (re-arm fix): settle before EVERY delivery, not once per worker. The\n    // head-swallow race is NOT startup-only — a mid-session `/clear` re-enters the\n    // harness's raw-mode input reader and re-creates the pre-settle window (field-proven\n    // on 0.29.0: a checkpoint-wake payload injected right after `/clear` lost its head\n    // even though the worker had long since settled at spawn). So an echoing/interactive\n    // PTY re-settles per delivery (cheap: a tick or two once the reader answers). The old\n    // one-shot's ONLY legitimate purpose was sparing a NON-echoing ConPTY the full\n    // settle-deadline on every delivery (its DSR answer never surfaces, and it has no\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\n    // keeps it clear, so the bug-prone class re-settles forever.\n    let mut probe_unobservable = false;\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\n    // input into its observable output (a real TUI's input line does; a bare shell / a\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\n    // AMBIENT env can NOT arm this: both vars are scrubbed at daemon startup\n    // (`spt_runtime::INJECT_ECHO_ENV_VARS`, F-036 env-inheritance class — an inherited\n    // dev-shell export once force-enabled echo-verify host-wide and a false miss RETYPED\n    // sequences into a live session's input). Only an explicit declaration (or an\n    // in-process test broker, which never runs the daemon entrypoints) reaches this read.\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\n    // `event_rx` blocks here; events queue → one sequence at a time.\n    for envelope in event_rx {\n        // A well-behaved filter only emits in response to an event, but drop any\n        // unsolicited commands left in the channel so they can't be mis-attributed\n        // to this event's sequence.\n        while cmd_rx.try_recv().is_ok() {}\n\n        let envelope_copy = envelope.clone();\n\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\n        // unchanged (C-1 miss-tolerance).\n        let mut committed = false;\n        let mut disconnected = false;\n        let mut send_failed = false;\n        let mut integrity_lost = false;\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\n            // readiness before retyping). Skip only where the probe is known unobservable\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\n            if should_settle(attempt, probe_unobservable) {\n                let observed = settle_before_inject(&input, &log, &endpoint);\n                if attempt == 1 {\n                    probe_unobservable = !observed;\n                }\n            }\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\n            // Layer 2 sees only THIS sequence's output.\n            let seq_start = recover_log(&log).high_water();\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\n                SeqOutcome::SendFailed => {\n                    send_failed = true;\n                    break;\n                }\n                SeqOutcome::Disconnected => {\n                    disconnected = true;\n                    break;\n                }\n                SeqOutcome::Missed => {\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\n                    // integrity re-drive; the binary never signalled a complete sequence).\n                    break;\n                }\n                SeqOutcome::Committed { sent_text } => {\n                    committed = true;\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\n                        break; // delivered byte-complete (or echo-verify not applicable here)\n                    }\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\n                        spt_proto::emit_line_err!(\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\n                        );\n                        committed = false; // re-drive; only a verified commit counts\n                        continue;\n                    }\n                    // Second miss → the head is swallowed even after a re-drive.\n                    integrity_lost = true;\n                    break;\n                }\n            }\n        }\n\n        if send_failed {\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\n            respool_and_fault(\n                &endpoint,\n                envelope_copy,\n                &faulted,\n                &child,\n                \"stdin write failed\",\n            );\n            return;\n        }\n\n        if integrity_lost {\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\n            // committed fine; the terminal swallowed the head), so the worker survives.\n            respool_envelope(&endpoint, &envelope_copy);\n            spt_proto::emit_line_err!(\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\n            );\n            strikes = 0;\n            respawns.store(0, Ordering::Relaxed);\n            respooled_once.remove(&envelope_copy);\n            continue;\n        }\n\n        if committed {\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\n            // (a working binary heals it), and forget any earlier respool of THIS\n            // envelope (it succeeded → a future miss may respool it afresh).\n            strikes = 0;\n            respawns.store(0, Ordering::Relaxed);\n            respooled_once.remove(&envelope_copy);\n            continue;\n        }\n\n        if disconnected {\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\n            // terminate + exit for a bounded respawn.\n            respool_and_fault(\n                &endpoint,\n                envelope_copy,\n                &faulted,\n                &child,\n                \"binary stdout disconnected\",\n            );\n            return;\n        }\n\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\n        strikes += 1;\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\n            MissRespool::Respool => {\n                // First miss of this envelope — respool it ONCE (idle-eligible,\n                // poll-fed) without faulting.\n                respool_envelope(&endpoint, &envelope_copy);\n                spt_proto::emit_line_err!(\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\n                    inject_miss_strike_budget()\n                );\n            }\n            MissRespool::DeadLetter => {\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\n                // do NOT respool again (never a silent drop; it still counts as a\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\n                spt_proto::emit_line_err!(\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\n                    inject_miss_strike_budget()\n                );\n            }\n        }\n\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\n            // envelope was already respooled/dead-lettered above — no double respool.)\n            fault_translation_stamped(\n                &endpoint,\n                &faulted,\n                &child,\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\n            );\n            return;\n        }\n        // Miss tolerated (below the budget): the binary survives — continue to the\n        // next queued event through the SAME live binary.\n    }\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\n    child.terminate();\n}\n\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\n/// composed with the fault, on a REAL binary-death fault.\n// [impl->REQ-MSG-DELIVERY-AXES]\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\nfn respool_envelope(endpoint: &str, envelope: &str) {\n    let perch_path =\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\n        spt_proto::emit_line_err!(\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\n        );\n    }\n}\n\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\n/// in-flight delivery), then stamp the perch + fault the translation binary.\n// [impl->REQ-MSG-DELIVERY-AXES]\nfn respool_and_fault(\n    endpoint: &str,\n    envelope: String,\n    faulted: &AtomicBool,\n    child: &TranslationChild,\n    why: &str,\n) {\n    respool_envelope(endpoint, &envelope);\n    fault_translation_stamped(endpoint, faulted, child, why);\n}\n\n/// Stamp the perch's harness-reachable translation-fault surface (F029 C-1,\n/// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH). Off-lock file I/O — safe from the\n/// worker thread (no log lock held). Best-effort: a stamp failure never blocks the\n/// fault (the stderr line still fires).\nfn stamp_translation_fault(endpoint: &str, why: &str) {\n…\n}\n\n[Showing lines 4569-5203 of 16479. Use :5204 to continue]"}],"details":{"truncation":{"content":"/// dispatch thread, fire-and-forget with a LOUD stderr fallback (silence is\r\n/// the one thing brute force is buying), rate-bounded by the seal ledger's\r\n/// own backoff.\r\n// [impl->REQ-SEAL-CEREMONY-TOTP]\r\nfn alert_seal_ceremony_brute_force(subnet: &str, failures: u32) {\r\n    let subnet = subnet.to_string();\r\n    let body = format!(\r\n        \"{failures} wrong seal-ceremony codes have been presented at this node. \\\r\n         If that was not you, an agent on this machine is trying to mint a wax \\\r\n         seal in your name.\"\r\n    );\r\n    spt_proto::emit_line_err!(\"SEAL_CEREMONY_BRUTE_FORCE:{subnet}: {body}\");\r\n    std::thread::spawn(move || {\r\n        if let Err(e) = raise_engine_room_alert(&subnet, &body) {\r\n            spt_proto::emit_line_err!(\"SEAL_CEREMONY_ALERT_UNRAISED:{subnet}: {e} — {body}\");\r\n        }\r\n    });\r\n}\r\n\r\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\r\n    loop {\r\n        let chunk = lock_floor(floor).take_or_release();\r\n        match chunk {\r\n            Some(bytes) => {\r\n                input.enqueue(bytes);\r\n            }\r\n            None => break,\r\n        }\r\n    }\r\n}\r\n\r\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\r\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\r\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\r\n///\r\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\r\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\r\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\r\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\r\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\r\n/// deadline-miss or binary death: flush+release anyway (operator input is never\r\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\r\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\r\n/// (session teardown), reaping the child.\r\n///\r\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\r\n/// sequence and the operator's live controller share one PTY without clobbering\r\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\r\n/// the \"never park forever\" bound applied to injection.\r\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\r\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\r\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\r\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\r\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\r\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\r\n/// (reused; only the translation child is created). Extracted verbatim from\r\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\r\n/// rebuild a session's translation child from the NEW binary without disturbing\r\n/// the PTY / output log / session (brain-parity).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\nfn build_translation(\r\n    argv: &[String],\r\n    endpoint_id: &str,\r\n    input: &Arc<InputWriter>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: &Arc<Mutex<OutputLog>>,\r\n) -> Option<Arc<Translation>> {\r\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\r\n    match TranslationChild::spawn(argv, cmd_tx) {\r\n        Ok(child) => {\r\n            let child = Arc::new(child);\r\n            // First line: identity (content-free of any PTY bytes).\r\n            let _ = child.send(&ToBinary::Init {\r\n                endpoint_id: endpoint_id.to_string(),\r\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\r\n            });\r\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\r\n            let faulted = Arc::new(AtomicBool::new(false));\r\n            let (event_tx, event_rx) = channel::<String>();\r\n            let w_input = Arc::clone(input);\r\n            let w_child = Arc::clone(&child);\r\n            let w_floor = Arc::clone(&floor);\r\n            let w_faulted = Arc::clone(&faulted);\r\n            let w_respawns = Arc::clone(&respawns);\r\n            let w_log = Arc::clone(log);\r\n            let w_endpoint = endpoint_id.to_string();\r\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\r\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\r\n            // recovery missed, anything) must NOT silently kill the thread and orphan\r\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\r\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\r\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\r\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\r\n            let sup_child = Arc::clone(&child);\r\n            let sup_faulted = Arc::clone(&faulted);\r\n            let sup_endpoint = endpoint_id.to_string();\r\n            thread::spawn(move || {\r\n                run_inject_worker_supervised(\r\n                    sup_child,\r\n                    sup_faulted,\r\n                    sup_endpoint,\r\n                    std::panic::AssertUnwindSafe(move || {\r\n                        run_inject_worker(\r\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\r\n                            w_log, w_endpoint,\r\n                        );\r\n                    }),\r\n                );\r\n            });\r\n            Some(Arc::new(Translation {\r\n                floor,\r\n                event_tx,\r\n                child,\r\n                faulted,\r\n            }))\r\n        }\r\n        Err(e) => {\r\n            spt_proto::emit_line_err!(\r\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\r\n            );\r\n            None\r\n        }\r\n    }\r\n}\r\n\r\n/// The resident translation binary path declared by the manifest at\r\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\r\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\r\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\r\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\n// [impl->REQ-TRANSLATE-COMMAND]\r\nfn read_translation_command(install_dir: &std::path::Path) -> Option<Vec<String>> {\r\n    let s = std::fs::read_to_string(install_dir.join(\"manifest.toml\")).ok()?;\r\n    let manifest = spt_runtime::Manifest::from_toml_str(&s).ok()?;\r\n    let decl = manifest.message_idle_translation_binary.as_ref()?;\r\n    // F-019 / REQ-INSTALL-11 + ADR-0029: resolve `command` (preferred) else the\r\n    // deprecated `path` to a spawnable argv — adapter-static keys substituted, the\r\n    // program token resolved against the install dir (same as the initial spawn in\r\n    // harnesshost) so the W3d live-update RESPAWN runs the real binary, not a\r\n    // verbatim path Command::new fails to find (which would spool idle inbound).\r\n    spt_runtime::runtime::resolve_translation_argv(\r\n        decl,\r\n        Some(install_dir),\r\n        Some(manifest.adapter.name.as_str()),\r\n    )\r\n}\r\n\r\n/// Run the inject worker under a panic guard (B6, REQ-HAZARD-INJECT-WORKER-POISON):\r\n/// `body` is the [`run_inject_worker`] call. If it PANICS, the guard FAULTs the\r\n/// translation binary — `fault_translation` terminates the child (no orphaned live\r\n/// pid) and sets `faulted` (idle inbound then spools, never a silent \"worker-gone\"\r\n/// force-native refusal against a still-running binary). Without this, a worker\r\n/// panic drops `event_rx` while the child keeps running: every later inject sees\r\n/// `event_tx.send` fail and reports delivered=false forever. `child` + `faulted` are\r\n/// the guard's OWN clones (the `body` moves its own into `run_inject_worker`).\r\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\r\nfn run_inject_worker_supervised(\r\n    child: Arc<TranslationChild>,\r\n    faulted: Arc<AtomicBool>,\r\n    endpoint: String,\r\n    body: impl FnOnce() + std::panic::UnwindSafe,\r\n) {\r\n    if std::panic::catch_unwind(body).is_err() {\r\n        // A worker panic is a REAL fault — stamp the perch (F029 C-1, so the\r\n        // degraded delivery is harness-reachable, not only on the F-019 stderr)\r\n        // then terminate + latch.\r\n        stamp_translation_fault(&endpoint, \"inject worker panicked\");\r\n        fault_translation(&faulted, &child, \"inject worker panicked\");\r\n    }\r\n}\r\n\r\n/// Layer 1 (REQ-INJECT-MULTILINE-INTEGRITY, the load-bearing fix): settle-gate before\r\n/// ANY inject byte of a sequence. The head-swallow race is a PTY whose input reader is\r\n/// not yet attached/settled when the first bytes are typed. It is NOT startup-only: a\r\n/// mid-session harness reset (a `/clear` re-entering raw mode, re-attaching the input\r\n/// reader) RE-CREATES the same window (field-proven on 0.29.0 — a checkpoint-wake payload\r\n/// injected right after `/clear` lost its head), so an echoing/interactive PTY must\r\n/// re-settle every delivery, not once per worker. Detect readiness with a POSITIVE\r\n/// signal, not a blind timer: write the benign DSR cursor-position probe and wait for the\r\n/// session ring to produce output (the child answered / re-rendered → its reader is live).\r\n/// Re-probe each tick until output appears or the bounded deadline elapses — a genuinely\r\n/// quiet/dead child never parks the worker (echo-verify + the commit deadline still bound\r\n/// delivery). No lock is held across a PTY write. Returns `true` iff the probe was\r\n/// OBSERVED (ring advanced) — the caller uses that to distinguish an echoing PTY (re-settle\r\n/// every delivery) from a non-echoing ConPTY (settle is useless + costs the full deadline).\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\r\n    let baseline = recover_log(log).high_water();\r\n    let deadline = Instant::now() + inject_settle_deadline();\r\n    loop {\r\n        // Probe: a live reader produces output → the ring advances. On an\r\n        // echoing PTY the observable is the CPR round-trip: the probe echoes\r\n        // into the child's OUTPUT, where the drain answers it and STRIPS the\r\n        // query from the forwarded stream (REQ-DSR-SINGLE-CPR — the echoed\r\n        // query itself no longer reaches the ring); the CPR written to the\r\n        // child's input then echoes back as output and advances the ring one\r\n        // hop later, well within the poll cadence. A re-render works the same\r\n        // as before; a non-echoing ConPTY stays unobservable (the latch).\r\n        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\r\n        thread::sleep(INJECT_SETTLE_POLL);\r\n        if recover_log(log).high_water() > baseline {\r\n            return true; // output produced since baseline → the input reader is live\r\n        }\r\n        if Instant::now() >= deadline {\r\n            spt_proto::emit_line_err!(\r\n                \"INJECT_SETTLE_TIMEOUT:{endpoint}: no PTY readiness echo within {}ms — proceeding (echo-verify guards head loss)\",\r\n                inject_settle_deadline().as_millis()\r\n            );\r\n            return false;\r\n        }\r\n    }\r\n}\r\n\r\n/// Decide whether to run the settle-gate for this delivery attempt (Layer 1 re-arm,\r\n/// REQ-INJECT-MULTILINE-INTEGRITY). The reader-reattach race is NOT startup-only, so an\r\n/// echoing/interactive PTY (the bug-prone class — a `/clear` re-attaches its reader\r\n/// mid-session) MUST re-settle before EVERY delivery's first byte. We only skip that\r\n/// steady-state settle where the probe is known UNOBSERVABLE on this PTY (a non-echoing\r\n/// ConPTY, whose DSR answer never surfaces): there is no reader-reattach race to guard,\r\n/// and each settle would burn the full deadline. A RE-DRIVE (attempt > 1) always settles\r\n/// regardless of the latch — it is only reached on a swallowed head, so readiness must be\r\n/// re-confirmed before retyping.\r\n// [impl->REQ-HAZARD-INJECT-SETTLE-REARM]\r\nfn should_settle(attempt: u32, probe_unobservable: bool) -> bool {\r\n    attempt > 1 || !probe_unobservable\r\n}\r\n\r\n/// Layer 2 (REQ-INJECT-MULTILINE-INTEGRITY): after a `{commit}`, wait a bounded window\r\n/// for the typed payload's HEAD to echo back on the session ring (bytes produced since\r\n/// `seq_start`), then verify. A swallowed head means the prefix never appears. Empty\r\n/// payload (keys-only sequence) is vacuously verified with no wait.\r\n///\r\n/// **Fail SAFE on an unobservable echo.** The head-loss signature is a NON-empty echo\r\n/// whose head is missing (the field \"~322B tail-only suffix\"). But some environments\r\n/// echo nothing observable at all (Windows ConPTY does not surface the cooked echo of a\r\n/// PROGRAMMATICALLY-injected line, and a harness may not re-render typed text into the\r\n/// output stream). There, no echo ever appears; treating that as head-loss would re-drive\r\n/// and LOUD-spool EVERY real delivery. So when the window elapses with NO output produced,\r\n/// we assume delivered (the settle-gate remains the root fix); only a non-empty echo that\r\n/// is missing the head is judged a loss.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\r\n    if sent_text.is_empty() {\r\n        return true;\r\n    }\r\n    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\r\n    // spool wiring deterministically on any platform (physical head-loss is a forkpty-\r\n    // only race). Read only when the env is present, so production never pays for it.\r\n    if std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some() {\r\n        return false;\r\n    }\r\n    let deadline = Instant::now() + inject_echo_verify_deadline();\r\n    loop {\r\n        let echoed = recover_log(log).bytes_since(seq_start);\r\n        if echo_verified(sent_text, &echoed) {\r\n            return true;\r\n        }\r\n        if Instant::now() >= deadline {\r\n            // Unverifiable (nothing echoed at all) → fail safe, assume delivered. A\r\n            // NON-empty echo missing the head is the real head-loss → not verified.\r\n            return echoed.is_empty();\r\n        }\r\n        thread::sleep(INJECT_SETTLE_POLL);\r\n    }\r\n}\r\n\r\n/// Layer 3 (REQ-INJECT-MULTILINE-INTEGRITY, the ruling's named fallback under the\r\n/// settle-gate): enqueue payload text in bounded chunks rather than one write, so no\r\n/// single huge write races the terminal's input coalescer. Order is preserved (single\r\n/// FIFO, single writer). The reader is already confirmed live by the settle-gate before\r\n/// the first chunk lands.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn enqueue_text_chunked(input: &InputWriter, bytes: Vec<u8>) {\r\n    chunk_text(&bytes, inject_text_chunk(), |part| {\r\n        input.enqueue(part.to_vec());\r\n    });\r\n}\r\n\r\n/// Pure chunk-split core (unit-testable): a payload at or below `chunk` is emitted\r\n/// whole; a larger one is split into ordered `chunk`-sized parts (last part short).\r\n/// Reassembling the emitted parts in order yields the original bytes exactly.\r\nfn chunk_text(bytes: &[u8], chunk: usize, mut emit: impl FnMut(&[u8])) {\r\n    if bytes.len() <= chunk {\r\n        emit(bytes);\r\n        return;\r\n    }\r\n    for part in bytes.chunks(chunk) {\r\n        emit(part);\r\n    }\r\n}\r\n\r\n/// The outcome of driving ONE inject sequence (feed the event, drain keystroke\r\n/// commands to `{commit}` / deadline / death). Carries the payload text TYPED this\r\n/// attempt so Layer 2 can echo-verify it and (on a re-drive) the caller can re-run.\r\nenum SeqOutcome {\r\n    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\r\n    /// concatenated payload text typed (keys excluded — they carry no echoable text).\r\n    Committed { sent_text: Vec<u8> },\r\n    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\r\n    Missed,\r\n    /// The binary's stdout closed mid-sequence (a REAL death fault).\r\n    Disconnected,\r\n    /// The stdin write to feed the event FAILED — the binary is gone (a REAL fault).\r\n    SendFailed,\r\n}\r\n\r\n/// Drive one inject sequence for `envelope`: open the floor, feed the event, drain the\r\n/// binary's `{key}`/`{text}`/`{delay_ms}` commands to the PTY (text paced-chunked under\r\n/// the settle-gate) until `{commit}` / the commit deadline / stdout death, then flush the\r\n/// buffered controller input and release the floor in EVERY exit path (the anti-stall\r\n/// guarantee). Returns the outcome + typed payload text for the caller's echo-verify.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\r\nfn drive_one_sequence(\r\n    input: &InputWriter,\r\n    floor: &Mutex<InjectFloor>,\r\n    child: &TranslationChild,\r\n    cmd_rx: &Receiver<KeyCmd>,\r\n    envelope: String,\r\n) -> SeqOutcome {\r\n    lock_floor(floor).open();\r\n    if child.send(&ToBinary::Event { envelope }).is_err() {\r\n        flush_inject_floor(floor, input);\r\n        return SeqOutcome::SendFailed;\r\n    }\r\n    let deadline = Instant::now() + inject_commit_deadline();\r\n    let mut committed = false;\r\n    let mut disconnected = false;\r\n    let mut sent_text: Vec<u8> = Vec::new();\r\n    loop {\r\n        let now = Instant::now();\r\n        let remaining = deadline.saturating_duration_since(now);\r\n        if remaining.is_zero() {\r\n            break; // deadline → MISS\r\n        }\r\n        match cmd_rx.recv_timeout(remaining) {\r\n            Ok(KeyCmd::Key { key }) => {\r\n                if let Some(bytes) = key_to_bytes(&key) {\r\n                    input.enqueue(bytes);\r\n                }\r\n            }\r\n            Ok(KeyCmd::Text { text }) => {\r\n                // Accumulate the payload for echo-verify, then type it paced-chunked.\r\n                let bytes = text.into_bytes();\r\n                sent_text.extend_from_slice(&bytes);\r\n                enqueue_text_chunked(input, bytes);\r\n            }\r\n            Ok(KeyCmd::Delay { delay_ms }) => {\r\n                let want = Duration::from_millis(delay_ms);\r\n                let cap = deadline.saturating_duration_since(Instant::now());\r\n                thread::sleep(want.min(cap));\r\n            }\r\n            Ok(KeyCmd::Commit { .. }) => {\r\n                committed = true;\r\n                break;\r\n            }\r\n            Err(RecvTimeoutError::Timeout) => break,\r\n            Err(RecvTimeoutError::Disconnected) => {\r\n                disconnected = true;\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    // Flush buffered controller input AFTER the injected bytes + release the floor —\r\n    // in EVERY exit path (the ANTI-STALL guarantee).\r\n    flush_inject_floor(floor, input);\r\n    if disconnected {\r\n        SeqOutcome::Disconnected\r\n    } else if committed {\r\n        SeqOutcome::Committed { sent_text }\r\n    } else {\r\n        SeqOutcome::Missed\r\n    }\r\n}\r\n\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\n#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\r\nfn run_inject_worker(\r\n    input: Arc<InputWriter>,\r\n    child: Arc<TranslationChild>,\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    event_rx: Receiver<String>,\r\n    cmd_rx: Receiver<KeyCmd>,\r\n    faulted: Arc<AtomicBool>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: Arc<Mutex<OutputLog>>,\r\n    endpoint: String,\r\n) {\r\n    // C-1 worker-local state (REQ-TRANSLATE-COMMIT-MISS-TOLERANCE): CONSECUTIVE\r\n    // commit-deadline misses (reset on any successful `{commit}`), and the set of\r\n    // envelopes already respooled-once so a repeat miss dead-letters instead of\r\n    // respooling forever. Both are worker-local: a REAL fault exits the worker, and\r\n    // a respawn builds a fresh worker with a fresh budget + set.\r\n    let mut strikes: u32 = 0;\r\n    let mut respooled_once: HashSet<String> = HashSet::new();\r\n    // W5-A Layer 1 (re-arm fix): settle before EVERY delivery, not once per worker. The\r\n    // head-swallow race is NOT startup-only — a mid-session `/clear` re-enters the\r\n    // harness's raw-mode input reader and re-creates the pre-settle window (field-proven\r\n    // on 0.29.0: a checkpoint-wake payload injected right after `/clear` lost its head\r\n    // even though the worker had long since settled at spawn). So an echoing/interactive\r\n    // PTY re-settles per delivery (cheap: a tick or two once the reader answers). The old\r\n    // one-shot's ONLY legitimate purpose was sparing a NON-echoing ConPTY the full\r\n    // settle-deadline on every delivery (its DSR answer never surfaces, and it has no\r\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\r\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\r\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\r\n    // keeps it clear, so the bug-prone class re-settles forever.\r\n    let mut probe_unobservable = false;\r\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\r\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\r\n    // input into its observable output (a real TUI's input line does; a bare shell / a\r\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\r\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\r\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\r\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\r\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\r\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\r\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\r\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\r\n    // AMBIENT env can NOT arm this: both vars are scrubbed at daemon startup\r\n    // (`spt_runtime::INJECT_ECHO_ENV_VARS`, F-036 env-inheritance class — an inherited\r\n    // dev-shell export once force-enabled echo-verify host-wide and a false miss RETYPED\r\n    // sequences into a live session's input). Only an explicit declaration (or an\r\n    // in-process test broker, which never runs the daemon entrypoints) reaches this read.\r\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\r\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\r\n    // `event_rx` blocks here; events queue → one sequence at a time.\r\n    for envelope in event_rx {\r\n        // A well-behaved filter only emits in response to an event, but drop any\r\n        // unsolicited commands left in the channel so they can't be mis-attributed\r\n        // to this event's sequence.\r\n        while cmd_rx.try_recv().is_ok() {}\r\n\r\n        let envelope_copy = envelope.clone();\r\n\r\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\r\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\r\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\r\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\r\n        // unchanged (C-1 miss-tolerance).\r\n        let mut committed = false;\r\n        let mut disconnected = false;\r\n        let mut send_failed = false;\r\n        let mut integrity_lost = false;\r\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\r\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\r\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\r\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\r\n            // readiness before retyping). Skip only where the probe is known unobservable\r\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\r\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\r\n            if should_settle(attempt, probe_unobservable) {\r\n                let observed = settle_before_inject(&input, &log, &endpoint);\r\n                if attempt == 1 {\r\n                    probe_unobservable = !observed;\r\n                }\r\n            }\r\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\r\n            // Layer 2 sees only THIS sequence's output.\r\n            let seq_start = recover_log(&log).high_water();\r\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\r\n                SeqOutcome::SendFailed => {\r\n                    send_failed = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Disconnected => {\r\n                    disconnected = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Missed => {\r\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\r\n                    // integrity re-drive; the binary never signalled a complete sequence).\r\n                    break;\r\n                }\r\n                SeqOutcome::Committed { sent_text } => {\r\n                    committed = true;\r\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\r\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\r\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\r\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\r\n                        break; // delivered byte-complete (or echo-verify not applicable here)\r\n                    }\r\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\r\n                        spt_proto::emit_line_err!(\r\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\r\n                        );\r\n                        committed = false; // re-drive; only a verified commit counts\r\n                        continue;\r\n                    }\r\n                    // Second miss → the head is swallowed even after a re-drive.\r\n                    integrity_lost = true;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if send_failed {\r\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\r\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"stdin write failed\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        if integrity_lost {\r\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\r\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\r\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\r\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\r\n            // committed fine; the terminal swallowed the head), so the worker survives.\r\n            respool_envelope(&endpoint, &envelope_copy);\r\n            spt_proto::emit_line_err!(\r\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\r\n            );\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if committed {\r\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\r\n            // (a working binary heals it), and forget any earlier respool of THIS\r\n            // envelope (it succeeded → a future miss may respool it afresh).\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if disconnected {\r\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\r\n            // terminate + exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"binary stdout disconnected\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\r\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\r\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\r\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\r\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\r\n        strikes += 1;\r\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\r\n            MissRespool::Respool => {\r\n                // First miss of this envelope — respool it ONCE (idle-eligible,\r\n                // poll-fed) without faulting.\r\n                respool_envelope(&endpoint, &envelope_copy);\r\n                spt_proto::emit_line_err!(\r\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n            MissRespool::DeadLetter => {\r\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\r\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\r\n                // do NOT respool again (never a silent drop; it still counts as a\r\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\r\n                spt_proto::emit_line_err!(\r\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n        }\r\n\r\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\r\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\r\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\r\n            // envelope was already respooled/dead-lettered above — no double respool.)\r\n            fault_translation_stamped(\r\n                &endpoint,\r\n                &faulted,\r\n                &child,\r\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\r\n            );\r\n            return;\r\n        }\r\n        // Miss tolerated (below the budget): the binary survives — continue to the\r\n        // next queued event through the SAME live binary.\r\n    }\r\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\r\n    child.terminate();\r\n}\r\n\r\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\r\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\r\n/// composed with the fault, on a REAL binary-death fault.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\nfn respool_envelope(endpoint: &str, envelope: &str) {\r\n    let perch_path =\r\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\r\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\r\n        spt_proto::emit_line_err!(\r\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\r\n        );\r\n    }\r\n}\r\n\r\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\r\n/// in-flight delivery), then stamp the perch + fault the translation binary.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\nfn respool_and_fault(\r\n    endpoint: &str,\r\n    envelope: String,\r\n    faulted: &AtomicBool,\r\n    child: &TranslationChild,\r\n    why: &str,\r\n) {\r\n    respool_envelope(endpoint, &envelope);\r\n    fault_translation_stamped(endpoint, faulted, child, why);\r\n}\r\n\r\n/// Stamp the perch's harness-reachable translation-fault surface (F029 C-1,\r\n/// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH). Off-lock file I/O — safe from the\r\n/// worker thread (no log lock held). Best-effort: a stamp failure never blocks the\r\n/// fault (the stderr line still fires).\r\nfn stamp_translation_fault(endpoint: &str, why: &str) {\r","truncated":true,"truncatedBy":"lines","totalLines":11911,"totalBytes":33363,"outputLines":635,"outputBytes":33363,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":16479,"displayContent":{"text":"/// dispatch thread, fire-and-forget with a LOUD stderr fallback (silence is\r\n/// the one thing brute force is buying), rate-bounded by the seal ledger's\r\n/// own backoff.\r\n// [impl->REQ-SEAL-CEREMONY-TOTP]\r\nfn alert_seal_ceremony_brute_force(subnet: &str, failures: u32) {\r\n    let subnet = subnet.to_string();\r\n    let body = format!(\r\n        \"{failures} wrong seal-ceremony codes have been presented at this node. \\\r\n         If that was not you, an agent on this machine is trying to mint a wax \\\r\n         seal in your name.\"\r\n    );\r\n    spt_proto::emit_line_err!(\"SEAL_CEREMONY_BRUTE_FORCE:{subnet}: {body}\");\r\n    std::thread::spawn(move || {\r\n        if let Err(e) = raise_engine_room_alert(&subnet, &body) {\r\n            spt_proto::emit_line_err!(\"SEAL_CEREMONY_ALERT_UNRAISED:{subnet}: {e} — {body}\");\r\n        }\r\n    });\r\n}\r\n\r\nfn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {\r\n    loop {\r\n        let chunk = lock_floor(floor).take_or_release();\r\n        match chunk {\r\n            Some(bytes) => {\r\n                input.enqueue(bytes);\r\n            }\r\n            None => break,\r\n        }\r\n    }\r\n}\r\n\r\n/// The inject worker (W2 Layer C): one thread per translation-bearing session. It\r\n/// drives inbound `<EVENT>`s through the binary and applies the binary's keystroke\r\n/// commands to the PTY ATOMICALLY w.r.t. the operator's live controller.\r\n///\r\n/// Per queued event: drop any stray pre-event commands, OPEN the floor (operator\r\n/// input now buffers in `dispatch_input`), feed the binary the `{type:event}`,\r\n/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until\r\n/// `{commit}` — bounded by [`INJECT_COMMIT_DEADLINE`]. On commit: flush the\r\n/// buffered controller bytes AFTER the injected sequence and release the floor. On\r\n/// deadline-miss or binary death: flush+release anyway (operator input is never\r\n/// stranded), then FAULT — terminate the binary, set `faulted`, and exit (idle\r\n/// inbound then SPOOLS, never raw-injected). The loop ends when `event_tx` is dropped\r\n/// (session teardown), reaping the child.\r\n///\r\n/// The atomic open→drain→commit→flush is the W1 coexistence mechanism: an injected\r\n/// sequence and the operator's live controller share one PTY without clobbering\r\n/// each other (REQ-HAZARD-INJECT-CONTROL-COEXIST), and the deadline FAULT path is\r\n/// the \"never park forever\" bound applied to injection.\r\n/// Bring up a translation binary at `path` for `endpoint_id`: spawn the\r\n/// stdin→stdout filter, send the identity `Init`, start the inject worker that\r\n/// owns the atomic PTY apply, and return the `Arc<Translation>`. A spawn failure\r\n/// FAILS CLOSED to SPOOLING (`None`, logged): `None` now MEANS the inbound spools\r\n/// (idle delivery is translation-binary-only, never a raw PTY inject) — a missing\r\n/// binary costs idle delivery, never the operator their agent. `input` is the session's EXISTING PTY input writer\r\n/// (reused; only the translation child is created). Extracted verbatim from\r\n/// `dispatch_spawn` so a live-update restart (`dispatch_adapter_apply`, W3d) can\r\n/// rebuild a session's translation child from the NEW binary without disturbing\r\n/// the PTY / output log / session (brain-parity).\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\nfn build_translation(\r\n    argv: &[String],\r\n    endpoint_id: &str,\r\n    input: &Arc<InputWriter>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: &Arc<Mutex<OutputLog>>,\r\n) -> Option<Arc<Translation>> {\r\n    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\r\n    match TranslationChild::spawn(argv, cmd_tx) {\r\n        Ok(child) => {\r\n            let child = Arc::new(child);\r\n            // First line: identity (content-free of any PTY bytes).\r\n            let _ = child.send(&ToBinary::Init {\r\n                endpoint_id: endpoint_id.to_string(),\r\n                node: spt_store::hostlabel::os_hostname().unwrap_or_default(),\r\n            });\r\n            let floor = Arc::new(Mutex::new(InjectFloor::default()));\r\n            let faulted = Arc::new(AtomicBool::new(false));\r\n            let (event_tx, event_rx) = channel::<String>();\r\n            let w_input = Arc::clone(input);\r\n            let w_child = Arc::clone(&child);\r\n            let w_floor = Arc::clone(&floor);\r\n            let w_faulted = Arc::clone(&faulted);\r\n            let w_respawns = Arc::clone(&respawns);\r\n            let w_log = Arc::clone(log);\r\n            let w_endpoint = endpoint_id.to_string();\r\n            // B6 (REQ-HAZARD-INJECT-WORKER-POISON): run the worker under a panic\r\n            // guard. A panic inside the worker (a residual unwrap, a poisoned lock the\r\n            // recovery missed, anything) must NOT silently kill the thread and orphan\r\n            // the translation child — that drops `event_rx` WITHOUT reaping the binary,\r\n            // so a live pid + failed `event_tx.send` reports \"worker-gone\" forever. On\r\n            // a panic, FAULT (terminate the child + set faulted) — the same terminal\r\n            // state the worker's own fault paths reach — so idle inbound cleanly spools.\r\n            let sup_child = Arc::clone(&child);\r\n            let sup_faulted = Arc::clone(&faulted);\r\n            let sup_endpoint = endpoint_id.to_string();\r\n            thread::spawn(move || {\r\n                run_inject_worker_supervised(\r\n                    sup_child,\r\n                    sup_faulted,\r\n                    sup_endpoint,\r\n                    std::panic::AssertUnwindSafe(move || {\r\n                        run_inject_worker(\r\n                            w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted, w_respawns,\r\n                            w_log, w_endpoint,\r\n                        );\r\n                    }),\r\n                );\r\n            });\r\n            Some(Arc::new(Translation {\r\n                floor,\r\n                event_tx,\r\n                child,\r\n                faulted,\r\n            }))\r\n        }\r\n        Err(e) => {\r\n            spt_proto::emit_line_err!(\r\n                \"TRANSLATION_SPAWN_FAILED:{argv:?}: {e} — no idle PTY delivery; inbound spools (declare/repair the binary)\"\r\n            );\r\n            None\r\n        }\r\n    }\r\n}\r\n\r\n/// The resident translation binary path declared by the manifest at\r\n/// `<install_dir>/manifest.toml` (`[message-idle-translation-binary].path`), or\r\n/// `None` if there is no such manifest / declaration. Read fresh from disk so a\r\n/// live-update restart picks up a MOVED/renamed binary (verdict flag-5: never\r\n/// reuse the stale cached `SpawnReq.translation_binary`). (W3d)\r\n// [impl->REQ-ADAPTER-LIVE-UPDATE]\r\n// [impl->REQ-TRANSLATE-COMMAND]\r\nfn read_translation_command(install_dir: &std::path::Path) -> Option<Vec<String>> {\r\n    let s = std::fs::read_to_string(install_dir.join(\"manifest.toml\")).ok()?;\r\n    let manifest = spt_runtime::Manifest::from_toml_str(&s).ok()?;\r\n    let decl = manifest.message_idle_translation_binary.as_ref()?;\r\n    // F-019 / REQ-INSTALL-11 + ADR-0029: resolve `command` (preferred) else the\r\n    // deprecated `path` to a spawnable argv — adapter-static keys substituted, the\r\n    // program token resolved against the install dir (same as the initial spawn in\r\n    // harnesshost) so the W3d live-update RESPAWN runs the real binary, not a\r\n    // verbatim path Command::new fails to find (which would spool idle inbound).\r\n    spt_runtime::runtime::resolve_translation_argv(\r\n        decl,\r\n        Some(install_dir),\r\n        Some(manifest.adapter.name.as_str()),\r\n    )\r\n}\r\n\r\n/// Run the inject worker under a panic guard (B6, REQ-HAZARD-INJECT-WORKER-POISON):\r\n/// `body` is the [`run_inject_worker`] call. If it PANICS, the guard FAULTs the\r\n/// translation binary — `fault_translation` terminates the child (no orphaned live\r\n/// pid) and sets `faulted` (idle inbound then spools, never a silent \"worker-gone\"\r\n/// force-native refusal against a still-running binary). Without this, a worker\r\n/// panic drops `event_rx` while the child keeps running: every later inject sees\r\n/// `event_tx.send` fail and reports delivered=false forever. `child` + `faulted` are\r\n/// the guard's OWN clones (the `body` moves its own into `run_inject_worker`).\r\n// [impl->REQ-HAZARD-INJECT-WORKER-POISON]\r\nfn run_inject_worker_supervised(\r\n    child: Arc<TranslationChild>,\r\n    faulted: Arc<AtomicBool>,\r\n    endpoint: String,\r\n    body: impl FnOnce() + std::panic::UnwindSafe,\r\n) {\r\n    if std::panic::catch_unwind(body).is_err() {\r\n        // A worker panic is a REAL fault — stamp the perch (F029 C-1, so the\r\n        // degraded delivery is harness-reachable, not only on the F-019 stderr)\r\n        // then terminate + latch.\r\n        stamp_translation_fault(&endpoint, \"inject worker panicked\");\r\n        fault_translation(&faulted, &child, \"inject worker panicked\");\r\n    }\r\n}\r\n\r\n/// Layer 1 (REQ-INJECT-MULTILINE-INTEGRITY, the load-bearing fix): settle-gate before\r\n/// ANY inject byte of a sequence. The head-swallow race is a PTY whose input reader is\r\n/// not yet attached/settled when the first bytes are typed. It is NOT startup-only: a\r\n/// mid-session harness reset (a `/clear` re-entering raw mode, re-attaching the input\r\n/// reader) RE-CREATES the same window (field-proven on 0.29.0 — a checkpoint-wake payload\r\n/// injected right after `/clear` lost its head), so an echoing/interactive PTY must\r\n/// re-settle every delivery, not once per worker. Detect readiness with a POSITIVE\r\n/// signal, not a blind timer: write the benign DSR cursor-position probe and wait for the\r\n/// session ring to produce output (the child answered / re-rendered → its reader is live).\r\n/// Re-probe each tick until output appears or the bounded deadline elapses — a genuinely\r\n/// quiet/dead child never parks the worker (echo-verify + the commit deadline still bound\r\n/// delivery). No lock is held across a PTY write. Returns `true` iff the probe was\r\n/// OBSERVED (ring advanced) — the caller uses that to distinguish an echoing PTY (re-settle\r\n/// every delivery) from a non-echoing ConPTY (settle is useless + costs the full deadline).\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn settle_before_inject(input: &InputWriter, log: &Mutex<OutputLog>, endpoint: &str) -> bool {\r\n    let baseline = recover_log(log).high_water();\r\n    let deadline = Instant::now() + inject_settle_deadline();\r\n    loop {\r\n        // Probe: a live reader produces output → the ring advances. On an\r\n        // echoing PTY the observable is the CPR round-trip: the probe echoes\r\n        // into the child's OUTPUT, where the drain answers it and STRIPS the\r\n        // query from the forwarded stream (REQ-DSR-SINGLE-CPR — the echoed\r\n        // query itself no longer reaches the ring); the CPR written to the\r\n        // child's input then echoes back as output and advances the ring one\r\n        // hop later, well within the poll cadence. A re-render works the same\r\n        // as before; a non-echoing ConPTY stays unobservable (the latch).\r\n        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\r\n        thread::sleep(INJECT_SETTLE_POLL);\r\n        if recover_log(log).high_water() > baseline {\r\n            return true; // output produced since baseline → the input reader is live\r\n        }\r\n        if Instant::now() >= deadline {\r\n            spt_proto::emit_line_err!(\r\n                \"INJECT_SETTLE_TIMEOUT:{endpoint}: no PTY readiness echo within {}ms — proceeding (echo-verify guards head loss)\",\r\n                inject_settle_deadline().as_millis()\r\n            );\r\n            return false;\r\n        }\r\n    }\r\n}\r\n\r\n/// Decide whether to run the settle-gate for this delivery attempt (Layer 1 re-arm,\r\n/// REQ-INJECT-MULTILINE-INTEGRITY). The reader-reattach race is NOT startup-only, so an\r\n/// echoing/interactive PTY (the bug-prone class — a `/clear` re-attaches its reader\r\n/// mid-session) MUST re-settle before EVERY delivery's first byte. We only skip that\r\n/// steady-state settle where the probe is known UNOBSERVABLE on this PTY (a non-echoing\r\n/// ConPTY, whose DSR answer never surfaces): there is no reader-reattach race to guard,\r\n/// and each settle would burn the full deadline. A RE-DRIVE (attempt > 1) always settles\r\n/// regardless of the latch — it is only reached on a swallowed head, so readiness must be\r\n/// re-confirmed before retyping.\r\n// [impl->REQ-HAZARD-INJECT-SETTLE-REARM]\r\nfn should_settle(attempt: u32, probe_unobservable: bool) -> bool {\r\n    attempt > 1 || !probe_unobservable\r\n}\r\n\r\n/// Layer 2 (REQ-INJECT-MULTILINE-INTEGRITY): after a `{commit}`, wait a bounded window\r\n/// for the typed payload's HEAD to echo back on the session ring (bytes produced since\r\n/// `seq_start`), then verify. A swallowed head means the prefix never appears. Empty\r\n/// payload (keys-only sequence) is vacuously verified with no wait.\r\n///\r\n/// **Fail SAFE on an unobservable echo.** The head-loss signature is a NON-empty echo\r\n/// whose head is missing (the field \"~322B tail-only suffix\"). But some environments\r\n/// echo nothing observable at all (Windows ConPTY does not surface the cooked echo of a\r\n/// PROGRAMMATICALLY-injected line, and a harness may not re-render typed text into the\r\n/// output stream). There, no echo ever appears; treating that as head-loss would re-drive\r\n/// and LOUD-spool EVERY real delivery. So when the window elapses with NO output produced,\r\n/// we assume delivered (the settle-gate remains the root fix); only a non-empty echo that\r\n/// is missing the head is judged a loss.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\r\n    if sent_text.is_empty() {\r\n        return true;\r\n    }\r\n    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\r\n    // spool wiring deterministically on any platform (physical head-loss is a forkpty-\r\n    // only race). Read only when the env is present, so production never pays for it.\r\n    if std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some() {\r\n        return false;\r\n    }\r\n    let deadline = Instant::now() + inject_echo_verify_deadline();\r\n    loop {\r\n        let echoed = recover_log(log).bytes_since(seq_start);\r\n        if echo_verified(sent_text, &echoed) {\r\n            return true;\r\n        }\r\n        if Instant::now() >= deadline {\r\n            // Unverifiable (nothing echoed at all) → fail safe, assume delivered. A\r\n            // NON-empty echo missing the head is the real head-loss → not verified.\r\n            return echoed.is_empty();\r\n        }\r\n        thread::sleep(INJECT_SETTLE_POLL);\r\n    }\r\n}\r\n\r\n/// Layer 3 (REQ-INJECT-MULTILINE-INTEGRITY, the ruling's named fallback under the\r\n/// settle-gate): enqueue payload text in bounded chunks rather than one write, so no\r\n/// single huge write races the terminal's input coalescer. Order is preserved (single\r\n/// FIFO, single writer). The reader is already confirmed live by the settle-gate before\r\n/// the first chunk lands.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\nfn enqueue_text_chunked(input: &InputWriter, bytes: Vec<u8>) {\r\n    chunk_text(&bytes, inject_text_chunk(), |part| {\r\n        input.enqueue(part.to_vec());\r\n    });\r\n}\r\n\r\n/// Pure chunk-split core (unit-testable): a payload at or below `chunk` is emitted\r\n/// whole; a larger one is split into ordered `chunk`-sized parts (last part short).\r\n/// Reassembling the emitted parts in order yields the original bytes exactly.\r\nfn chunk_text(bytes: &[u8], chunk: usize, mut emit: impl FnMut(&[u8])) {\r\n    if bytes.len() <= chunk {\r\n        emit(bytes);\r\n        return;\r\n    }\r\n    for part in bytes.chunks(chunk) {\r\n        emit(part);\r\n    }\r\n}\r\n\r\n/// The outcome of driving ONE inject sequence (feed the event, drain keystroke\r\n/// commands to `{commit}` / deadline / death). Carries the payload text TYPED this\r\n/// attempt so Layer 2 can echo-verify it and (on a re-drive) the caller can re-run.\r\nenum SeqOutcome {\r\n    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\r\n    /// concatenated payload text typed (keys excluded — they carry no echoable text).\r\n    Committed { sent_text: Vec<u8> },\r\n    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\r\n    Missed,\r\n    /// The binary's stdout closed mid-sequence (a REAL death fault).\r\n    Disconnected,\r\n    /// The stdin write to feed the event FAILED — the binary is gone (a REAL fault).\r\n    SendFailed,\r\n}\r\n\r\n/// Drive one inject sequence for `envelope`: open the floor, feed the event, drain the\r\n/// binary's `{key}`/`{text}`/`{delay_ms}` commands to the PTY (text paced-chunked under\r\n/// the settle-gate) until `{commit}` / the commit deadline / stdout death, then flush the\r\n/// buffered controller input and release the floor in EVERY exit path (the anti-stall\r\n/// guarantee). Returns the outcome + typed payload text for the caller's echo-verify.\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\r\nfn drive_one_sequence(\r\n    input: &InputWriter,\r\n    floor: &Mutex<InjectFloor>,\r\n    child: &TranslationChild,\r\n    cmd_rx: &Receiver<KeyCmd>,\r\n    envelope: String,\r\n) -> SeqOutcome {\r\n    lock_floor(floor).open();\r\n    if child.send(&ToBinary::Event { envelope }).is_err() {\r\n        flush_inject_floor(floor, input);\r\n        return SeqOutcome::SendFailed;\r\n    }\r\n    let deadline = Instant::now() + inject_commit_deadline();\r\n    let mut committed = false;\r\n    let mut disconnected = false;\r\n    let mut sent_text: Vec<u8> = Vec::new();\r\n    loop {\r\n        let now = Instant::now();\r\n        let remaining = deadline.saturating_duration_since(now);\r\n        if remaining.is_zero() {\r\n            break; // deadline → MISS\r\n        }\r\n        match cmd_rx.recv_timeout(remaining) {\r\n            Ok(KeyCmd::Key { key }) => {\r\n                if let Some(bytes) = key_to_bytes(&key) {\r\n                    input.enqueue(bytes);\r\n                }\r\n            }\r\n            Ok(KeyCmd::Text { text }) => {\r\n                // Accumulate the payload for echo-verify, then type it paced-chunked.\r\n                let bytes = text.into_bytes();\r\n                sent_text.extend_from_slice(&bytes);\r\n                enqueue_text_chunked(input, bytes);\r\n            }\r\n            Ok(KeyCmd::Delay { delay_ms }) => {\r\n                let want = Duration::from_millis(delay_ms);\r\n                let cap = deadline.saturating_duration_since(Instant::now());\r\n                thread::sleep(want.min(cap));\r\n            }\r\n            Ok(KeyCmd::Commit { .. }) => {\r\n                committed = true;\r\n                break;\r\n            }\r\n            Err(RecvTimeoutError::Timeout) => break,\r\n            Err(RecvTimeoutError::Disconnected) => {\r\n                disconnected = true;\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    // Flush buffered controller input AFTER the injected bytes + release the floor —\r\n    // in EVERY exit path (the ANTI-STALL guarantee).\r\n    flush_inject_floor(floor, input);\r\n    if disconnected {\r\n        SeqOutcome::Disconnected\r\n    } else if committed {\r\n        SeqOutcome::Committed { sent_text }\r\n    } else {\r\n        SeqOutcome::Missed\r\n    }\r\n}\r\n\r\n// [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\n// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\r\n#[allow(clippy::too_many_arguments)] // per-session worker wiring (C-1 added the respawn counter, W5-A the output-log tap); a struct would obscure the call\r\nfn run_inject_worker(\r\n    input: Arc<InputWriter>,\r\n    child: Arc<TranslationChild>,\r\n    floor: Arc<Mutex<InjectFloor>>,\r\n    event_rx: Receiver<String>,\r\n    cmd_rx: Receiver<KeyCmd>,\r\n    faulted: Arc<AtomicBool>,\r\n    respawns: Arc<AtomicU32>,\r\n    log: Arc<Mutex<OutputLog>>,\r\n    endpoint: String,\r\n) {\r\n    // C-1 worker-local state (REQ-TRANSLATE-COMMIT-MISS-TOLERANCE): CONSECUTIVE\r\n    // commit-deadline misses (reset on any successful `{commit}`), and the set of\r\n    // envelopes already respooled-once so a repeat miss dead-letters instead of\r\n    // respooling forever. Both are worker-local: a REAL fault exits the worker, and\r\n    // a respawn builds a fresh worker with a fresh budget + set.\r\n    let mut strikes: u32 = 0;\r\n    let mut respooled_once: HashSet<String> = HashSet::new();\r\n    // W5-A Layer 1 (re-arm fix): settle before EVERY delivery, not once per worker. The\r\n    // head-swallow race is NOT startup-only — a mid-session `/clear` re-enters the\r\n    // harness's raw-mode input reader and re-creates the pre-settle window (field-proven\r\n    // on 0.29.0: a checkpoint-wake payload injected right after `/clear` lost its head\r\n    // even though the worker had long since settled at spawn). So an echoing/interactive\r\n    // PTY re-settles per delivery (cheap: a tick or two once the reader answers). The old\r\n    // one-shot's ONLY legitimate purpose was sparing a NON-echoing ConPTY the full\r\n    // settle-deadline on every delivery (its DSR answer never surfaces, and it has no\r\n    // reader-reattach race to guard). We keep exactly that optimization via a LATCH driven\r\n    // by the settle's own return value: a settle that TIMES OUT (probe unobservable) sets\r\n    // this, and thereafter first-attempt settles are skipped; a settle that is OBSERVED\r\n    // keeps it clear, so the bug-prone class re-settles forever.\r\n    let mut probe_unobservable = false;\r\n    // Layer 2 (echo-verify → re-drive → LOUD spool) is a DECLARED CAPABILITY, default\r\n    // OFF (`SPT_INJECT_VERIFY_ECHO`). It applies ONLY where the harness re-renders typed\r\n    // input into its observable output (a real TUI's input line does; a bare shell / a\r\n    // ConPTY programmatic inject / a test echo-child on ConPTY does NOT). Universal\r\n    // auto-detection is infeasible — a raw-mode TUI does not echo a benign control probe\r\n    // (so the probe cannot prove text-echo), and incidental output (a prompt, a redraw)\r\n    // would make a head-prefix check false-read head-loss and spuriously LOUD-spool EVERY\r\n    // delivery. So an adapter that KNOWS its harness echoes declares the capability\r\n    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\r\n    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\r\n    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\r\n    // AMBIENT env can NOT arm this: both vars are scrubbed at daemon startup\r\n    // (`spt_runtime::INJECT_ECHO_ENV_VARS`, F-036 env-inheritance class — an inherited\r\n    // dev-shell export once force-enabled echo-verify host-wide and a false miss RETYPED\r\n    // sequences into a live session's input). Only an explicit declaration (or an\r\n    // in-process test broker, which never runs the daemon entrypoints) reaches this read.\r\n    let echo_capable = std::env::var_os(\"SPT_INJECT_VERIFY_ECHO\").is_some()\r\n        || std::env::var_os(\"SPT_INJECT_FORCE_ECHO_MISS\").is_some();\r\n    // `event_rx` blocks here; events queue → one sequence at a time.\r\n    for envelope in event_rx {\r\n        // A well-behaved filter only emits in response to an event, but drop any\r\n        // unsolicited commands left in the channel so they can't be mis-attributed\r\n        // to this event's sequence.\r\n        while cmd_rx.try_recv().is_ok() {}\r\n\r\n        let envelope_copy = envelope.clone();\r\n\r\n        // ── W5-A: run the sequence with a settle-gate (Layer 1) before ANY byte, an\r\n        // echo-verify (Layer 2) after `{commit}`, and ONE re-drive on a verify miss →\r\n        // LOUD spool on a second miss (silent+unrecoverable head-loss → recoverable+loud).\r\n        // The commit-deadline MISS / binary-death / send-fail semantics below are\r\n        // unchanged (C-1 miss-tolerance).\r\n        let mut committed = false;\r\n        let mut disconnected = false;\r\n        let mut send_failed = false;\r\n        let mut integrity_lost = false;\r\n        for attempt in 1..=INJECT_REDRIVE_ATTEMPTS {\r\n            // Layer 1: settle-gate before the first byte of EVERY delivery on an echoing\r\n            // PTY (re-arm — a mid-session `/clear` re-creates the reader-reattach race),\r\n            // and always before a RE-DRIVE (only reached on a swallowed head, so re-confirm\r\n            // readiness before retyping). Skip only where the probe is known unobservable\r\n            // (a non-echoing ConPTY — no race to guard, and settling would burn the full\r\n            // deadline). The latch is driven by the FIRST-ATTEMPT settle's own outcome.\r\n            if should_settle(attempt, probe_unobservable) {\r\n                let observed = settle_before_inject(&input, &log, &endpoint);\r\n                if attempt == 1 {\r\n                    probe_unobservable = !observed;\r\n                }\r\n            }\r\n            // Snapshot the ring high-water AFTER settling (excludes probe echoes) so\r\n            // Layer 2 sees only THIS sequence's output.\r\n            let seq_start = recover_log(&log).high_water();\r\n            match drive_one_sequence(&input, &floor, &child, &cmd_rx, envelope_copy.clone()) {\r\n                SeqOutcome::SendFailed => {\r\n                    send_failed = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Disconnected => {\r\n                    disconnected = true;\r\n                    break;\r\n                }\r\n                SeqOutcome::Missed => {\r\n                    // Commit-deadline miss — leave to the C-1 miss path (not an\r\n                    // integrity re-drive; the binary never signalled a complete sequence).\r\n                    break;\r\n                }\r\n                SeqOutcome::Committed { sent_text } => {\r\n                    committed = true;\r\n                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\r\n                    // demonstrably echoes (else a non-echoing PTY's incidental output would\r\n                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\r\n                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\r\n                        break; // delivered byte-complete (or echo-verify not applicable here)\r\n                    }\r\n                    if attempt < INJECT_REDRIVE_ATTEMPTS {\r\n                        spt_proto::emit_line_err!(\r\n                            \"INJECT_ECHO_MISS:{endpoint}: typed head did not echo (attempt {attempt}/{INJECT_REDRIVE_ATTEMPTS}) — re-driving once\"\r\n                        );\r\n                        committed = false; // re-drive; only a verified commit counts\r\n                        continue;\r\n                    }\r\n                    // Second miss → the head is swallowed even after a re-drive.\r\n                    integrity_lost = true;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if send_failed {\r\n            // stdin write FAILED → the binary is genuinely GONE (a REAL fault): re-spool\r\n            // the in-flight envelope, stamp + terminate, exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"stdin write failed\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        if integrity_lost {\r\n            // Layer 2 fallback: the typed head never echoed, even after a re-drive.\r\n            // A live-SENT inject leaves no spool copy, so convert the silent+unrecoverable\r\n            // truncation into recoverable+loud — re-spool the envelope (idle-eligible,\r\n            // poll-fed) with a distinct integrity marker. NOT a binary fault (the binary\r\n            // committed fine; the terminal swallowed the head), so the worker survives.\r\n            respool_envelope(&endpoint, &envelope_copy);\r\n            spt_proto::emit_line_err!(\r\n                \"INJECT_HEAD_LOSS_SPOOL:{endpoint}: typed payload head did not echo after {INJECT_REDRIVE_ATTEMPTS} attempts — re-spooled loud (delivered=false, recoverable); frame integrity NOT confirmed\"\r\n            );\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if committed {\r\n            // Healthy sequence: reset the strike counter, refresh the respawn budget\r\n            // (a working binary heals it), and forget any earlier respool of THIS\r\n            // envelope (it succeeded → a future miss may respool it afresh).\r\n            strikes = 0;\r\n            respawns.store(0, Ordering::Relaxed);\r\n            respooled_once.remove(&envelope_copy);\r\n            continue;\r\n        }\r\n\r\n        if disconnected {\r\n            // REAL binary death (stdout closed mid-sequence): re-spool + stamp +\r\n            // terminate + exit for a bounded respawn.\r\n            respool_and_fault(\r\n                &endpoint,\r\n                envelope_copy,\r\n                &faulted,\r\n                &child,\r\n                \"binary stdout disconnected\",\r\n            );\r\n            return;\r\n        }\r\n\r\n        // ── COMMIT-DEADLINE MISS (miss ≠ fault, REQ-TRANSLATE-COMMIT-MISS-TOLERANCE).\r\n        // The stdin write SUCCEEDED so the binary is ALIVE; the watchdog already did\r\n        // its ONLY legitimate job (flushed + released the floor above). Do NOT\r\n        // terminate, do NOT latch `faulted`. The in-memory pending wake survives the\r\n        // boundary → the v0.12.0 checkpoint post-clear wake works as intended.\r\n        strikes += 1;\r\n        match note_miss_respool(&mut respooled_once, &envelope_copy) {\r\n            MissRespool::Respool => {\r\n                // First miss of this envelope — respool it ONCE (idle-eligible,\r\n                // poll-fed) without faulting.\r\n                respool_envelope(&endpoint, &envelope_copy);\r\n                spt_proto::emit_line_err!(\r\n                    \"TRANSLATION_COMMIT_MISS:{endpoint}: no {{commit}} within deadline (strike {strikes}/{}) — binary preserved, envelope respooled once\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n            MissRespool::DeadLetter => {\r\n                // A repeat miss of an already-respooled envelope (e.g. the checkpoint\r\n                // duplicate FIRE looping back on a later drain): DEAD-LETTER it loud —\r\n                // do NOT respool again (never a silent drop; it still counts as a\r\n                // strike), REQ-HAZARD-IDLE-SILENT-NONDELIVERY posture.\r\n                spt_proto::emit_line_err!(\r\n                    \"TRANSLATION_COMMIT_MISS_DEADLETTER:{endpoint}: envelope missed {{commit}} again after a prior respool (strike {strikes}/{}) — dead-lettered, NOT respooled again (reported non-delivery, not a silent drop)\",\r\n                    inject_miss_strike_budget()\r\n                );\r\n            }\r\n        }\r\n\r\n        if miss_strikes_exhausted(strikes, inject_miss_strike_budget()) {\r\n            // N CONSECUTIVE misses with no intervening commit → a REAL fault: the\r\n            // binary is wedged. Stamp + terminate + exit for a bounded respawn. (The\r\n            // envelope was already respooled/dead-lettered above — no double respool.)\r\n            fault_translation_stamped(\r\n                &endpoint,\r\n                &faulted,\r\n                &child,\r\n                \"no {commit} within INJECT_COMMIT_DEADLINE for the strike budget\",\r\n            );\r\n            return;\r\n        }\r\n        // Miss tolerated (below the budget): the binary survives — continue to the\r\n        // next queued event through the SAME live binary.\r\n    }\r\n    // `event_tx` dropped → session teardown. Reap the child (no zombie).\r\n    child.terminate();\r\n}\r\n\r\n/// Re-spool the in-flight envelope as non-deferred (idle-eligible) so the\r\n/// relay/poll can surface it. Used on a commit-deadline MISS (no fault) and,\r\n/// composed with the fault, on a REAL binary-death fault.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\n// [impl->REQ-TRANSLATE-COMMIT-MISS-TOLERANCE]\r\nfn respool_envelope(endpoint: &str, envelope: &str) {\r\n    let perch_path =\r\n        spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);\r\n    if let Err(e) = spt_store::spool::spool_message_at(&perch_path, \"\", envelope) {\r\n        spt_proto::emit_line_err!(\r\n            \"TRANSLATION_FAULT_RESPOOL:{endpoint}: failed to re-spool ({e}) — message dropped\"\r\n        );\r\n    }\r\n}\r\n\r\n/// Re-spool the in-flight envelope (a REAL binary-death fault genuinely lost the\r\n/// in-flight delivery), then stamp the perch + fault the translation binary.\r\n// [impl->REQ-MSG-DELIVERY-AXES]\r\nfn respool_and_fault(\r\n    endpoint: &str,\r\n    envelope: String,\r\n    faulted: &AtomicBool,\r\n    child: &TranslationChild,\r\n    why: &str,\r\n) {\r\n    respool_envelope(endpoint, &envelope);\r\n    fault_translation_stamped(endpoint, faulted, child, why);\r\n}\r\n\r\n/// Stamp the perch's harness-reachable translation-fault surface (F029 C-1,\r\n/// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH). Off-lock file I/O — safe from the\r\n/// worker thread (no log lock held). Best-effort: a stamp failure never blocks the\r\n/// fault (the stderr line still fires).\r\nfn stamp_translation_fault(endpoint: &str, why: &str) {\r\n…\n}","startLine":4569,"lineNumbers":[4569,4570,4571,4572,4573,4574,4575,4576,4577,4578,4579,4580,4581,4582,4583,4584,4585,4586,4587,4588,4589,4590,4591,4592,4593,4594,4595,4596,4597,4598,4599,4600,4601,4602,4603,4604,4605,4606,4607,4608,4609,4610,4611,4612,4613,4614,4615,4616,4617,4618,4619,4620,4621,4622,4623,4624,4625,4626,4627,4628,4629,4630,4631,4632,4633,4634,4635,4636,4637,4638,4639,4640,4641,4642,4643,4644,4645,4646,4647,4648,4649,4650,4651,4652,4653,4654,4655,4656,4657,4658,4659,4660,4661,4662,4663,4664,4665,4666,4667,4668,4669,4670,4671,4672,4673,4674,4675,4676,4677,4678,4679,4680,4681,4682,4683,4684,4685,4686,4687,4688,4689,4690,4691,4692,4693,4694,4695,4696,4697,4698,4699,4700,4701,4702,4703,4704,4705,4706,4707,4708,4709,4710,4711,4712,4713,4714,4715,4716,4717,4718,4719,4720,4721,4722,4723,4724,4725,4726,4727,4728,4729,4730,4731,4732,4733,4734,4735,4736,4737,4738,4739,4740,4741,4742,4743,4744,4745,4746,4747,4748,4749,4750,4751,4752,4753,4754,4755,4756,4757,4758,4759,4760,4761,4762,4763,4764,4765,4766,4767,4768,4769,4770,4771,4772,4773,4774,4775,4776,4777,4778,4779,4780,4781,4782,4783,4784,4785,4786,4787,4788,4789,4790,4791,4792,4793,4794,4795,4796,4797,4798,4799,4800,4801,4802,4803,4804,4805,4806,4807,4808,4809,4810,4811,4812,4813,4814,4815,4816,4817,4818,4819,4820,4821,4822,4823,4824,4825,4826,4827,4828,4829,4830,4831,4832,4833,4834,4835,4836,4837,4838,4839,4840,4841,4842,4843,4844,4845,4846,4847,4848,4849,4850,4851,4852,4853,4854,4855,4856,4857,4858,4859,4860,4861,4862,4863,4864,4865,4866,4867,4868,4869,4870,4871,4872,4873,4874,4875,4876,4877,4878,4879,4880,4881,4882,4883,4884,4885,4886,4887,4888,4889,4890,4891,4892,4893,4894,4895,4896,4897,4898,4899,4900,4901,4902,4903,4904,4905,4906,4907,4908,4909,4910,4911,4912,4913,4914,4915,4916,4917,4918,4919,4920,4921,4922,4923,4924,4925,4926,4927,4928,4929,4930,4931,4932,4933,4934,4935,4936,4937,4938,4939,4940,4941,4942,4943,4944,4945,4946,4947,4948,4949,4950,4951,4952,4953,4954,4955,4956,4957,4958,4959,4960,4961,4962,4963,4964,4965,4966,4967,4968,4969,4970,4971,4972,4973,4974,4975,4976,4977,4978,4979,4980,4981,4982,4983,4984,4985,4986,4987,4988,4989,4990,4991,4992,4993,4994,4995,4996,4997,4998,4999,5000,5001,5002,5003,5004,5005,5006,5007,5008,5009,5010,5011,5012,5013,5014,5015,5016,5017,5018,5019,5020,5021,5022,5023,5024,5025,5026,5027,5028,5029,5030,5031,5032,5033,5034,5035,5036,5037,5038,5039,5040,5041,5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,5056,5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,5113,5114,5115,5116,5117,5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149,5150,5151,5152,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164,5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,5176,5177,5178,5179,5180,5181,5182,5183,5184,5185,5186,5187,5188,5189,5190,5191,5192,5193,5194,5195,5196,5197,5198,5199,5200,5201,5202,5203,null,5207]},"fileSize":857503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":16479,"totalBytes":33363,"outputLines":635,"outputBytes":33363,"shownRange":{"start":4569,"end":5203},"nextOffset":5204}}},"isError":false,"timestamp":1789295280997}}
{"type":"message","id":"968aacf1","parentId":"8d07838c","timestamp":"2026-09-13T10:28:01.001Z","message":{"role":"toolResult","toolCallId":"call_UAUprIcXUPLc8oLAbvLgER1T|fc_07ba52f17df332c2016aa67ab0c5cc87d2a7e7f89e048289fd","toolName":"grep","content":[{"type":"text","text":" 776|/// Pure echo-verify decision (Layer 2, REQ-INJECT-MULTILINE-INTEGRITY): did the\n*777|/// typed payload's HEAD echo back? Compare a bounded prefix of `sent_text` against\n 778|/// the PTY echo captured since the sequence began. A TUI re-renders typed text so the\n 779|/// leading bytes appear in output; a swallowed head means the prefix is ABSENT. An\n 780|/// empty payload (a keys-only sequence — no echoable text) is vacuously verified.\n*781|fn echo_verified(sent_text: &[u8], echoed: &[u8]) -> bool {\n*782|    if sent_text.is_empty() {\n 783|        return true;\n 784|    }\n*785|    let n = sent_text.len().min(ECHO_VERIFY_PREFIX);\n*786|    contains_subslice(echoed, &sent_text[..n])\n 787|}\n 788|\n 789|/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n...\n 1616|    /// lock — the BROKER'S SEAT TABLE, never the perch info stamp (the stamp\n*1617|    /// lags the seat decision: KH 7.15 / info.rs terminal_normalize, and\n 1618|    /// `SessionInfo.controller_by == None` is ambiguous between empty and\n 1619|    /// local). `None` = no controller at all. The `SharedSend` rides along for\n 1620|    /// the bounded direct-write degrade the exit fan-out uses.\n...\n 4592|            Some(bytes) => {\n*4593|                input.enqueue(bytes);\n 4594|            }\n 4595|            None => break,\n 4596|        }\n...\n 4768|        // as before; a non-echoing ConPTY stays unobservable (the latch).\n*4769|        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\n 4770|        thread::sleep(INJECT_SETTLE_POLL);\n 4771|        if recover_log(log).high_water() > baseline {\n 4772|            return true; // output produced since baseline → the input reader is live\n...\n 4811|// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n*4812|fn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\n*4813|    if sent_text.is_empty() {\n 4814|        return true;\n 4815|    }\n 4816|    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\n...\n 4824|        let echoed = recover_log(log).bytes_since(seq_start);\n*4825|        if echo_verified(sent_text, &echoed) {\n 4826|            return true;\n 4827|        }\n 4828|        if Instant::now() >= deadline {\n...\n 4844|    chunk_text(&bytes, inject_text_chunk(), |part| {\n*4845|        input.enqueue(part.to_vec());\n 4846|    });\n 4847|}\n 4848|\n...\n 4865|enum SeqOutcome {\n*4866|    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\n 4867|    /// concatenated payload text typed (keys excluded — they carry no echoable text).\n*4868|    Committed { sent_text: Vec<u8> },\n 4869|    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\n 4870|    Missed,\n 4871|    /// The binary's stdout closed mid-sequence (a REAL death fault).\n...\n 4898|    let mut disconnected = false;\n*4899|    let mut sent_text: Vec<u8> = Vec::new();\n 4900|    loop {\n 4901|        let now = Instant::now();\n 4902|        let remaining = deadline.saturating_duration_since(now);\n...\n 4908|                if let Some(bytes) = key_to_bytes(&key) {\n*4909|                    input.enqueue(bytes);\n 4910|                }\n 4911|            }\n 4912|            Ok(KeyCmd::Text { text }) => {\n...\n 4914|                let bytes = text.into_bytes();\n*4915|                sent_text.extend_from_slice(&bytes);\n 4916|                enqueue_text_chunked(input, bytes);\n 4917|            }\n 4918|            Ok(KeyCmd::Delay { delay_ms }) => {\n...\n 4939|    } else if committed {\n*4940|        SeqOutcome::Committed { sent_text }\n 4941|    } else {\n 4942|        SeqOutcome::Missed\n 4943|    }\n...\n 5048|                }\n*5049|                SeqOutcome::Committed { sent_text } => {\n 5050|                    committed = true;\n 5051|                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\n 5052|                    // demonstrably echoes (else a non-echoing PTY's incidental output would\n 5053|                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\n*5054|                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\n 5055|                        break; // delivered byte-complete (or echo-verify not applicable here)\n 5056|                    }\n 5057|                    if attempt < INJECT_REDRIVE_ATTEMPTS {\n...\n 8817|                        if !buffered(&bytes) {\n*8818|                            input.enqueue(bytes.clone());\n 8819|                        }\n 8820|                        Ok(())\n 8821|                    })\n...\n 8842|                if !buffered(&bytes) {\n*8843|                    input.enqueue(bytes);\n 8844|                }\n 8845|                Ok(())\n 8846|            }\n...\n 11071|        for rec in &inputs {\n*11072|            assert!(w.enqueue(rec.clone()), \"depth 256: every enqueue is accepted\");\n 11073|        }\n 11074|        // Close the FIFO so the drain loop terminates, then drain through the SOLE\n 11075|        // writer exactly as `input_writer` does.\n...\n 11108|        for i in 0..DEPTH {\n*11109|            assert!(w.enqueue(vec![i as u8]), \"enqueue {i} fits within the bound\");\n 11110|        }\n 11111|        assert!(\n 11112|            !w.backpressure.load(Ordering::Acquire),\n...\n 11118|        assert!(\n*11119|            !w.enqueue(b\"OVERFLOW\".to_vec()),\n 11120|            \"an enqueue into a full FIFO is DROPPED (returns false) — never blocks \\\n 11121|             the dispatch thread (the wedge cure)\"\n 11122|        );\n...\n 11128|        // stamp is rising-edge-only, but the STATE remains true — no flap to false).\n*11129|        assert!(!w.enqueue(b\"OVERFLOW-2\".to_vec()), \"still dropping while full\");\n 11130|        assert!(\n 11131|            w.backpressure.load(Ordering::Acquire),\n 11132|            \"backpressure stays asserted while the queue remains saturated\"\n...\n 11138|        assert!(\n*11139|            w.enqueue(b\"AFTER-DRAIN\".to_vec()),\n 11140|            \"with a freed slot the next enqueue is ACCEPTED again\"\n 11141|        );\n 11142|        assert!(\n...\n 11385|            crate::msg::KIND_SIZE,\n*11386|            \"the viewer's first post-commit frame is the size frame, never raw suppressed output\"\n 11387|        );\n 11388|        let ev: crate::msg::SizeEvent = serde_json::from_value(f.payload).unwrap();\n 11389|        assert_eq!((ev.rows, ev.cols), (4, 10));\n...\n 11594|    // world is the pre-barrier code, where no sync frame exists at all and the\n*11595|    // first post-commit viewer frame is raw output; they hold the disposition\n 11596|    // table against regression.)\n 11597|    #[test]\n 11598|    fn tracked_state_inside_the_window_surfaces_in_the_sync_frame() {\n...\n 11697|    // NOT evicted by a suppressed append, and (b) is NOT evicted by the first\n*11698|    // post-commit append either — `last_ok` resets at commit. RED-FIRST on the\n 11699|    // shipped code: the suppressed append hands off + evaluates the deadline and\n 11700|    // returns Some(epoch) — a spurious evict signal fired from inside the window.\n 11701|    #[test]\n...\n 11740|        assert_eq!(\n*11741|            log.append(b\"first post-commit chunk\"),\n 11742|            None,\n*11743|            \"last_ok reset at commit: the first post-commit Full is a fresh drop, not an evict\"\n 11744|        );\n 11745|        log.controller = None; // cleanup: tx drops, the parked writer exits\n 11746|    }\n...\n 12137|            .apply_once(key, EffectKind::PtyWrite, || {\n*12138|                w.enqueue(bytes.clone());\n 12139|                Ok(())\n 12140|            })\n 12141|            .unwrap();\n...\n 12145|            .apply_once(key, EffectKind::PtyWrite, || {\n*12146|                w.enqueue(bytes.clone());\n 12147|                Ok(())\n 12148|            })\n 12149|            .unwrap();\n...\n 12787|\n*12788|    // [unit->REQ-DRIVEN-BY-OWN-NODE-NORMALIZE] own-node latch is TRUTHFUL (doyle\n 12789|    // ruling v3, 2026-07-19): a same-node controller — routed through the net path\n 12790|    // by W1's canonical_wire_id work — legitimately carries OUR OWN node hex, and\n 12791|    // `driven_by` = the controlling node, own node INCLUDED (CONTEXT.md:386)."}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":35,"fileCount":1,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":35}],"truncated":false,"displayContent":"   776│/// Pure echo-verify decision (Layer 2, REQ-INJECT-MULTILINE-INTEGRITY): did the\n  *777│/// typed payload's HEAD echo back? Compare a bounded prefix of `sent_text` against\n   778│/// the PTY echo captured since the sequence began. A TUI re-renders typed text so the\n   779│/// leading bytes appear in output; a swallowed head means the prefix is ABSENT. An\n   780│/// empty payload (a keys-only sequence — no echoable text) is vacuously verified.\n  *781│fn echo_verified(sent_text: &[u8], echoed: &[u8]) -> bool {\n  *782│    if sent_text.is_empty() {\n   783│        return true;\n   784│    }\n  *785│    let n = sent_text.len().min(ECHO_VERIFY_PREFIX);\n  *786│    contains_subslice(echoed, &sent_text[..n])\n   787│}\n   788│\n   789│/// The pure respool-once / dead-letter decision for a commit-deadline MISS (F029\n      │...\n  1616│    /// lock — the BROKER'S SEAT TABLE, never the perch info stamp (the stamp\n *1617│    /// lags the seat decision: KH 7.15 / info.rs terminal_normalize, and\n  1618│    /// `SessionInfo.controller_by == None` is ambiguous between empty and\n  1619│    /// local). `None` = no controller at all. The `SharedSend` rides along for\n  1620│    /// the bounded direct-write degrade the exit fan-out uses.\n      │...\n  4592│            Some(bytes) => {\n *4593│                input.enqueue(bytes);\n  4594│            }\n  4595│            None => break,\n  4596│        }\n      │...\n  4768│        // as before; a non-echoing ConPTY stays unobservable (the latch).\n *4769│        input.enqueue(INJECT_SETTLE_PROBE.to_vec());\n  4770│        thread::sleep(INJECT_SETTLE_POLL);\n  4771│        if recover_log(log).high_water() > baseline {\n  4772│            return true; // output produced since baseline → the input reader is live\n      │...\n  4811│// [impl->REQ-INJECT-MULTILINE-INTEGRITY]\n *4812│fn echo_verify_after(log: &Mutex<OutputLog>, seq_start: u64, sent_text: &[u8]) -> bool {\n *4813│    if sent_text.is_empty() {\n  4814│        return true;\n  4815│    }\n  4816│    // Test seam (int gate only): force a verify MISS to exercise the re-drive + LOUD-\n      │...\n  4824│        let echoed = recover_log(log).bytes_since(seq_start);\n *4825│        if echo_verified(sent_text, &echoed) {\n  4826│            return true;\n  4827│        }\n  4828│        if Instant::now() >= deadline {\n      │...\n  4844│    chunk_text(&bytes, inject_text_chunk(), |part| {\n *4845│        input.enqueue(part.to_vec());\n  4846│    });\n  4847│}\n  4848│\n      │...\n  4865│enum SeqOutcome {\n *4866│    /// The binary emitted `{commit}` — a complete sequence. `sent_text` is the\n  4867│    /// concatenated payload text typed (keys excluded — they carry no echoable text).\n *4868│    Committed { sent_text: Vec<u8> },\n  4869│    /// The commit deadline elapsed with the binary still ALIVE (a MISS, not a fault).\n  4870│    Missed,\n  4871│    /// The binary's stdout closed mid-sequence (a REAL death fault).\n      │...\n  4898│    let mut disconnected = false;\n *4899│    let mut sent_text: Vec<u8> = Vec::new();\n  4900│    loop {\n  4901│        let now = Instant::now();\n  4902│        let remaining = deadline.saturating_duration_since(now);\n      │...\n  4908│                if let Some(bytes) = key_to_bytes(&key) {\n *4909│                    input.enqueue(bytes);\n  4910│                }\n  4911│            }\n  4912│            Ok(KeyCmd::Text { text }) => {\n      │...\n  4914│                let bytes = text.into_bytes();\n *4915│                sent_text.extend_from_slice(&bytes);\n  4916│                enqueue_text_chunked(input, bytes);\n  4917│            }\n  4918│            Ok(KeyCmd::Delay { delay_ms }) => {\n      │...\n  4939│    } else if committed {\n *4940│        SeqOutcome::Committed { sent_text }\n  4941│    } else {\n  4942│        SeqOutcome::Missed\n  4943│    }\n      │...\n  5048│                }\n *5049│                SeqOutcome::Committed { sent_text } => {\n  5050│                    committed = true;\n  5051│                    // Layer 2: verify the typed head echoed back — ONLY where the terminal\n  5052│                    // demonstrably echoes (else a non-echoing PTY's incidental output would\n  5053│                    // false-read head-loss). Where it does not echo, Layer 1 stands alone.\n *5054│                    if !echo_capable || echo_verify_after(&log, seq_start, &sent_text) {\n  5055│                        break; // delivered byte-complete (or echo-verify not applicable here)\n  5056│                    }\n  5057│                    if attempt < INJECT_REDRIVE_ATTEMPTS {\n      │...\n  8817│                        if !buffered(&bytes) {\n *8818│                            input.enqueue(bytes.clone());\n  8819│                        }\n  8820│                        Ok(())\n  8821│                    })\n      │...\n  8842│                if !buffered(&bytes) {\n *8843│                    input.enqueue(bytes);\n  8844│                }\n  8845│                Ok(())\n  8846│            }\n      │...\n 11071│        for rec in &inputs {\n*11072│            assert!(w.enqueue(rec.clone()), \"depth 256: every enqueue is accepted\");\n 11073│        }\n 11074│        // Close the FIFO so the drain loop terminates, then drain through the SOLE\n 11075│        // writer exactly as `input_writer` does.\n      │...\n 11108│        for i in 0..DEPTH {\n*11109│            assert!(w.enqueue(vec![i as u8]), \"enqueue {i} fits within the bound\");\n 11110│        }\n 11111│        assert!(\n 11112│            !w.backpressure.load(Ordering::Acquire),\n      │...\n 11118│        assert!(\n*11119│            !w.enqueue(b\"OVERFLOW\".to_vec()),\n 11120│            \"an enqueue into a full FIFO is DROPPED (returns false) — never blocks \\\n 11121│             the dispatch thread (the wedge cure)\"\n 11122│        );\n      │...\n 11128│        // stamp is rising-edge-only, but the STATE remains true — no flap to false).\n*11129│        assert!(!w.enqueue(b\"OVERFLOW-2\".to_vec()), \"still dropping while full\");\n 11130│        assert!(\n 11131│            w.backpressure.load(Ordering::Acquire),\n 11132│            \"backpressure stays asserted while the queue remains saturated\"\n      │...\n 11138│        assert!(\n*11139│            w.enqueue(b\"AFTER-DRAIN\".to_vec()),\n 11140│            \"with a freed slot the next enqueue is ACCEPTED again\"\n 11141│        );\n 11142│        assert!(\n      │...\n 11385│            crate::msg::KIND_SIZE,\n*11386│            \"the viewer's first post-commit frame is the size frame, never raw suppressed output\"\n 11387│        );\n 11388│        let ev: crate::msg::SizeEvent = serde_json::from_value(f.payload).unwrap();\n 11389│        assert_eq!((ev.rows, ev.cols), (4, 10));\n      │...\n 11594│    // world is the pre-barrier code, where no sync frame exists at all and the\n*11595│    // first post-commit viewer frame is raw output; they hold the disposition\n 11596│    // table against regression.)\n 11597│    #[test]\n 11598│    fn tracked_state_inside_the_window_surfaces_in_the_sync_frame() {\n      │...\n 11697│    // NOT evicted by a suppressed append, and (b) is NOT evicted by the first\n*11698│    // post-commit append either — `last_ok` resets at commit. RED-FIRST on the\n 11699│    // shipped code: the suppressed append hands off + evaluates the deadline and\n 11700│    // returns Some(epoch) — a spurious evict signal fired from inside the window.\n 11701│    #[test]\n      │...\n 11740│        assert_eq!(\n*11741│            log.append(b\"first post-commit chunk\"),\n 11742│            None,\n*11743│            \"last_ok reset at commit: the first post-commit Full is a fresh drop, not an evict\"\n 11744│        );\n 11745│        log.controller = None; // cleanup: tx drops, the parked writer exits\n 11746│    }\n      │...\n 12137│            .apply_once(key, EffectKind::PtyWrite, || {\n*12138│                w.enqueue(bytes.clone());\n 12139│                Ok(())\n 12140│            })\n 12141│            .unwrap();\n      │...\n 12145│            .apply_once(key, EffectKind::PtyWrite, || {\n*12146│                w.enqueue(bytes.clone());\n 12147│                Ok(())\n 12148│            })\n 12149│            .unwrap();\n      │...\n 12787│\n*12788│    // [unit->REQ-DRIVEN-BY-OWN-NODE-NORMALIZE] own-node latch is TRUTHFUL (doyle\n 12789│    // ruling v3, 2026-07-19): a same-node controller — routed through the net path\n 12790│    // by W1's canonical_wire_id work — legitimately carries OUR OWN node hex, and\n 12791│    // `driven_by` = the controlling node, own node INCLUDED (CONTEXT.md:386)."},"isError":false,"timestamp":1789295281000}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_v0nts1Wf3dxflDURjL33AmHJ|fc_07ba52f17df332c2016aa67ab9914487d2b6b3bd0a5ce6c135","toolName":"read","startedAt":"2026-09-13T10:28:09.669Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8746-8852"},"intent":"Reading controller custody locking"},"id":"2875fafa","parentId":"968aacf1","timestamp":"2026-09-13T10:28:09.669Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hlcfz5IT81843cvt9v4aBOLf|fc_07ba52f17df332c2016aa67ab9915487d28fcd4f1b887d7d27","toolName":"grep","startedAt":"2026-09-13T10:28:09.669Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src;.worktrees/300-input-acceptance/crates/spt-pty/src"},"intent":"Finding alternative delivery paths"},"id":"6e823fb8","parentId":"2875fafa","timestamp":"2026-09-13T10:28:09.669Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6p1aVHjqpZLzx8KkvnqQT9Ht|fc_07ba52f17df332c2016aa67ab9915c87d2812c3fda4f272570","toolName":"read","startedAt":"2026-09-13T10:28:09.670Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8304-8356"},"intent":"Reading writer construction dependencies"},"id":"5ab155f1","parentId":"6e823fb8","timestamp":"2026-09-13T10:28:09.670Z"}
{"type":"message","id":"a7a45cd3","parentId":"5ab155f1","timestamp":"2026-09-13T10:28:09.670Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Checking the target branch**"},{"type":"toolCall","id":"call_v0nts1Wf3dxflDURjL33AmHJ|fc_07ba52f17df332c2016aa67ab9914487d2b6b3bd0a5ce6c135","name":"read","arguments":{"i":"Reading controller custody locking","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8746-8852"},"intent":"Reading controller custody locking"},{"type":"toolCall","id":"call_hlcfz5IT81843cvt9v4aBOLf|fc_07ba52f17df332c2016aa67ab9915487d28fcd4f1b887d7d27","name":"grep","arguments":{"i":"Finding alternative delivery paths","pattern":"typeahead|post_commit|post-commit|bracketed|normalize.*deliver|enum.*Write|TranslationEffect|KeyCmd|write_input\\(","path":".worktrees/300-input-acceptance/crates/spt-daemon/src;.worktrees/300-input-acceptance/crates/spt-pty/src","case":false,"gitignore":false,"skip":null},"intent":"Finding alternative delivery paths"},{"type":"toolCall","id":"call_6p1aVHjqpZLzx8KkvnqQT9Ht|fc_07ba52f17df332c2016aa67ab9915c87d2812c3fda4f272570","name":"read","arguments":{"i":"Reading writer construction dependencies","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8304-8356"},"intent":"Reading writer construction dependencies"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":14629,"output":235,"cacheRead":22016,"cacheWrite":0,"totalTokens":36880,"reasoningTokens":26,"cost":{"input":0.14629,"output":0.01175,"cacheRead":0.022015999999999997,"cacheWrite":0,"total":0.18005600000000002}},"stopReason":"toolUse","timestamp":1789295281031,"responseId":"resp_07ba52f17df332c2016aa67ab143e087d2beddddb1a6cc4994","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_07ba52f17df332c2016aa67ab28d0c87d293a26b6ec20bda0c","type":"reasoning","content":[],"encrypted_content":"gAAAAABqpnq5wX91-2jiOhACuaCpDNUd7QSD6BouRd4wEoJpDRJpEzJDwe6eYfLNy49l0YFF7MC4ifUnEM01CE0lIdbmI1emNl0GNP_wQc7jWINqhNtGdlKB-Ho1jOewWM2JDFodO7OW-jChV_-LmjUzAel9rzxxNux9hsm1Lx21WA7KJB3DLs2VHByLc9WefiE38APhgDhJyxVEET0-dhhHnHFzUvb25xGdJDqyT8ivFJa174Y1tva4T2iYlehRp40eBx8fdxsXDoA2Ba-QPd1oiWo-p8ec18vlpw6Djy9jrDgpU1u_8TgB8A4YXs9QDkDX6-5_Q4fOAnAG_HsXxqrei6Kf-gS5m9xS3xt4DPtKiixOok6UOgrcJz7G0ROnJxMcy3IjTVT-f7tHKq0a4e8ipY64uuuRxWVRrb1VJ_CZ7R35xUk8vascrCApYWSrfjnb8g58uMsPNZc1hgocJZdS2uLmXNzxIDle1m1V0HzHDkvb5Wz3LDHOGIgoPs6Z-BHSKu-RnJ_vybqYQ4iyChVRvW6rmxm_Zp2hGX3kE2teBVJcZ1I9dRQXCc2Eu7ytrsFy_aweM9ivx7kezpo8hwaaeWzcijW99oIetS-q0apawVwoO6mmyB8om_n5LKIq1Se4vr1Hwcog3wlGPXo1qjxiN4kdIeN8KNpA5W6_NyK_vAOo-vgWbVZkHkAKibiNmeLlY1WSd3j_0arzvDAXtnCH1fpukbtbF6goMSLPGTsXtJDHSwtMsRBnA2Ayu9A3R0_-IUMJB0qKY_qePIWOhQhqbDWj9ppRB1fHZqzFiG9JxrY8U4aJO7AX_19uxLKAGmQcI2AS0hKHGF6GqL_12Z_Xua2ec1NOS1Jg9nQhVQFxZmxoqZCURbEONZwWF4T9He7yxqEvFG_0e23A1U1cGAq4gSjGsBl0YiKk6VQGpvIG0h1xuf93LbHppR81W1_PnKp9mPLFdqfJr4WseMsG0mgQGXy3GmxhyXtesXVB0Hz7J0jm0JyIykJU8dp-X7jqlApFkLwhiq76zFsUE-z9W0D_yhCDo0SuKo7AkA6ddHIAMqMgfaUO7OqrexrzlfKEGrkqOFJGxO-v_7byFgNsXwkK_apjcrDI8PdQ1u3sJBWeIMBz-5CkKblLfEyxWL7xTw3OqFHY4NBwNyM0WLIuEjlR3R8YIfgzFiz8ky4APpoWl66WjpGR36CykslvpcLJhptjVolLKMkl7lIDg_ecYGfB0FHfZcD9ZY5JsJgk5GG3q7CKr4ZdUtvzCqVNiSQQEVErZAmVO0CwuTkgYw48aXPxR6BDKIjvWhXwvSkPszA-YZtgyfZcwNG2u7bInUp4R34xACwllJciVMLblwAIVi-x8YgYWuEoqpoPId2MH5Da6UbYrfYzhxd5rjfKeYrP_idrmEQYP8u5B5E4UTbunSSBdrmsOExX7MOr5obOBGjW32ESH2shbmuOcYCDempdHt13fiezuD4G","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"summary":[{"type":"summary_text","text":"**Checking the target branch**"}],"metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab9914487d2b6b3bd0a5ce6c135","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading controller custody locking\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8746-8852\"}","call_id":"call_v0nts1Wf3dxflDURjL33AmHJ","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab9915487d28fcd4f1b887d7d27","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding alternative delivery paths\",\"pattern\":\"typeahead|post_commit|post-commit|bracketed|normalize.*deliver|enum.*Write|TranslationEffect|KeyCmd|write_input\\\\(\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src;.worktrees/300-input-acceptance/crates/spt-pty/src\",\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_hlcfz5IT81843cvt9v4aBOLf","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ab9915c87d2812c3fda4f272570","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading writer construction dependencies\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:8304-8356\"}","call_id":"call_6p1aVHjqpZLzx8KkvnqQT9Ht","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":8623.519299999985,"ttft":1602.5412999999826,"completedAt":1789295289664,"contextSnapshot":{"promptTokens":36645,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"60c356f8","parentId":"a7a45cd3","timestamp":"2026-09-13T10:28:09.695Z","message":{"role":"toolResult","toolCallId":"call_6p1aVHjqpZLzx8KkvnqQT9Ht|fc_07ba52f17df332c2016aa67ab9915c87d2812c3fda4f272570","toolName":"read","content":[{"type":"text","text":"        let session = Arc::new(session);\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\n        let pid = session.process_id();\n        // Birth identity is trustworthy here: Windows retains the child handle;\n        // Unix cannot recycle our child's pid until it is reaped, and no waiter\n        // has been started yet. Exec may change its image, not its birth stamp.\n        // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\n        let process_started_at = pid.and_then(spt_store::proc::process_started_at);\n        // The ConPTY-create span releases#199 candidate C names, closed.\n        spawn_phase(\"pty_created\", &format!(\"pid={pid:?}\"));\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        // releases#199: the span from here to `row_inserted` is the one a 10-way\n        // contention run put 1427ms into while the ConPTY create took 14ms — the\n        // child is ALREADY RUNNING through all of it and the row the bring-up poll\n        // reads does not exist yet, which is this issue's face in miniature. So it\n        // is stamped per step rather than as one block.\n        spawn_phase(\"input_writer\", \"\");\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        spawn_phase(\"log_ready\", \"\");\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        let log_drain = Arc::clone(&log);\n        // DSR cursor authority (REQ-DSR-SINGLE-CPR): the drain answers each DSR\n        // with the grid's real display-column cursor. The provider runs on the\n        // drain thread BETWEEN sink handoffs (the append lock is released), and\n        // the grid is advanced by that same thread, so the reported position is\n        // exact at the query point.\n        let log_cursor = Arc::clone(&log);\n        let drain = session\n            .drain_with_cursor(\n                move |chunk| {\n                    // OBS RIDER Tap A (debug-gated, default OFF): the raw pre-parse\n…\n                },\n…\n            )\n            .map_err(|e| format!(\"drain failed: {e}\"))?;\n\n[Showing lines 8303-8359 of 16479. Use :8360 to continue]"}],"details":{"truncation":{"content":"        let session = Arc::new(session);\r\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\r\n        let pid = session.process_id();\r\n        // Birth identity is trustworthy here: Windows retains the child handle;\r\n        // Unix cannot recycle our child's pid until it is reaped, and no waiter\r\n        // has been started yet. Exec may change its image, not its birth stamp.\r\n        // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\r\n        let process_started_at = pid.and_then(spt_store::proc::process_started_at);\r\n        // The ConPTY-create span releases#199 candidate C names, closed.\r\n        spawn_phase(\"pty_created\", &format!(\"pid={pid:?}\"));\r\n\r\n        // The session's lone PTY input-writer (P0): the SOLE caller of the\r\n        // blocking `write_input`. Every dispatch-side write enqueues here, so a\r\n        // paste burst parks only this thread — never the broker dispatch thread\r\n        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\r\n        // too, so the lone writer enforces the W2 inject-floor order.\r\n        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());\r\n        // releases#199: the span from here to `row_inserted` is the one a 10-way\r\n        // contention run put 1427ms into while the ConPTY create took 14ms — the\r\n        // child is ALREADY RUNNING through all of it and the row the bring-up poll\r\n        // reads does not exist yet, which is this issue's face in miniature. So it\r\n        // is stamped per step rather than as one block.\r\n        spawn_phase(\"input_writer\", \"\");\r\n\r\n        // The output log, with the spawning connection pre-attached as the\r\n        // controller (identity `None` = LOCAL, not a remote driver) so no chunk\r\n        // is missed before an explicit subscribe — and so a remote attach to a\r\n        // spawned-but-undriven session takes control silently (no loud displace\r\n        // of the local-spawner placeholder; REQ-RCVIEW-1).\r\n        let log = Arc::new(Mutex::new(OutputLog::new(\r\n            id,\r\n            self.log_cap_chunks,\r\n            req.endpoint.clone(),\r\n            (req.rows, req.cols),\r\n        )));\r\n        log.lock()\r\n            .unwrap()\r\n            .become_controller(Arc::clone(send), None, 0, 0);\r\n        spawn_phase(\"log_ready\", \"\");\r\n\r\n        // Output drain → append under the log lock (fast: ring + viewer fan-out),\r\n        // then deliver to the controller OFF the lock with a bounded deadline\r\n        // (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no\r\n        // longer inline under the log lock, so a backed-up controller can never\r\n        // wedge the session; a fully-stalled controller is detached after the\r\n        // deadline (bounded wedge, never permanent).\r\n        let log_drain = Arc::clone(&log);\r\n        // DSR cursor authority (REQ-DSR-SINGLE-CPR): the drain answers each DSR\r\n        // with the grid's real display-column cursor. The provider runs on the\r\n        // drain thread BETWEEN sink handoffs (the append lock is released), and\r\n        // the grid is advanced by that same thread, so the reported position is\r\n        // exact at the query point.\r\n        let log_cursor = Arc::clone(&log);\r\n        let drain = session\r\n            .drain_with_cursor(\r\n                move |chunk| {\r\n                    // OBS RIDER Tap A (debug-gated, default OFF): the raw pre-parse\r","truncated":true,"truncatedBy":"lines","totalLines":8177,"totalBytes":3406,"outputLines":57,"outputBytes":3406,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":16479,"displayContent":{"text":"        let session = Arc::new(session);\r\n        let id = self.next_id.fetch_add(1, Ordering::Relaxed);\r\n        let pid = session.process_id();\r\n        // Birth identity is trustworthy here: Windows retains the child handle;\r\n        // Unix cannot recycle our child's pid until it is reaped, and no waiter\r\n        // has been started yet. Exec may change its image, not its birth stamp.\r\n        // [impl->REQ-BROKER-ZOMBIE-IDENTITY]\r\n        let process_started_at = pid.and_then(spt_store::proc::process_started_at);\r\n        // The ConPTY-create span releases#199 candidate C names, closed.\r\n        spawn_phase(\"pty_created\", &format!(\"pid={pid:?}\"));\r\n\r\n        // The session's lone PTY input-writer (P0): the SOLE caller of the\r\n        // blocking `write_input`. Every dispatch-side write enqueues here, so a\r\n        // paste burst parks only this thread — never the broker dispatch thread\r\n        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it\r\n        // too, so the lone writer enforces the W2 inject-floor order.\r\n        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());\r\n        // releases#199: the span from here to `row_inserted` is the one a 10-way\r\n        // contention run put 1427ms into while the ConPTY create took 14ms — the\r\n        // child is ALREADY RUNNING through all of it and the row the bring-up poll\r\n        // reads does not exist yet, which is this issue's face in miniature. So it\r\n        // is stamped per step rather than as one block.\r\n        spawn_phase(\"input_writer\", \"\");\r\n\r\n        // The output log, with the spawning connection pre-attached as the\r\n        // controller (identity `None` = LOCAL, not a remote driver) so no chunk\r\n        // is missed before an explicit subscribe — and so a remote attach to a\r\n        // spawned-but-undriven session takes control silently (no loud displace\r\n        // of the local-spawner placeholder; REQ-RCVIEW-1).\r\n        let log = Arc::new(Mutex::new(OutputLog::new(\r\n            id,\r\n            self.log_cap_chunks,\r\n            req.endpoint.clone(),\r\n            (req.rows, req.cols),\r\n        )));\r\n        log.lock()\r\n            .unwrap()\r\n            .become_controller(Arc::clone(send), None, 0, 0);\r\n        spawn_phase(\"log_ready\", \"\");\r\n\r\n        // Output drain → append under the log lock (fast: ring + viewer fan-out),\r\n        // then deliver to the controller OFF the lock with a bounded deadline\r\n        // (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST). The controller write is no\r\n        // longer inline under the log lock, so a backed-up controller can never\r\n        // wedge the session; a fully-stalled controller is detached after the\r\n        // deadline (bounded wedge, never permanent).\r\n        let log_drain = Arc::clone(&log);\r\n        // DSR cursor authority (REQ-DSR-SINGLE-CPR): the drain answers each DSR\r\n        // with the grid's real display-column cursor. The provider runs on the\r\n        // drain thread BETWEEN sink handoffs (the append lock is released), and\r\n        // the grid is advanced by that same thread, so the reported position is\r\n        // exact at the query point.\r\n        let log_cursor = Arc::clone(&log);\r\n        let drain = session\r\n            .drain_with_cursor(\r\n                move |chunk| {\r\n                    // OBS RIDER Tap A (debug-gated, default OFF): the raw pre-parse\r\n…\n                },\n…\n            )\n            .map_err(|e| format!(\"drain failed: {e}\"))?;","startLine":8303,"lineNumbers":[8303,8304,8305,8306,8307,8308,8309,8310,8311,8312,8313,8314,8315,8316,8317,8318,8319,8320,8321,8322,8323,8324,8325,8326,8327,8328,8329,8330,8331,8332,8333,8334,8335,8336,8337,8338,8339,8340,8341,8342,8343,8344,8345,8346,8347,8348,8349,8350,8351,8352,8353,8354,8355,8356,8357,8358,8359,null,8372,null,8374,8375]},"fileSize":857503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":16479,"totalBytes":3406,"outputLines":57,"outputBytes":3406,"shownRange":{"start":8303,"end":8359},"nextOffset":8360}}},"isError":false,"timestamp":1789295289694}}
{"type":"message","id":"3e7cb2e0","parentId":"60c356f8","timestamp":"2026-09-13T10:28:09.701Z","message":{"role":"toolResult","toolCallId":"call_v0nts1Wf3dxflDURjL33AmHJ|fc_07ba52f17df332c2016aa67ab9914487d2b6b3bd0a5ce6c135","toolName":"read","content":[{"type":"text","text":"    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\n    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n        let req: InputReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad input payload: {e}\"))?;\n        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!(\"bad input base64: {e}\"))?;\n        // Clone the input-writer Arc (and the translation handle, if any) out from\n        // under the sessions lock so the ENQUEUE happens without holding the table\n        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the\n        // dispatch thread is never parked here even when the harness has stalled\n        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\n        let (input, translation, log) = {\n            let sessions = recover(&self.sessions);\n            let h = sessions\n                .get(&req.session_id)\n                .ok_or_else(|| format!(\"no such session {}\", req.session_id))?;\n            (Arc::clone(&h.input), h.translation.clone(), Arc::clone(&h.log))\n        };\n        // RC-ORIGIN INPUT FENCE (ADR-0044 decision 3, the required defense):\n        // an rc-tagged input must come from the ACTIVE controller lease's\n        // originating conn — the same conn-identity gate resize has always had\n        // — so a displaced viewport can never keep typing into the PTY, no\n        // matter whether its Displaced notice was delivered, delayed, or lost.\n        // SCOPED to `Minter::Rc`: shell/system injection (`Minter::Shell`,\n        // legacy/untagged senders) legitimately writes from non-controller\n        // conns and is untouched. The error frame this surfaces terminates the\n        // stale serve worker's loop — defense in depth on the same terminal\n        // signal. In-memory check only (no I/O under the log lock).\n        // [impl->REQ-INPUT-CONTROLLER-FENCE]\n        if rc_input_rejected(req.minter, recover_log(&log).is_controller(send)) {\n            return Err(format!(\n                \"input is controller-exclusive: this rc lease was displaced on \\\n                 session {} (a newer controller holds the slot)\",\n                req.session_id\n            ));\n        }\n        // Layer C: when a (non-faulted) translation binary is attached, fire a\n        // CONTENT-FREE `{type:input}` ping so the binary can track operator idleness\n        // — best-effort, never carries the keystroke bytes (doyle gate #1, privacy).\n        if let Some(tr) = &translation {\n            if !tr.faulted.load(Ordering::Relaxed) {\n                let _ = tr.child.send(&ToBinary::Input);\n            }\n        }\n        // Layer C: if an injected sequence is mid-flight, the operator's keystrokes\n        // BUFFER in the inject floor and are flushed to the PTY after the sequence\n        // commits — so a stash/restore choreography can't be clobbered. Returns\n        // true when buffered (the caller must NOT write the PTY now).\n        let buffered = |bytes: &[u8]| -> bool {\n            translation\n                .as_ref()\n                .is_some_and(|tr| lock_floor(&tr.floor).buffer_if_held(bytes))\n        };\n        match req.op_id {\n            Some(op) => {\n                // Materialize the minter at the decode seam: an untagged (older)\n                // sender ⇒ Legacy; a live producer stamped its real tag (ADR-0034).\n                let minter = req.minter.unwrap_or(Minter::Legacy);\n                let key = EffectKey {\n                    class: req.session_id,\n                    minter,\n                    op,\n                };\n                let outcome = self\n                    .journal\n                    .apply_once(key, EffectKind::PtyWrite, || {\n                        // Buffer-or-ENQUEUE INSIDE the journal effect so the op is\n                        // still recorded applied + acked (dedup-safe) whether the byte\n                        // buffers (inject floor held), enqueues, or is dropped under\n                        // backpressure. The effect is now the non-blocking enqueue —\n                        // PtyWrite is ephemeral, so Applied-at-enqueue is sound\n                        // (V0.13.0-P0 design); the ack means \"accepted + ordered + will\n                        // land,\" and rc does not gate on landing.\n                        if !buffered(&bytes) {\n                            input.enqueue(bytes.clone());\n                        }\n                        Ok(())\n                    })\n                    .map_err(|e| format!(\"input enqueue failed: {e}\"))?;\n                // Ack exactly-once — but ONLY when the caller requested it\n                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward\n                // operator/rc path sends `ack=false`: writing the ack back onto the\n                // same brain↔broker conn it is flooding with input fills the return\n                // direction and deadlocks the per-conn handler (the wedge). With no\n                // ack the handler never writes back while servicing the flood, so it\n                // always drains. The acked path (`shellchan`, one-at-a-time) still\n                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the\n                // applied-set is the exactly-once anchor, not the ack frame).\n                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\n                if req.ack {\n                    send_frame(\n                        send,\n                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),\n                    );\n                }\n                Ok(())\n            }\n            None => {\n                if !buffered(&bytes) {\n                    input.enqueue(bytes);\n                }\n                Ok(())\n            }\n        }\n    }\n\n    /// Attempt a BOUNDED eager respawn of a FAULTED translation binary (F029 C-1,\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH) and return the endpoint's live\n    /// translation (the rebuilt one on success, or the existing faulted/`None` one\n    /// when we give up). Called from the inbound path when it observes `tr.faulted`.\n    ///\n    /// This is the seam that replaces ADR-0022's original never-respawn-on-fault\n\n[Showing lines 8745-8855 of 16479. Use :8856 to continue]"}],"details":{"truncation":{"content":"    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\r\n    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\r\n        let req: InputReq =\r\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad input payload: {e}\"))?;\r\n        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!(\"bad input base64: {e}\"))?;\r\n        // Clone the input-writer Arc (and the translation handle, if any) out from\r\n        // under the sessions lock so the ENQUEUE happens without holding the table\r\n        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the\r\n        // dispatch thread is never parked here even when the harness has stalled\r\n        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\n        let (input, translation, log) = {\r\n            let sessions = recover(&self.sessions);\r\n            let h = sessions\r\n                .get(&req.session_id)\r\n                .ok_or_else(|| format!(\"no such session {}\", req.session_id))?;\r\n            (Arc::clone(&h.input), h.translation.clone(), Arc::clone(&h.log))\r\n        };\r\n        // RC-ORIGIN INPUT FENCE (ADR-0044 decision 3, the required defense):\r\n        // an rc-tagged input must come from the ACTIVE controller lease's\r\n        // originating conn — the same conn-identity gate resize has always had\r\n        // — so a displaced viewport can never keep typing into the PTY, no\r\n        // matter whether its Displaced notice was delivered, delayed, or lost.\r\n        // SCOPED to `Minter::Rc`: shell/system injection (`Minter::Shell`,\r\n        // legacy/untagged senders) legitimately writes from non-controller\r\n        // conns and is untouched. The error frame this surfaces terminates the\r\n        // stale serve worker's loop — defense in depth on the same terminal\r\n        // signal. In-memory check only (no I/O under the log lock).\r\n        // [impl->REQ-INPUT-CONTROLLER-FENCE]\r\n        if rc_input_rejected(req.minter, recover_log(&log).is_controller(send)) {\r\n            return Err(format!(\r\n                \"input is controller-exclusive: this rc lease was displaced on \\\r\n                 session {} (a newer controller holds the slot)\",\r\n                req.session_id\r\n            ));\r\n        }\r\n        // Layer C: when a (non-faulted) translation binary is attached, fire a\r\n        // CONTENT-FREE `{type:input}` ping so the binary can track operator idleness\r\n        // — best-effort, never carries the keystroke bytes (doyle gate #1, privacy).\r\n        if let Some(tr) = &translation {\r\n            if !tr.faulted.load(Ordering::Relaxed) {\r\n                let _ = tr.child.send(&ToBinary::Input);\r\n            }\r\n        }\r\n        // Layer C: if an injected sequence is mid-flight, the operator's keystrokes\r\n        // BUFFER in the inject floor and are flushed to the PTY after the sequence\r\n        // commits — so a stash/restore choreography can't be clobbered. Returns\r\n        // true when buffered (the caller must NOT write the PTY now).\r\n        let buffered = |bytes: &[u8]| -> bool {\r\n            translation\r\n                .as_ref()\r\n                .is_some_and(|tr| lock_floor(&tr.floor).buffer_if_held(bytes))\r\n        };\r\n        match req.op_id {\r\n            Some(op) => {\r\n                // Materialize the minter at the decode seam: an untagged (older)\r\n                // sender ⇒ Legacy; a live producer stamped its real tag (ADR-0034).\r\n                let minter = req.minter.unwrap_or(Minter::Legacy);\r\n                let key = EffectKey {\r\n                    class: req.session_id,\r\n                    minter,\r\n                    op,\r\n                };\r\n                let outcome = self\r\n                    .journal\r\n                    .apply_once(key, EffectKind::PtyWrite, || {\r\n                        // Buffer-or-ENQUEUE INSIDE the journal effect so the op is\r\n                        // still recorded applied + acked (dedup-safe) whether the byte\r\n                        // buffers (inject floor held), enqueues, or is dropped under\r\n                        // backpressure. The effect is now the non-blocking enqueue —\r\n                        // PtyWrite is ephemeral, so Applied-at-enqueue is sound\r\n                        // (V0.13.0-P0 design); the ack means \"accepted + ordered + will\r\n                        // land,\" and rc does not gate on landing.\r\n                        if !buffered(&bytes) {\r\n                            input.enqueue(bytes.clone());\r\n                        }\r\n                        Ok(())\r\n                    })\r\n                    .map_err(|e| format!(\"input enqueue failed: {e}\"))?;\r\n                // Ack exactly-once — but ONLY when the caller requested it\r\n                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward\r\n                // operator/rc path sends `ack=false`: writing the ack back onto the\r\n                // same brain↔broker conn it is flooding with input fills the return\r\n                // direction and deadlocks the per-conn handler (the wedge). With no\r\n                // ack the handler never writes back while servicing the flood, so it\r\n                // always drains. The acked path (`shellchan`, one-at-a-time) still\r\n                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the\r\n                // applied-set is the exactly-once anchor, not the ack frame).\r\n                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\r\n                if req.ack {\r\n                    send_frame(\r\n                        send,\r\n                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),\r\n                    );\r\n                }\r\n                Ok(())\r\n            }\r\n            None => {\r\n                if !buffered(&bytes) {\r\n                    input.enqueue(bytes);\r\n                }\r\n                Ok(())\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Attempt a BOUNDED eager respawn of a FAULTED translation binary (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH) and return the endpoint's live\r\n    /// translation (the rebuilt one on success, or the existing faulted/`None` one\r\n    /// when we give up). Called from the inbound path when it observes `tr.faulted`.\r\n    ///\r\n    /// This is the seam that replaces ADR-0022's original never-respawn-on-fault\r","truncated":true,"truncatedBy":"lines","totalLines":7735,"totalBytes":6328,"outputLines":111,"outputBytes":6328,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":16479,"displayContent":{"text":"    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]\r\n    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\r\n        let req: InputReq =\r\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad input payload: {e}\"))?;\r\n        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!(\"bad input base64: {e}\"))?;\r\n        // Clone the input-writer Arc (and the translation handle, if any) out from\r\n        // under the sessions lock so the ENQUEUE happens without holding the table\r\n        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the\r\n        // dispatch thread is never parked here even when the harness has stalled\r\n        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).\r\n        let (input, translation, log) = {\r\n            let sessions = recover(&self.sessions);\r\n            let h = sessions\r\n                .get(&req.session_id)\r\n                .ok_or_else(|| format!(\"no such session {}\", req.session_id))?;\r\n            (Arc::clone(&h.input), h.translation.clone(), Arc::clone(&h.log))\r\n        };\r\n        // RC-ORIGIN INPUT FENCE (ADR-0044 decision 3, the required defense):\r\n        // an rc-tagged input must come from the ACTIVE controller lease's\r\n        // originating conn — the same conn-identity gate resize has always had\r\n        // — so a displaced viewport can never keep typing into the PTY, no\r\n        // matter whether its Displaced notice was delivered, delayed, or lost.\r\n        // SCOPED to `Minter::Rc`: shell/system injection (`Minter::Shell`,\r\n        // legacy/untagged senders) legitimately writes from non-controller\r\n        // conns and is untouched. The error frame this surfaces terminates the\r\n        // stale serve worker's loop — defense in depth on the same terminal\r\n        // signal. In-memory check only (no I/O under the log lock).\r\n        // [impl->REQ-INPUT-CONTROLLER-FENCE]\r\n        if rc_input_rejected(req.minter, recover_log(&log).is_controller(send)) {\r\n            return Err(format!(\r\n                \"input is controller-exclusive: this rc lease was displaced on \\\r\n                 session {} (a newer controller holds the slot)\",\r\n                req.session_id\r\n            ));\r\n        }\r\n        // Layer C: when a (non-faulted) translation binary is attached, fire a\r\n        // CONTENT-FREE `{type:input}` ping so the binary can track operator idleness\r\n        // — best-effort, never carries the keystroke bytes (doyle gate #1, privacy).\r\n        if let Some(tr) = &translation {\r\n            if !tr.faulted.load(Ordering::Relaxed) {\r\n                let _ = tr.child.send(&ToBinary::Input);\r\n            }\r\n        }\r\n        // Layer C: if an injected sequence is mid-flight, the operator's keystrokes\r\n        // BUFFER in the inject floor and are flushed to the PTY after the sequence\r\n        // commits — so a stash/restore choreography can't be clobbered. Returns\r\n        // true when buffered (the caller must NOT write the PTY now).\r\n        let buffered = |bytes: &[u8]| -> bool {\r\n            translation\r\n                .as_ref()\r\n                .is_some_and(|tr| lock_floor(&tr.floor).buffer_if_held(bytes))\r\n        };\r\n        match req.op_id {\r\n            Some(op) => {\r\n                // Materialize the minter at the decode seam: an untagged (older)\r\n                // sender ⇒ Legacy; a live producer stamped its real tag (ADR-0034).\r\n                let minter = req.minter.unwrap_or(Minter::Legacy);\r\n                let key = EffectKey {\r\n                    class: req.session_id,\r\n                    minter,\r\n                    op,\r\n                };\r\n                let outcome = self\r\n                    .journal\r\n                    .apply_once(key, EffectKind::PtyWrite, || {\r\n                        // Buffer-or-ENQUEUE INSIDE the journal effect so the op is\r\n                        // still recorded applied + acked (dedup-safe) whether the byte\r\n                        // buffers (inject floor held), enqueues, or is dropped under\r\n                        // backpressure. The effect is now the non-blocking enqueue —\r\n                        // PtyWrite is ephemeral, so Applied-at-enqueue is sound\r\n                        // (V0.13.0-P0 design); the ack means \"accepted + ordered + will\r\n                        // land,\" and rc does not gate on landing.\r\n                        if !buffered(&bytes) {\r\n                            input.enqueue(bytes.clone());\r\n                        }\r\n                        Ok(())\r\n                    })\r\n                    .map_err(|e| format!(\"input enqueue failed: {e}\"))?;\r\n                // Ack exactly-once — but ONLY when the caller requested it\r\n                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward\r\n                // operator/rc path sends `ack=false`: writing the ack back onto the\r\n                // same brain↔broker conn it is flooding with input fills the return\r\n                // direction and deadlocks the per-conn handler (the wedge). With no\r\n                // ack the handler never writes back while servicing the flood, so it\r\n                // always drains. The acked path (`shellchan`, one-at-a-time) still\r\n                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the\r\n                // applied-set is the exactly-once anchor, not the ack frame).\r\n                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]\r\n                if req.ack {\r\n                    send_frame(\r\n                        send,\r\n                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),\r\n                    );\r\n                }\r\n                Ok(())\r\n            }\r\n            None => {\r\n                if !buffered(&bytes) {\r\n                    input.enqueue(bytes);\r\n                }\r\n                Ok(())\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Attempt a BOUNDED eager respawn of a FAULTED translation binary (F029 C-1,\r\n    /// REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH) and return the endpoint's live\r\n    /// translation (the rebuilt one on success, or the existing faulted/`None` one\r\n    /// when we give up). Called from the inbound path when it observes `tr.faulted`.\r\n    ///\r\n    /// This is the seam that replaces ADR-0022's original never-respawn-on-fault\r","startLine":8745,"lineNumbers":[8745,8746,8747,8748,8749,8750,8751,8752,8753,8754,8755,8756,8757,8758,8759,8760,8761,8762,8763,8764,8765,8766,8767,8768,8769,8770,8771,8772,8773,8774,8775,8776,8777,8778,8779,8780,8781,8782,8783,8784,8785,8786,8787,8788,8789,8790,8791,8792,8793,8794,8795,8796,8797,8798,8799,8800,8801,8802,8803,8804,8805,8806,8807,8808,8809,8810,8811,8812,8813,8814,8815,8816,8817,8818,8819,8820,8821,8822,8823,8824,8825,8826,8827,8828,8829,8830,8831,8832,8833,8834,8835,8836,8837,8838,8839,8840,8841,8842,8843,8844,8845,8846,8847,8848,8849,8850,8851,8852,8853,8854,8855]},"fileSize":857503,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":16479,"totalBytes":6328,"outputLines":111,"outputBytes":6328,"shownRange":{"start":8745,"end":8855},"nextOffset":8856}}},"isError":false,"timestamp":1789295289701}}
{"type":"message","id":"9044c830","parentId":"3e7cb2e0","timestamp":"2026-09-13T10:28:09.731Z","message":{"role":"toolResult","toolCallId":"call_hlcfz5IT81843cvt9v4aBOLf|fc_07ba52f17df332c2016aa67ab9915487d28fcd4f1b887d7d27","toolName":"grep","content":[{"type":"text","text":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## broker.rs\n 82|use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n*83|use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n 84|use crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n 85|\n 86|/// The shared, serialized send side of one brain connection. Output frames,\n...\n 694|// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n*695|// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n 696|// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n 697|\n 698|/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n...\n 750|/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n*751|/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n 752|/// rather than one write, so no single huge write races the terminal's coalescer.\n 753|/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n 754|/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\n...\n 4275|    while let Ok(bytes) = rx.recv() {\n*4276|        let _ = session.write_input(&bytes);\n 4277|    }\n 4278|}\n 4279|\n...\n 4636|) -> Option<Arc<Translation>> {\n*4637|    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\n 4638|    match TranslationChild::spawn(argv, cmd_tx) {\n 4639|        Ok(child) => {\n 4640|            let child = Arc::new(child);\n...\n 4887|    child: &TranslationChild,\n*4888|    cmd_rx: &Receiver<KeyCmd>,\n 4889|    envelope: String,\n 4890|) -> SeqOutcome {\n 4891|    lock_floor(floor).open();\n...\n 4906|        match cmd_rx.recv_timeout(remaining) {\n*4907|            Ok(KeyCmd::Key { key }) => {\n 4908|                if let Some(bytes) = key_to_bytes(&key) {\n 4909|                    input.enqueue(bytes);\n 4910|                }\n 4911|            }\n*4912|            Ok(KeyCmd::Text { text }) => {\n 4913|                // Accumulate the payload for echo-verify, then type it paced-chunked.\n 4914|                let bytes = text.into_bytes();\n 4915|                sent_text.extend_from_slice(&bytes);\n...\n 4917|            }\n*4918|            Ok(KeyCmd::Delay { delay_ms }) => {\n 4919|                let want = Duration::from_millis(delay_ms);\n 4920|                let cap = deadline.saturating_duration_since(Instant::now());\n 4921|                thread::sleep(want.min(cap));\n 4922|            }\n*4923|            Ok(KeyCmd::Commit { .. }) => {\n 4924|                committed = true;\n 4925|                break;\n 4926|            }\n...\n 4956|    event_rx: Receiver<String>,\n*4957|    cmd_rx: Receiver<KeyCmd>,\n 4958|    faulted: Arc<AtomicBool>,\n 4959|    respawns: Arc<AtomicU32>,\n 4960|    log: Arc<Mutex<OutputLog>>,\n...\n 4990|    // delivery. So an adapter that KNOWS its harness echoes declares the capability\n*4991|    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\n 4992|    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\n 4993|    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\n 4994|    // AMBIENT env can NOT arm this: both vars are scrubbed at daemon startup\n...\n 11032|    /// recording the bytes it receives — EXACTLY mirroring the production\n*11033|    /// `input_writer` loop (`while let Ok(b) = rx.recv() { surface.write_input(&b) }`).\n 11034|    /// One FIFO + one writer ⇒ exact input order. Non-vacuous: a reorder/LIFO/dedup\n 11035|    /// mutation of the single-writer contract makes the recorded sequence diverge.\n 11036|    // [unit->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n...\n 11044|        impl SessionSurface for RecordingSurface {\n*11045|            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046|                self.0.lock().unwrap().push(bytes.to_vec());\n 11047|                Ok(())\n 11048|            }\n...\n 11077|        while let Ok(bytes) = rx.recv() {\n*11078|            surface.write_input(&bytes).unwrap();\n 11079|        }\n 11080|\n 11081|        assert_eq!(\n...\n 11385|            crate::msg::KIND_SIZE,\n*11386|            \"the viewer's first post-commit frame is the size frame, never raw suppressed output\"\n 11387|        );\n 11388|        let ev: crate::msg::SizeEvent = serde_json::from_value(f.payload).unwrap();\n 11389|        assert_eq!((ev.rows, ev.cols), (4, 10));\n...\n 11594|    // world is the pre-barrier code, where no sync frame exists at all and the\n*11595|    // first post-commit viewer frame is raw output; they hold the disposition\n 11596|    // table against regression.)\n 11597|    #[test]\n 11598|    fn tracked_state_inside_the_window_surfaces_in_the_sync_frame() {\n...\n 11653|    // [unit->REQ-RC-RESIZE-PRESENTATION-BARRIER] LEG 5, deferred + dropped\n*11654|    // classes: bracketed-paste and mouse toggles inside the suppressed window\n 11655|    // surface AFTER the repaint inside the sync frame (deferred verbatim, in\n 11656|    // emission order — a dropped toggle is an indefinite client/child\n 11657|    // divergence), while an OSC 8 hyperlink pair does NOT surface at all\n...\n 11697|    // NOT evicted by a suppressed append, and (b) is NOT evicted by the first\n*11698|    // post-commit append either — `last_ok` resets at commit. RED-FIRST on the\n 11699|    // shipped code: the suppressed append hands off + evaluates the deadline and\n 11700|    // returns Some(epoch) — a spurious evict signal fired from inside the window.\n 11701|    #[test]\n...\n 11740|        assert_eq!(\n*11741|            log.append(b\"first post-commit chunk\"),\n 11742|            None,\n## conn.rs\n 211|#[derive(Debug, PartialEq, Eq)]\n*212|pub(crate) enum ConnWrite {\n 213|    /// The frame was fully written.\n 214|    Done,\n 215|    /// The pre-write gate closure declined (e.g. a superseded controller\n## linkhost.rs\n 648|/// instruction, termination window, authenticated kill, token retired for the\n*649|/// spool's sake, drive + tunnel slots cleared). It is bracketed by two refusals\n 650|/// that `CloseOutcome` cannot express, which is why they live here and not in\n 651|/// the close:\n 652|///\n## rollback_compat.rs\n 17|//!\n*18|//! 1. **Enumerates the pre-ready durable writes in one place**\n 19|//!    ([`PRE_READY_DURABLE_FILES`]) — the files a brain writes *before* it signals\n 20|//!    ready, which a rolled-back N-1 brain must still read.\n 21|//! 2. **Pins each file's additive / N-1-readable contract** (the unit tripwire\n## translation.rs\n 63|#[serde(untagged)]\n*64|pub enum KeyCmd {\n 65|    /// A named key or `ctrl+<x>` chord → mapped to PTY bytes by [`key_to_bytes`].\n 66|    Key { key: String },\n 67|    /// Literal text typed verbatim into the PTY (the message payload).\n...\n 219|/// A live translation-binary child process: its piped stdin (for [`ToBinary`]\n*220|/// lines) plus a reader thread that parses [`KeyCmd`] lines off its stdout and\n 221|/// forwards them on the caller-supplied channel. The broker owns the receiving\n 222|/// end and the atomic PTY apply.\n 223|pub struct TranslationChild {\n...\n 236|    /// Spawn `argv` (program + args) as a stdin→stdout filter, forwarding each\n*237|    /// parsed [`KeyCmd`] on `cmds`. stderr is inherited so an adapter author can\n 238|    /// see the binary's own diagnostics. Fails closed: a missing/unspawnable\n 239|    /// binary returns the OS error (the broker logs it and `None` → idle inbound\n 240|    /// spools, never raw-injected). `argv[0]` is the (install-dir-resolved) program;\n...\n 244|    // [impl->REQ-HAZARD-CHILD-CONSOLE-FLASH]\n*245|    pub fn spawn(argv: &[String], cmds: Sender<KeyCmd>) -> io::Result<Self> {\n 246|        let (program, args) = argv\n 247|            .split_first()\n 248|            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"empty translation argv\"))?;\n...\n 280|                }\n*281|                match serde_json::from_str::<KeyCmd>(trimmed) {\n 282|                    Ok(cmd) => {\n 283|                        if cmds.send(cmd).is_err() {\n 284|                            break; // broker dropped the receiver — stop reading\n...\n 415|        let prog = \"cat\";\n*416|        let (tx, _rx) = std::sync::mpsc::channel::<KeyCmd>();\n 417|        match TranslationChild::spawn(&[prog.to_string()], tx) {\n 418|            Ok(child) => child.terminate(), // bounded no-zombie reap\n 419|            Err(e) => panic!(\n...\n 425|    // [unit->REQ-MSG-IDLE-TRANSLATION-BINARY] each stdout shape parses to its\n*426|    // KeyCmd variant; an unknown extra field on a recognized shape is ignored\n 427|    // (forward-compat); a line matching no shape errors (the reader skips it).\n 428|    #[test]\n 429|    fn key_cmd_parses_each_wire_shape() {\n 430|        assert_eq!(\n*431|            serde_json::from_str::<KeyCmd>(r#\"{\"key\":\"ctrl+s\"}\"#).unwrap(),\n*432|            KeyCmd::Key { key: \"ctrl+s\".into() }\n 433|        );\n 434|        assert_eq!(\n*435|            serde_json::from_str::<KeyCmd>(r#\"{\"text\":\"hello\"}\"#).unwrap(),\n*436|            KeyCmd::Text { text: \"hello\".into() }\n 437|        );\n 438|        assert_eq!(\n*439|            serde_json::from_str::<KeyCmd>(r#\"{\"delay_ms\":50}\"#).unwrap(),\n*440|            KeyCmd::Delay { delay_ms: 50 }\n 441|        );\n 442|        assert_eq!(\n*443|            serde_json::from_str::<KeyCmd>(r#\"{\"commit\":true}\"#).unwrap(),\n*444|            KeyCmd::Commit { commit: true }\n 445|        );\n 446|        // forward-compat: an unknown field alongside a known shape still parses.\n 447|        assert_eq!(\n*448|            serde_json::from_str::<KeyCmd>(r#\"{\"key\":\"enter\",\"repeat\":3}\"#).unwrap(),\n*449|            KeyCmd::Key { key: \"enter\".into() }\n 450|        );\n 451|        // a line matching no shape is an error.\n*452|        assert!(serde_json::from_str::<KeyCmd>(r#\"{\"bogus\":1}\"#).is_err());\n 453|    }\n 454|\n 455|    // [unit->REQ-MSG-IDLE-TRANSLATION-BINARY] the inject floor: while held, operator\n\n## bin/\n### xlate_choreo_fixture.rs\n 25|//!   - `bigmultiline` — the choreo sequence but the `{\"text\":…}` payload is a LARGE\n*26|//!     MULTI-LINE block bracketed by `HEADSTART`…`TAILEND` markers (W5-A\n 27|//!     REQ-INJECT-MULTILINE-INTEGRITY): a single big typed payload the broker's\n 28|//!     settle-gate + chunked write must deliver byte-complete (head AND tail intact)\n 29|//!     across a LOOP of injects.\n...\n 138|\n*139|/// A large multi-line payload for the `bigmultiline` mode, bracketed by unique\n 140|/// `HEADSTART`…`TAILEND` markers so the int test can assert BOTH the head and the tail\n 141|/// survived byte-complete (the head-loss bug drops the head, so a plain tail-marker check\n 142|/// would false-pass). ~12 lines of ~70 cols ≈ 850 B — larger than the field's ~322 B\n\nSkipped missing paths: .worktrees/300-input-acceptance/crates/spt-pty/src"}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":43,"fileCount":6,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/conn.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/linkhost.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/rollback_compat.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/bin/xlate_choreo_fixture.rs","count":2},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":20},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/conn.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/linkhost.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/rollback_compat.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","count":18}],"truncated":true,"perFileLimitReached":20,"displayContent":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## broker.rs\n    82│use crate::nethost::{NetHost, NET_EFFECT_SESSION};\n   *83│use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n    84│use crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n    85│\n    86│/// The shared, serialized send side of one brain connection. Output frames,\n      │...\n   694│// after typing → re-drive once → LOUD spool (silent+unrecoverable → recoverable+loud);\n  *695│// (3) chunked-paced write under the gate (bracketed-paste deferred — the binary owns\n   696│// the key/text interleave, not a clean wrap seam; F-019 found it moot for CC submit).\n   697│\n   698│/// The settle-gate deadline (Layer 1): how long the worker probes a PTY for a\n      │...\n   750│/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n  *751│/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n   752│/// rather than one write, so no single huge write races the terminal's coalescer.\n   753│/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n   754│/// first chunk). The int gate tunes it via `SPT_INJECT_TEXT_CHUNK`.\n      │...\n  4275│    while let Ok(bytes) = rx.recv() {\n *4276│        let _ = session.write_input(&bytes);\n  4277│    }\n  4278│}\n  4279│\n      │...\n  4636│) -> Option<Arc<Translation>> {\n *4637│    let (cmd_tx, cmd_rx) = channel::<KeyCmd>();\n  4638│    match TranslationChild::spawn(argv, cmd_tx) {\n  4639│        Ok(child) => {\n  4640│            let child = Arc::new(child);\n      │...\n  4887│    child: &TranslationChild,\n *4888│    cmd_rx: &Receiver<KeyCmd>,\n  4889│    envelope: String,\n  4890│) -> SeqOutcome {\n  4891│    lock_floor(floor).open();\n      │...\n  4906│        match cmd_rx.recv_timeout(remaining) {\n *4907│            Ok(KeyCmd::Key { key }) => {\n  4908│                if let Some(bytes) = key_to_bytes(&key) {\n  4909│                    input.enqueue(bytes);\n  4910│                }\n  4911│            }\n *4912│            Ok(KeyCmd::Text { text }) => {\n  4913│                // Accumulate the payload for echo-verify, then type it paced-chunked.\n  4914│                let bytes = text.into_bytes();\n  4915│                sent_text.extend_from_slice(&bytes);\n      │...\n  4917│            }\n *4918│            Ok(KeyCmd::Delay { delay_ms }) => {\n  4919│                let want = Duration::from_millis(delay_ms);\n  4920│                let cap = deadline.saturating_duration_since(Instant::now());\n  4921│                thread::sleep(want.min(cap));\n  4922│            }\n *4923│            Ok(KeyCmd::Commit { .. }) => {\n  4924│                committed = true;\n  4925│                break;\n  4926│            }\n      │...\n  4956│    event_rx: Receiver<String>,\n *4957│    cmd_rx: Receiver<KeyCmd>,\n  4958│    faulted: Arc<AtomicBool>,\n  4959│    respawns: Arc<AtomicU32>,\n  4960│    log: Arc<Mutex<OutputLog>>,\n      │...\n  4990│    // delivery. So an adapter that KNOWS its harness echoes declares the capability\n *4991│    // (parallel to the ruling's \"bracketed-paste where the harness declares it\"); Layer 1\n  4992│    // (the settle-gate) is the UNCONDITIONAL root fix. The int gate also forces it on\n  4993│    // (`SPT_INJECT_FORCE_ECHO_MISS`) to exercise the re-drive/spool wiring on any platform.\n  4994│    // AMBIENT env can NOT arm this: both vars are scrubbed at daemon startup\n      │...\n 11032│    /// recording the bytes it receives — EXACTLY mirroring the production\n*11033│    /// `input_writer` loop (`while let Ok(b) = rx.recv() { surface.write_input(&b) }`).\n 11034│    /// One FIFO + one writer ⇒ exact input order. Non-vacuous: a reorder/LIFO/dedup\n 11035│    /// mutation of the single-writer contract makes the recorded sequence diverge.\n 11036│    // [unit->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]\n      │...\n 11044│        impl SessionSurface for RecordingSurface {\n*11045│            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046│                self.0.lock().unwrap().push(bytes.to_vec());\n 11047│                Ok(())\n 11048│            }\n      │...\n 11077│        while let Ok(bytes) = rx.recv() {\n*11078│            surface.write_input(&bytes).unwrap();\n 11079│        }\n 11080│\n 11081│        assert_eq!(\n      │...\n 11385│            crate::msg::KIND_SIZE,\n*11386│            \"the viewer's first post-commit frame is the size frame, never raw suppressed output\"\n 11387│        );\n 11388│        let ev: crate::msg::SizeEvent = serde_json::from_value(f.payload).unwrap();\n 11389│        assert_eq!((ev.rows, ev.cols), (4, 10));\n      │...\n 11594│    // world is the pre-barrier code, where no sync frame exists at all and the\n*11595│    // first post-commit viewer frame is raw output; they hold the disposition\n 11596│    // table against regression.)\n 11597│    #[test]\n 11598│    fn tracked_state_inside_the_window_surfaces_in_the_sync_frame() {\n      │...\n 11653│    // [unit->REQ-RC-RESIZE-PRESENTATION-BARRIER] LEG 5, deferred + dropped\n*11654│    // classes: bracketed-paste and mouse toggles inside the suppressed window\n 11655│    // surface AFTER the repaint inside the sync frame (deferred verbatim, in\n 11656│    // emission order — a dropped toggle is an indefinite client/child\n 11657│    // divergence), while an OSC 8 hyperlink pair does NOT surface at all\n      │...\n 11697│    // NOT evicted by a suppressed append, and (b) is NOT evicted by the first\n*11698│    // post-commit append either — `last_ok` resets at commit. RED-FIRST on the\n 11699│    // shipped code: the suppressed append hands off + evaluates the deadline and\n 11700│    // returns Some(epoch) — a spurious evict signal fired from inside the window.\n 11701│    #[test]\n      │...\n 11740│        assert_eq!(\n*11741│            log.append(b\"first post-commit chunk\"),\n 11742│            None,\n## conn.rs\n 211│#[derive(Debug, PartialEq, Eq)]\n*212│pub(crate) enum ConnWrite {\n 213│    /// The frame was fully written.\n 214│    Done,\n 215│    /// The pre-write gate closure declined (e.g. a superseded controller\n## linkhost.rs\n 648│/// instruction, termination window, authenticated kill, token retired for the\n*649│/// spool's sake, drive + tunnel slots cleared). It is bracketed by two refusals\n 650│/// that `CloseOutcome` cannot express, which is why they live here and not in\n 651│/// the close:\n 652│///\n## rollback_compat.rs\n 17│//!\n*18│//! 1. **Enumerates the pre-ready durable writes in one place**\n 19│//!    ([`PRE_READY_DURABLE_FILES`]) — the files a brain writes *before* it signals\n 20│//!    ready, which a rolled-back N-1 brain must still read.\n 21│//! 2. **Pins each file's additive / N-1-readable contract** (the unit tripwire\n## translation.rs\n  63│#[serde(untagged)]\n *64│pub enum KeyCmd {\n  65│    /// A named key or `ctrl+<x>` chord → mapped to PTY bytes by [`key_to_bytes`].\n  66│    Key { key: String },\n  67│    /// Literal text typed verbatim into the PTY (the message payload).\n    │...\n 219│/// A live translation-binary child process: its piped stdin (for [`ToBinary`]\n*220│/// lines) plus a reader thread that parses [`KeyCmd`] lines off its stdout and\n 221│/// forwards them on the caller-supplied channel. The broker owns the receiving\n 222│/// end and the atomic PTY apply.\n 223│pub struct TranslationChild {\n    │...\n 236│    /// Spawn `argv` (program + args) as a stdin→stdout filter, forwarding each\n*237│    /// parsed [`KeyCmd`] on `cmds`. stderr is inherited so an adapter author can\n 238│    /// see the binary's own diagnostics. Fails closed: a missing/unspawnable\n 239│    /// binary returns the OS error (the broker logs it and `None` → idle inbound\n 240│    /// spools, never raw-injected). `argv[0]` is the (install-dir-resolved) program;\n    │...\n 244│    // [impl->REQ-HAZARD-CHILD-CONSOLE-FLASH]\n*245│    pub fn spawn(argv: &[String], cmds: Sender<KeyCmd>) -> io::Result<Self> {\n 246│        let (program, args) = argv\n 247│            .split_first()\n 248│            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"empty translation argv\"))?;\n    │...\n 280│                }\n*281│                match serde_json::from_str::<KeyCmd>(trimmed) {\n 282│                    Ok(cmd) => {\n 283│                        if cmds.send(cmd).is_err() {\n 284│                            break; // broker dropped the receiver — stop reading\n    │...\n 415│        let prog = \"cat\";\n*416│        let (tx, _rx) = std::sync::mpsc::channel::<KeyCmd>();\n 417│        match TranslationChild::spawn(&[prog.to_string()], tx) {\n 418│            Ok(child) => child.terminate(), // bounded no-zombie reap\n 419│            Err(e) => panic!(\n    │...\n 425│    // [unit->REQ-MSG-IDLE-TRANSLATION-BINARY] each stdout shape parses to its\n*426│    // KeyCmd variant; an unknown extra field on a recognized shape is ignored\n 427│    // (forward-compat); a line matching no shape errors (the reader skips it).\n 428│    #[test]\n 429│    fn key_cmd_parses_each_wire_shape() {\n 430│        assert_eq!(\n*431│            serde_json::from_str::<KeyCmd>(r#\"{\"key\":\"ctrl+s\"}\"#).unwrap(),\n*432│            KeyCmd::Key { key: \"ctrl+s\".into() }\n 433│        );\n 434│        assert_eq!(\n*435│            serde_json::from_str::<KeyCmd>(r#\"{\"text\":\"hello\"}\"#).unwrap(),\n*436│            KeyCmd::Text { text: \"hello\".into() }\n 437│        );\n 438│        assert_eq!(\n*439│            serde_json::from_str::<KeyCmd>(r#\"{\"delay_ms\":50}\"#).unwrap(),\n*440│            KeyCmd::Delay { delay_ms: 50 }\n 441│        );\n 442│        assert_eq!(\n*443│            serde_json::from_str::<KeyCmd>(r#\"{\"commit\":true}\"#).unwrap(),\n*444│            KeyCmd::Commit { commit: true }\n 445│        );\n 446│        // forward-compat: an unknown field alongside a known shape still parses.\n 447│        assert_eq!(\n*448│            serde_json::from_str::<KeyCmd>(r#\"{\"key\":\"enter\",\"repeat\":3}\"#).unwrap(),\n*449│            KeyCmd::Key { key: \"enter\".into() }\n 450│        );\n 451│        // a line matching no shape is an error.\n*452│        assert!(serde_json::from_str::<KeyCmd>(r#\"{\"bogus\":1}\"#).is_err());\n 453│    }\n 454│\n 455│    // [unit->REQ-MSG-IDLE-TRANSLATION-BINARY] the inject floor: while held, operator\n\n## bin/\n### xlate_choreo_fixture.rs\n  25│//!   - `bigmultiline` — the choreo sequence but the `{\"text\":…}` payload is a LARGE\n *26│//!     MULTI-LINE block bracketed by `HEADSTART`…`TAILEND` markers (W5-A\n  27│//!     REQ-INJECT-MULTILINE-INTEGRITY): a single big typed payload the broker's\n  28│//!     settle-gate + chunked write must deliver byte-complete (head AND tail intact)\n  29│//!     across a LOOP of injects.\n    │...\n 138│\n*139│/// A large multi-line payload for the `bigmultiline` mode, bracketed by unique\n 140│/// `HEADSTART`…`TAILEND` markers so the int test can assert BOTH the head and the tail\n 141│/// survived byte-complete (the head-loss bug drops the head, so a plain tail-marker check\n 142│/// would false-pass). ~12 lines of ~70 cols ≈ 850 B — larger than the field's ~322 B","missingPaths":[".worktrees/300-input-acceptance/crates/spt-pty/src"]},"isError":false,"timestamp":1789295289730}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_HVLtffKpOvISRsxCzkDGWzzj|fc_07ba52f17df332c2016aa67abee61c87d28f61827b86d27062","toolName":"hub","startedAt":"2026-09-13T10:28:17.961Z","intent":"Flagging inspected source mismatch"},"id":"9c45b805","parentId":"9044c830","timestamp":"2026-09-13T10:28:17.961Z"}
{"type":"message","id":"1530d5aa","parentId":"9c45b805","timestamp":"2026-09-13T10:28:17.963Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_HVLtffKpOvISRsxCzkDGWzzj|fc_07ba52f17df332c2016aa67abee61c87d28f61827b86d27062","name":"hub","arguments":{"i":"Flagging inspected source mismatch","op":"send","to":"Main","message":"Target checkout currently has KeyCmd::{Key,Text,Delay,Commit}, not translated Write; no typeahead/post-commit delivery implementation found. Own delivery converges at InputWriter→input_writer broker.rs:4164-4277; raw Vec loses origin and write errors swallowed. Investigating exact limits; flush_inject_floor is controller input, must never classify own delivery."},"intent":"Flagging inspected source mismatch"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":5730,"output":120,"cacheRead":36480,"cacheWrite":0,"totalTokens":42330,"reasoningTokens":9,"cost":{"input":0.057300000000000004,"output":0.006,"cacheRead":0.03648,"cacheWrite":0,"total":0.09978000000000001}},"stopReason":"toolUse","timestamp":1789295289774,"responseId":"resp_07ba52f17df332c2016aa67ab9fe1487d283d50c83d6bb9077","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_07ba52f17df332c2016aa67abe50ac87d2beb92ded9ea8fe74","type":"reasoning","content":[],"encrypted_content":"gAAAAABqpnq-QUZmNYPME7_tJ_lvgDk0gCF6GI9fVfxjIz_FM2X8fC42ij3ZwuaZL_doYNW64yybNBZnqdlp16QIbAD0FgFeigc3czfLoY7OyDBJ_JoO92rE-aeztADdjVZ1w0OSyIp5ng574oVYbWL9dkqTYpXElHJtzNIHvEzwpF6c5-EOmfYzY4rLmHWAK9gUaQJk93vAFrFwvW5rdRO5Y72urWdH_IPoWDQXPanpMnOIIqgy6qFOS75Su1ux3EoKnN4irrlbL3d0YmoJEiOJlxCNLOd4CVqV7UYzeU0PLFM5Lu6nzIpjkJM9V3JwV6lBaP0yP8wS_9TL4xiRQ0SC_a2Di_-yn1eeJ-gHnGafpZTaCYco_PrHsBwDaJrDHc5GwVbEiqGOsbtiJCyMFoUhaxq9sDy6Z2RRDdF73f6tosOq1mokQIipgZ7Bip4isxEUGyNkSJLwASfG5XXXp8N8g7LTb7NdLc4eBAXe2WDE7O2QDEQaAQ97aWsXIjvCkGHpJGXBmmMRfR6QI6E8R1sIx3itoLUudx9KrPYKd5NJ6AXywIkX9obQKlN1oI4U1naPfWHb8cLZA9sCx_6NDiG9jRjiCb3HH2Uci97uYByBrrMzSjbcn5iiB3jy-d7gfFxi0sGkAZ99fcS927qI2KU6mPqUwhQOncd73hsOi9SQtyAPG3eUjVMcIGtXCTWHxeB0_P_J8_m0uhqj0cNbdgjq6Dv3R4tQZA49y2bLqVTE0K2JunQT2WetzWutep_VSJkO-rlVVQUN6L-t2zwUso6OtLS7Eeu8OmzFsPJVkLWLJlaVeBUB2CVieLQYo5nDfNXgD2fixu7Abi8YiaERu3N3szSDzb0gLSVk7fSkrzAuGWlHo3t_qqceIty3kIn19Knq-bRTzLwzltwjoMphTQuoQ787NAd7nOTkzdMtf2PZAUV_P299ONB3WGi0DPAE3S40AxstrYcqDMHyOSR_Gef6OfBZxf2YPOpMDmBDs9XwpfxY5rUnkvMI3lhypknXD42H6lEZGZ5vGQE2N7rGuZmVfoUq8pMTgIeF8JdJzQJlzeF2noybyNH4Rdvkv1PLNg_6xKf-RKDrFclOsd7nTiwZ8AltKLnP5J6CFMgqKrMnBi3IQnZ4FzQChjoL-hn4eDooG_9Fl40Zv-IokLxPseqFFZp15HfvyAbbjO-msj5tI6lRj0T02ov16eAgiUcvauCBj5GJ0MkeKIyR9K_AGwo7HCRgg8zNx9tZnE7yVhjI6HJIxvB-2lY1wfvpssa7oSV4x1B1iv6ggy4wYuHSWnIWri4UQE_TpB4hLpB0JMxfkALwPXsctoPyh-9lvY5_kCdqMxrhlmWC","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"summary":[],"metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67abee61c87d28f61827b86d27062","type":"function_call","status":"completed","arguments":"{\"i\":\"Flagging inspected source mismatch\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Target checkout currently has KeyCmd::{Key,Text,Delay,Commit}, not translated Write; no typeahead/post-commit delivery implementation found. Own delivery converges at InputWriter→input_writer broker.rs:4164-4277; raw Vec loses origin and write errors swallowed. Investigating exact limits; flush_inject_floor is controller input, must never classify own delivery.\"}","call_id":"call_HVLtffKpOvISRsxCzkDGWzzj","internal_chat_message_metadata_passthrough":{"create_time":1789295290.177273,"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"hub","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":8184.485799999966,"ttft":4558.319000000018,"completedAt":1789295297959,"contextSnapshot":{"promptTokens":42210,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"98d6f23b","parentId":"1530d5aa","timestamp":"2026-09-13T10:28:17.963Z","message":{"role":"toolResult","toolCallId":"call_HVLtffKpOvISRsxCzkDGWzzj|fc_07ba52f17df332c2016aa67abee61c87d28f61827b86d27062","toolName":"hub","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"DeliveryCustody","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1789295297963}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5HE5MV5cVgDfAm0kC4Gf83f2|fc_07ba52f17df332c2016aa67acaa82087d292f0c9ca41186c5f","toolName":"grep","startedAt":"2026-09-13T10:28:26.803Z","args":{"path":".worktrees/300-input-acceptance/crates"},"intent":"Finding delivery normalization alternatives"},"id":"b41b129d","parentId":"98d6f23b","timestamp":"2026-09-13T10:28:26.803Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_vvWJUKPvqDtIgpQvih6b5iBd|fc_07ba52f17df332c2016aa67acaa83087d2af6c716339da5ab5","toolName":"grep","startedAt":"2026-09-13T10:28:26.803Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:1-85"},"intent":"Finding terminal surface source"},"id":"e5059727","parentId":"b41b129d","timestamp":"2026-09-13T10:28:26.803Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_wfK8NvzgITBOUAjG54eAPsKo|fc_07ba52f17df332c2016aa67acaa83887d2840484e4e993ac50","toolName":"read","startedAt":"2026-09-13T10:28:26.803Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:263-317"},"intent":"Reading translation reader transformation"},"id":"8e89904a","parentId":"e5059727","timestamp":"2026-09-13T10:28:26.803Z"}
{"type":"message","id":"7baf9e6a","parentId":"8e89904a","timestamp":"2026-09-13T10:28:26.803Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_5HE5MV5cVgDfAm0kC4Gf83f2|fc_07ba52f17df332c2016aa67acaa82087d292f0c9ca41186c5f","name":"grep","arguments":{"i":"Finding delivery normalization alternatives","pattern":"typeahead|post_commit|delivery.*normal|normal.*delivery|strip.*paste|200~|201~|fn write_input","path":".worktrees/300-input-acceptance/crates","case":false,"gitignore":false,"skip":null},"intent":"Finding delivery normalization alternatives"},{"type":"toolCall","id":"call_vvWJUKPvqDtIgpQvih6b5iBd|fc_07ba52f17df332c2016aa67acaa83087d2af6c716339da5ab5","name":"grep","arguments":{"i":"Finding terminal surface source","pattern":"SessionSurface|PtySession","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:1-85","case":true,"gitignore":false,"skip":null},"intent":"Finding terminal surface source"},{"type":"toolCall","id":"call_wfK8NvzgITBOUAjG54eAPsKo|fc_07ba52f17df332c2016aa67acaa83887d2840484e4e993ac50","name":"read","arguments":{"i":"Reading translation reader transformation","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:263-317"},"intent":"Reading translation reader transformation"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":240,"output":201,"cacheRead":42112,"cacheWrite":0,"totalTokens":42553,"cost":{"input":0.0024000000000000002,"output":0.01005,"cacheRead":0.042112,"cacheWrite":0,"total":0.054562}},"stopReason":"toolUse","timestamp":1789295298000,"responseId":"resp_07ba52f17df332c2016aa67ac2505887d2b0ac3e58e7e6480c","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_07ba52f17df332c2016aa67acaa82087d292f0c9ca41186c5f","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding delivery normalization alternatives\",\"pattern\":\"typeahead|post_commit|delivery.*normal|normal.*delivery|strip.*paste|200~|201~|fn write_input\",\"path\":\".worktrees/300-input-acceptance/crates\",\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_5HE5MV5cVgDfAm0kC4Gf83f2","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67acaa83087d2af6c716339da5ab5","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding terminal surface source\",\"pattern\":\"SessionSurface|PtySession\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:1-85\",\"case\":true,\"gitignore\":false,\"skip\":null}","call_id":"call_vvWJUKPvqDtIgpQvih6b5iBd","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67acaa83887d2840484e4e993ac50","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading translation reader transformation\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs:263-317\"}","call_id":"call_wfK8NvzgITBOUAjG54eAPsKo","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":8801.265000000014,"ttft":8678.163300000015,"completedAt":1789295306802,"contextSnapshot":{"promptTokens":42352,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"5ab05f1c","parentId":"7baf9e6a","timestamp":"2026-09-13T10:28:26.806Z","message":{"role":"toolResult","toolCallId":"call_wfK8NvzgITBOUAjG54eAPsKo|fc_07ba52f17df332c2016aa67acaa83887d2840484e4e993ac50","toolName":"read","content":[{"type":"text","text":"    pub fn spawn(argv: &[String], cmds: Sender<KeyCmd>) -> io::Result<Self> {\n…\n        {\n            use std::os::windows::process::CommandExt;\n            const CREATE_NO_WINDOW: u32 = 0x0800_0000;\n            command.creation_flags(CREATE_NO_WINDOW);\n        }\n        let mut child = command.spawn()?;\n        let pid = child.id().into();\n        let stdout = child\n            .stdout\n            .take()\n            .expect(\"stdout piped at spawn\");\n        let stdin = child.stdin.take().expect(\"stdin piped at spawn\");\n        let reader = thread::spawn(move || {\n            let buf = BufReader::new(stdout);\n            for line in buf.lines() {\n                let Ok(line) = line else { break }; // child stdout closed\n                let trimmed = line.trim();\n                if trimmed.is_empty() {\n                    continue;\n                }\n                match serde_json::from_str::<KeyCmd>(trimmed) {\n                    Ok(cmd) => {\n                        if cmds.send(cmd).is_err() {\n                            break; // broker dropped the receiver — stop reading\n                        }\n                    }\n                    Err(e) => {\n                        spt_proto::emit_line_err!(\"TRANSLATION_BIN_BAD_LINE: {e}: {trimmed}\");\n                    }\n                }\n            }\n        });\n        Ok(TranslationChild {\n            child: Mutex::new(Some(child)),\n            stdin: Mutex::new(Some(stdin)),\n            _reader: reader,\n            pid,\n        })\n    }\n\n    /// The child's OS pid (for tests / introspection / the no-zombie assertion).\n    pub fn pid(&self) -> Option<u32> {\n        self.pid\n    }\n\n    /// Write one [`ToBinary`] line to the child's stdin (newline-terminated,\n    /// flushed). A closed stdin (post-shutdown) is a no-op `Ok`.\n    // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\n    pub fn send(&self, msg: &ToBinary) -> io::Result<()> {\n        let line = serde_json::to_string(msg).map_err(io::Error::other)?;\n        let mut guard = self.stdin.lock().unwrap();\n        if let Some(w) = guard.as_mut() {\n            w.write_all(line.as_bytes())?;\n            w.write_all(b\"\\n\")?;\n            w.flush()?;\n        }\n        Ok(())\n    }\n\n    /// Drop the child's stdin handle, sending EOF — a well-behaved filter then\n\n[Showing lines 262-320 of 507. Use :321 to continue]"}],"details":{"truncation":{"content":"            use std::os::windows::process::CommandExt;\r\n            const CREATE_NO_WINDOW: u32 = 0x0800_0000;\r\n            command.creation_flags(CREATE_NO_WINDOW);\r\n        }\r\n        let mut child = command.spawn()?;\r\n        let pid = child.id().into();\r\n        let stdout = child\r\n            .stdout\r\n            .take()\r\n            .expect(\"stdout piped at spawn\");\r\n        let stdin = child.stdin.take().expect(\"stdin piped at spawn\");\r\n        let reader = thread::spawn(move || {\r\n            let buf = BufReader::new(stdout);\r\n            for line in buf.lines() {\r\n                let Ok(line) = line else { break }; // child stdout closed\r\n                let trimmed = line.trim();\r\n                if trimmed.is_empty() {\r\n                    continue;\r\n                }\r\n                match serde_json::from_str::<KeyCmd>(trimmed) {\r\n                    Ok(cmd) => {\r\n                        if cmds.send(cmd).is_err() {\r\n                            break; // broker dropped the receiver — stop reading\r\n                        }\r\n                    }\r\n                    Err(e) => {\r\n                        spt_proto::emit_line_err!(\"TRANSLATION_BIN_BAD_LINE: {e}: {trimmed}\");\r\n                    }\r\n                }\r\n            }\r\n        });\r\n        Ok(TranslationChild {\r\n            child: Mutex::new(Some(child)),\r\n            stdin: Mutex::new(Some(stdin)),\r\n            _reader: reader,\r\n            pid,\r\n        })\r\n    }\r\n\r\n    /// The child's OS pid (for tests / introspection / the no-zombie assertion).\r\n    pub fn pid(&self) -> Option<u32> {\r\n        self.pid\r\n    }\r\n\r\n    /// Write one [`ToBinary`] line to the child's stdin (newline-terminated,\r\n    /// flushed). A closed stdin (post-shutdown) is a no-op `Ok`.\r\n    // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n    pub fn send(&self, msg: &ToBinary) -> io::Result<()> {\r\n        let line = serde_json::to_string(msg).map_err(io::Error::other)?;\r\n        let mut guard = self.stdin.lock().unwrap();\r\n        if let Some(w) = guard.as_mut() {\r\n            w.write_all(line.as_bytes())?;\r\n            w.write_all(b\"\\n\")?;\r\n            w.flush()?;\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    /// Drop the child's stdin handle, sending EOF — a well-behaved filter then\r","truncated":true,"truncatedBy":"lines","totalLines":246,"totalBytes":2263,"outputLines":59,"outputBytes":2263,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":507,"displayContent":{"text":"    pub fn spawn(argv: &[String], cmds: Sender<KeyCmd>) -> io::Result<Self> {\n…\n        {\n            use std::os::windows::process::CommandExt;\r\n            const CREATE_NO_WINDOW: u32 = 0x0800_0000;\r\n            command.creation_flags(CREATE_NO_WINDOW);\r\n        }\r\n        let mut child = command.spawn()?;\r\n        let pid = child.id().into();\r\n        let stdout = child\r\n            .stdout\r\n            .take()\r\n            .expect(\"stdout piped at spawn\");\r\n        let stdin = child.stdin.take().expect(\"stdin piped at spawn\");\r\n        let reader = thread::spawn(move || {\r\n            let buf = BufReader::new(stdout);\r\n            for line in buf.lines() {\r\n                let Ok(line) = line else { break }; // child stdout closed\r\n                let trimmed = line.trim();\r\n                if trimmed.is_empty() {\r\n                    continue;\r\n                }\r\n                match serde_json::from_str::<KeyCmd>(trimmed) {\r\n                    Ok(cmd) => {\r\n                        if cmds.send(cmd).is_err() {\r\n                            break; // broker dropped the receiver — stop reading\r\n                        }\r\n                    }\r\n                    Err(e) => {\r\n                        spt_proto::emit_line_err!(\"TRANSLATION_BIN_BAD_LINE: {e}: {trimmed}\");\r\n                    }\r\n                }\r\n            }\r\n        });\r\n        Ok(TranslationChild {\r\n            child: Mutex::new(Some(child)),\r\n            stdin: Mutex::new(Some(stdin)),\r\n            _reader: reader,\r\n            pid,\r\n        })\r\n    }\r\n\r\n    /// The child's OS pid (for tests / introspection / the no-zombie assertion).\r\n    pub fn pid(&self) -> Option<u32> {\r\n        self.pid\r\n    }\r\n\r\n    /// Write one [`ToBinary`] line to the child's stdin (newline-terminated,\r\n    /// flushed). A closed stdin (post-shutdown) is a no-op `Ok`.\r\n    // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]\r\n    pub fn send(&self, msg: &ToBinary) -> io::Result<()> {\r\n        let line = serde_json::to_string(msg).map_err(io::Error::other)?;\r\n        let mut guard = self.stdin.lock().unwrap();\r\n        if let Some(w) = guard.as_mut() {\r\n            w.write_all(line.as_bytes())?;\r\n            w.write_all(b\"\\n\")?;\r\n            w.flush()?;\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    /// Drop the child's stdin handle, sending EOF — a well-behaved filter then\r","startLine":245,"lineNumbers":[245,null,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]},"fileSize":22675,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\translation.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":507,"totalBytes":2263,"outputLines":59,"outputBytes":2263,"shownRange":{"start":262,"end":320},"nextOffset":321}}},"isError":false,"timestamp":1789295306805}}
{"type":"message","id":"2037a242","parentId":"5ab05f1c","timestamp":"2026-09-13T10:28:26.806Z","message":{"role":"toolResult","toolCallId":"call_vvWJUKPvqDtIgpQvih6b5iBd|fc_07ba52f17df332c2016aa67acaa83087d2af6c716339da5ab5","toolName":"grep","content":[{"type":"text","text":" 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...\n 42|use interprocess::local_socket::{prelude::*, Stream};\n*43|use spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n 44|\n 45|use spt_net::net::attach::AttachIntent;\n 46|use spt_net::net::endpoint::EndpointAddr;"}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src\\broker.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":2}],"truncated":false,"displayContent":"  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   │...\n 42│use interprocess::local_socket::{prelude::*, Stream};\n*43│use spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n 44│\n 45│use spt_net::net::attach::AttachIntent;\n 46│use spt_net::net::endpoint::EndpointAddr;"},"isError":false,"timestamp":1789295306806}}
{"type":"message","id":"12ca532f","parentId":"2037a242","timestamp":"2026-09-13T10:28:26.836Z","message":{"role":"toolResult","toolCallId":"call_5HE5MV5cVgDfAm0kC4Gf83f2|fc_07ba52f17df332c2016aa67acaa82087d292f0c9ca41186c5f","toolName":"grep","content":[{"type":"text","text":"# .worktrees/300-input-acceptance/crates/\n\n## spt-daemon/src/\n### broker.rs\n 11044|        impl SessionSurface for RecordingSurface {\n*11045|            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046|                self.0.lock().unwrap().push(bytes.to_vec());\n 11047|                Ok(())\n 11048|            }\n### inject.rs\n 41|    if spt_store::registry::resolve_address(target, owlery).is_some() {\n*42|        return false; // a relay listener exists → normal TCP delivery\n 43|    }\n 44|    spt_store::info::read_info(&spt_store::perch::resolve_perch_path(\n 45|        target,\n...\n 262|\n*263|            // A relay listener registered → normal TCP delivery, NOT inject.\n 264|            let addr: std::net::SocketAddr = \"127.0.0.1:6553\".parse().unwrap();\n 265|            spt_store::registry::register_address(id, &addr, &owlery).unwrap();\n 266|            assert!(\n\n## spt-term/src/\n### pty.rs\n 235|impl SessionSurface for PtySession {\n*236|    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 237|        let mut w = self.writer.lock().unwrap();\n 238|        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239|        w.flush().map_err(SurfaceError::Io)\n### surface.rs\n 104|    /// the concrete surface in A3). Flushed on return.\n*105|    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError>;\n 106|\n 107|    /// Inform the surface (and thus the child) of a new window geometry. On a\n 108|    /// real PTY this updates the kernel winsize and raises `SIGWINCH`.\n\n## spt/src/\n### cli.rs\n 27141|\n*27142|        // A relay listener registered → normal TCP delivery, NOT inject.\n 27143|        let addr: std::net::SocketAddr = \"127.0.0.1:6553\".parse().unwrap();\n 27144|        spt_store::registry::register_address(id, &addr, &owlery).unwrap();\n 27145|        assert!(!spt_daemon::is_spt_hosted_no_relay(id, &owlery), \"a relay address takes the TCP path\");\n### fetchverb.rs\n 62|    }\n*63|    // Strip a scheme and authority if the caller pasted a whole URL. Anything\n 64|    // else is treated as the shorthand, so a leading `/` is tolerated too.\n 65|    let rest = match trimmed.split_once(\"://\") {\n 66|        Some((scheme, after)) => {\n### rc.rs\n 650|\n*651|/// Wrap literal pasted content in xterm BRACKETED-PASTE markers (`ESC[200~` …\n*652|/// `ESC[201~`) so the harness treats it as one paste, not a keystroke storm —\n 653|/// CC enables bracketed-paste mode (`ESC[?2004h`) on its PTY, so a multi-line\n 654|/// paste lands intact with NO per-newline submit-storm, and it is harness-AGNOSTIC\n 655|/// (standard xterm contract). Content is forwarded VERBATIM — it is literal pasted\n...\n 659|fn wrap_bracketed_paste(content: &[u8]) -> Vec<u8> {\n*660|    const START: &[u8] = b\"\\x1b[200~\";\n*661|    const END: &[u8] = b\"\\x1b[201~\";\n 662|    let mut out = Vec::with_capacity(START.len() + content.len() + END.len());\n 663|    out.extend_from_slice(START);\n 664|    out.extend_from_slice(content);\n...\n 5342|\n*5343|    // [unit->REQ-RC-WIN-PASTE] the bracketed-paste wrap is EXACT (`ESC[200~` …\n*5344|    // `ESC[201~`) and the content is forwarded VERBATIM — a multi-line paste keeps\n 5345|    // its embedded newlines untranslated (the no-submit-storm property: CC sees the\n 5346|    // markers + treats the whole block as one paste, not a Enter-per-line storm).\n 5347|    #[cfg(windows)]\n...\n 5350|        // Single line.\n*5351|        assert_eq!(wrap_bracketed_paste(b\"hi\"), b\"\\x1b[200~hi\\x1b[201~\".to_vec());\n 5352|        // Multi-line: newlines stay literal INSIDE the markers (no \\r submit-storm,\n 5353|        // no per-char translation), markers exactly once around the whole block.\n 5354|        let content = b\"line1\\nline2\\nline3\";\n*5355|        let mut expect = b\"\\x1b[200~\".to_vec();\n 5356|        expect.extend_from_slice(content);\n*5357|        expect.extend_from_slice(b\"\\x1b[201~\");\n 5358|        assert_eq!(wrap_bracketed_paste(content), expect);\n 5359|        // Empty content still frames (the caller — clipboard_paste — is what no-ops\n 5360|        // on empty; the wrap itself is pure framing).\n*5361|        assert_eq!(wrap_bracketed_paste(b\"\"), b\"\\x1b[200~\\x1b[201~\".to_vec());\n 5362|    }\n 5363|\n 5364|    // [unit->REQ-RC-WIN-PASTE] the right-click paste gesture is ONLY right-button\n...\n 5393|            clipboard_paste(|| Some(b\"pasted\".to_vec())),\n*5394|            Some(b\"\\x1b[200~pasted\\x1b[201~\".to_vec())\n 5395|        );\n 5396|        // Empty clipboard → no-op (not an empty bracketed frame — nothing to paste).\n 5397|        assert_eq!(clipboard_paste(|| Some(Vec::new())), None);"}],"details":{"scopePath":".worktrees/300-input-acceptance/crates","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":18,"fileCount":7,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs",".worktrees/300-input-acceptance/crates/spt-daemon/src/inject.rs",".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs",".worktrees/300-input-acceptance/crates/spt-term/src/surface.rs",".worktrees/300-input-acceptance/crates/spt/src/cli.rs",".worktrees/300-input-acceptance/crates/spt/src/fetchverb.rs",".worktrees/300-input-acceptance/crates/spt/src/rc.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/inject.rs","count":2},{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt-term/src/surface.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt/src/cli.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt/src/fetchverb.rs","count":1},{"path":".worktrees/300-input-acceptance/crates/spt/src/rc.rs","count":11}],"truncated":false,"displayContent":"# .worktrees/300-input-acceptance/crates/\n\n## spt-daemon/src/\n### broker.rs\n 11044│        impl SessionSurface for RecordingSurface {\n*11045│            fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 11046│                self.0.lock().unwrap().push(bytes.to_vec());\n 11047│                Ok(())\n 11048│            }\n### inject.rs\n  41│    if spt_store::registry::resolve_address(target, owlery).is_some() {\n *42│        return false; // a relay listener exists → normal TCP delivery\n  43│    }\n  44│    spt_store::info::read_info(&spt_store::perch::resolve_perch_path(\n  45│        target,\n    │...\n 262│\n*263│            // A relay listener registered → normal TCP delivery, NOT inject.\n 264│            let addr: std::net::SocketAddr = \"127.0.0.1:6553\".parse().unwrap();\n 265│            spt_store::registry::register_address(id, &addr, &owlery).unwrap();\n 266│            assert!(\n\n## spt-term/src/\n### pty.rs\n 235│impl SessionSurface for PtySession {\n*236│    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n 237│        let mut w = self.writer.lock().unwrap();\n 238│        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239│        w.flush().map_err(SurfaceError::Io)\n### surface.rs\n 104│    /// the concrete surface in A3). Flushed on return.\n*105│    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError>;\n 106│\n 107│    /// Inform the surface (and thus the child) of a new window geometry. On a\n 108│    /// real PTY this updates the kernel winsize and raises `SIGWINCH`.\n\n## spt/src/\n### cli.rs\n 27141│\n*27142│        // A relay listener registered → normal TCP delivery, NOT inject.\n 27143│        let addr: std::net::SocketAddr = \"127.0.0.1:6553\".parse().unwrap();\n 27144│        spt_store::registry::register_address(id, &addr, &owlery).unwrap();\n 27145│        assert!(!spt_daemon::is_spt_hosted_no_relay(id, &owlery), \"a relay address takes the TCP path\");\n### fetchverb.rs\n 62│    }\n*63│    // Strip a scheme and authority if the caller pasted a whole URL. Anything\n 64│    // else is treated as the shorthand, so a leading `/` is tolerated too.\n 65│    let rest = match trimmed.split_once(\"://\") {\n 66│        Some((scheme, after)) => {\n### rc.rs\n  650│\n *651│/// Wrap literal pasted content in xterm BRACKETED-PASTE markers (`ESC[200~` …\n *652│/// `ESC[201~`) so the harness treats it as one paste, not a keystroke storm —\n  653│/// CC enables bracketed-paste mode (`ESC[?2004h`) on its PTY, so a multi-line\n  654│/// paste lands intact with NO per-newline submit-storm, and it is harness-AGNOSTIC\n  655│/// (standard xterm contract). Content is forwarded VERBATIM — it is literal pasted\n     │...\n  659│fn wrap_bracketed_paste(content: &[u8]) -> Vec<u8> {\n *660│    const START: &[u8] = b\"\\x1b[200~\";\n *661│    const END: &[u8] = b\"\\x1b[201~\";\n  662│    let mut out = Vec::with_capacity(START.len() + content.len() + END.len());\n  663│    out.extend_from_slice(START);\n  664│    out.extend_from_slice(content);\n     │...\n 5342│\n*5343│    // [unit->REQ-RC-WIN-PASTE] the bracketed-paste wrap is EXACT (`ESC[200~` …\n*5344│    // `ESC[201~`) and the content is forwarded VERBATIM — a multi-line paste keeps\n 5345│    // its embedded newlines untranslated (the no-submit-storm property: CC sees the\n 5346│    // markers + treats the whole block as one paste, not a Enter-per-line storm).\n 5347│    #[cfg(windows)]\n     │...\n 5350│        // Single line.\n*5351│        assert_eq!(wrap_bracketed_paste(b\"hi\"), b\"\\x1b[200~hi\\x1b[201~\".to_vec());\n 5352│        // Multi-line: newlines stay literal INSIDE the markers (no \\r submit-storm,\n 5353│        // no per-char translation), markers exactly once around the whole block.\n 5354│        let content = b\"line1\\nline2\\nline3\";\n*5355│        let mut expect = b\"\\x1b[200~\".to_vec();\n 5356│        expect.extend_from_slice(content);\n*5357│        expect.extend_from_slice(b\"\\x1b[201~\");\n 5358│        assert_eq!(wrap_bracketed_paste(content), expect);\n 5359│        // Empty content still frames (the caller — clipboard_paste — is what no-ops\n 5360│        // on empty; the wrap itself is pure framing).\n*5361│        assert_eq!(wrap_bracketed_paste(b\"\"), b\"\\x1b[200~\\x1b[201~\".to_vec());\n 5362│    }\n 5363│\n 5364│    // [unit->REQ-RC-WIN-PASTE] the right-click paste gesture is ONLY right-button\n     │...\n 5393│            clipboard_paste(|| Some(b\"pasted\".to_vec())),\n*5394│            Some(b\"\\x1b[200~pasted\\x1b[201~\".to_vec())\n 5395│        );\n 5396│        // Empty clipboard → no-op (not an empty bracketed frame — nothing to paste).\n 5397│        assert_eq!(clipboard_paste(|| Some(Vec::new())), None);"},"isError":false,"timestamp":1789295306835}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_zgZOmXxYwhchEGFCtU0KuCfL|fc_07ba52f17df332c2016aa67ad3787087d2a33d17105698eb08","toolName":"read","startedAt":"2026-09-13T10:28:35.553Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs:174-243"},"intent":"Reading physical writer guarantees"},"id":"b9bd2030","parentId":"12ca532f","timestamp":"2026-09-13T10:28:35.553Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YHsfW71T8210ZYZlNRtvgpXV|fc_07ba52f17df332c2016aa67ad3787c87d2b9836dd821624502","toolName":"grep","startedAt":"2026-09-13T10:28:35.553Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs"},"intent":"Mapping other physical PTY writes"},"id":"902e20b4","parentId":"b9bd2030","timestamp":"2026-09-13T10:28:35.553Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_LvDkGvFJuq6LXmfuhEi0BYbg|fc_07ba52f17df332c2016aa67ad3788087d2aa9d0ebb8f21fa4d","toolName":"grep","startedAt":"2026-09-13T10:28:35.553Z","args":{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4750-5190"},"intent":"Checking escaped normalization variants"},"id":"12f41e79","parentId":"902e20b4","timestamp":"2026-09-13T10:28:35.553Z"}
{"type":"message","id":"5d51414a","parentId":"12f41e79","timestamp":"2026-09-13T10:28:35.553Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_zgZOmXxYwhchEGFCtU0KuCfL|fc_07ba52f17df332c2016aa67ad3787087d2a33d17105698eb08","name":"read","arguments":{"i":"Reading physical writer guarantees","path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs:174-243"},"intent":"Reading physical writer guarantees"},{"type":"toolCall","id":"call_YHsfW71T8210ZYZlNRtvgpXV|fc_07ba52f17df332c2016aa67ad3787c87d2b9836dd821624502","name":"grep","arguments":{"i":"Mapping other physical PTY writes","pattern":"write_all|writer.lock|fn drain","path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","case":true,"gitignore":false,"skip":null},"intent":"Mapping other physical PTY writes"},{"type":"toolCall","id":"call_LvDkGvFJuq6LXmfuhEi0BYbg|fc_07ba52f17df332c2016aa67ad3788087d2aa9d0ebb8f21fa4d","name":"grep","arguments":{"i":"Checking escaped normalization variants","pattern":"replace\\(|strip_prefix|strip_suffix|trim_end","path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4750-5190","case":true,"gitignore":false,"skip":null},"intent":"Checking escaped normalization variants"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":2431,"output":229,"cacheRead":42240,"cacheWrite":0,"totalTokens":44900,"reasoningTokens":11,"cost":{"input":0.024310000000000002,"output":0.01145,"cacheRead":0.04224,"cacheWrite":0,"total":0.078}},"stopReason":"toolUse","timestamp":1789295306843,"responseId":"resp_07ba52f17df332c2016aa67acb416487d2845d7abb09fc86b8","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_07ba52f17df332c2016aa67acca69487d2ac8105076c412dfd","type":"reasoning","content":[],"encrypted_content":"gAAAAABqpnrTMgzqcqJkRyzT73fx7VemD0MnsEhvJpaCJcyg6LtKedTsC1lzk3S6tfZ3TFOf6MbZUeIQSfVMd7GY777RBUUDpYoCPV5aA5oNcXDnSRMv8zcW9VIKRaGdp5Z2FDNCcX5sSr68rfC_8Ui5B4-bRd4G9zDim39AAv1h4q9_6LNs-Y50934M1QdRsy3rHF5CMlbZm6wXX2cE2Whqxx6BVg6_Gm_fTZCyI77jYEuxSv-zYIJG_NDLs7w7fgUvDE5OlEi0w5eD3s5zeoUyJjFgbqVAGVXGIE0ZcvkZ0e5j1hbAZn8so5a0UavgZMBWJG4Go78ctdTgsJf4TNoSEn7yoExUOhPbjs3wnS3QvzKGLRd6W4IlMkA3sqPEF5KiWb4gf3IZRXs9y-DW91abZuAhL6026JzjpFvX9-IeTf-Qu1oBrfYVhOVcue4fnkttw6ezkV5KGxlVDafA6XMjjtKpzfuhAXsCrq3IdLIta_gSnPz_eLYO5bFnTuDZ7hPGocyzpfRTaYLfkQbHQlmd4BirEkY-1oWhsOCKu7Jb7N0G_NsG9nN7urSriOcN1Ez932vd7gPoh4is2TV9vY8LHYW3pGb6lW_8QFSIOpnAl_3ADm2xAmQ8FskqnEiSB-yY-0tZg3ahrCvcASC7wqeJJ4nvpkOKRPOCRAK2Qq4L9FrkJEjzzXXA0pz0TUpzcu2oljomfpydus6FFnxOKJjXDMkqmZfvGUsHodLUIqU-OxUW69O2OjbZUAABhtvrt82t-rgrmJUCew0J5HfUurFNiBWQyVV4-yYKvgnRSmsfBWkXeNRR9E5O68xSnwPVS9_3FyHJcGMFuqcKgEVRhPHt4bui_LoFC4rpj2GZM1s6X44RFkJLzXNz2KVKjgsnVSFkY8-TmsPfRDu8icd2-obOkpDjn-4Y_12S8Hm0goJEY7qMkrD3eV_Aowz-OH_4w6hQGbSgdeBrp4zOU96nCxhiAnPyXNog27Lr_k4G8VnvkjJhfeuDc-kI63l0Rf7lEhlbQfcQjxIZZjU7W13obECselKrNutRGfZT6N8aTIeF5pzgyud72uSD09jVEE1jiRPOej-JgcikPJEJEjxWrdtWYEaI7nlxj_dDLmYOMJ96ogO-6opt_axtBKtpxGy5Z_Ously0eCa0pHCO6ewf6o0zRYn94rzSnsX6ZFN8-RfPtYl1LqnIboYYrMlE403gEs-roLn-dz3M90HAByZ5U3AzMPNgoy9q6jVbfs5Dumnlh5a7hCt5ZaNbyIfdbfsZbem134Dkj4NlcZXwRK7cK-kBOnr-31-0POYe6XfPPEK7SAYgSLjWeeRVOhLvw3C-EDpB2TdpNzy-","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"summary":[],"metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ad3787087d2a33d17105698eb08","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading physical writer guarantees\",\"path\":\".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs:174-243\"}","call_id":"call_zgZOmXxYwhchEGFCtU0KuCfL","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"read","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ad3787c87d2b9836dd821624502","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping other physical PTY writes\",\"pattern\":\"write_all|writer.lock|fn drain\",\"path\":\".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs\",\"case\":true,\"gitignore\":false,\"skip\":null}","call_id":"call_YHsfW71T8210ZYZlNRtvgpXV","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ad3788087d2aa9d0ebb8f21fa4d","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking escaped normalization variants\",\"pattern\":\"replace\\\\(|strip_prefix|strip_suffix|trim_end\",\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs;.worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs:4750-5190\",\"case\":true,\"gitignore\":false,\"skip\":null}","call_id":"call_LvDkGvFJuq6LXmfuhEi0BYbg","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"grep","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":8707.436400000006,"ttft":1849.053899999999,"completedAt":1789295315551,"contextSnapshot":{"promptTokens":44671,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"c39e2aff","parentId":"5d51414a","timestamp":"2026-09-13T10:28:35.557Z","message":{"role":"toolResult","toolCallId":"call_zgZOmXxYwhchEGFCtU0KuCfL|fc_07ba52f17df332c2016aa67ad3787087d2a33d17105698eb08","toolName":"read","content":[{"type":"text","text":"impl PtySession {\n…\n    pub fn spawn_program_in_env<P, A, S, D, E, K, V>(\n…\n    {\n…\n    }\n\n    /// Take the master reader. Returns [`SurfaceError::ReaderTaken`] on the\n    /// second call — output is drained by exactly one consumer (the A2 drain\n    /// thread in the hosted path; a test consumer otherwise).\n    pub fn take_reader(&self) -> Result<Box<dyn Read + Send>, SurfaceError> {\n        self.reader\n            .lock()\n            .unwrap()\n            .take()\n            .ok_or(SurfaceError::ReaderTaken)\n    }\n\n    /// The shared writer handle — cloned to the A2 DSR drain thread so its\n    /// auto-answer goes through the same serialized writer the caller injects\n    /// input through (no torn writes).\n    pub(crate) fn shared_writer(&self) -> SharedWriter {\n        Arc::clone(&self.writer)\n    }\n\n    /// Poll for child exit without blocking.\n    pub fn try_wait(&self) -> Result<Option<ExitStatus>, SurfaceError> {\n        self.child\n            .lock()\n            .unwrap()\n            .try_wait()\n            .map_err(SurfaceError::Io)\n    }\n\n    /// Block until the child exits, yielding its status.\n    pub fn wait(&self) -> Result<ExitStatus, SurfaceError> {\n        self.child.lock().unwrap().wait().map_err(SurfaceError::Io)\n    }\n\n    /// Terminate the child. Safe to call from another thread while `wait`\n    /// blocks (uses the split `ChildKiller`). Confirm the reap via\n    /// [`PtySession::wait`] / [`PtySession::try_wait`].\n    pub fn kill(&self) -> Result<(), SurfaceError> {\n        let res = self.killer.lock().unwrap().kill();\n        #[cfg(windows)]\n        {\n            // portable-pty 0.8's Windows `ChildKiller` returns the spurious\n            // `last_os_error` even when `TerminateProcess` *succeeds* (the\n            // upstream success check is inverted; `WinChild::kill` swallows it,\n            // the cloned killer does not). Its Result is therefore untrustworthy\n            // — kill is best-effort; confirm death by reaping.\n            let _ = res;\n            Ok(())\n        }\n        #[cfg(unix)]\n        {\n            res.map_err(SurfaceError::Io)\n        }\n    }\n\n    /// The child's OS process id, if the backend exposes one.\n    pub fn process_id(&self) -> Option<u32> {\n        self.pid\n    }\n}\n\n// [impl->REQ-TERM-1]\nimpl SessionSurface for PtySession {\n    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n        let mut w = self.writer.lock().unwrap();\n        w.write_all(bytes).map_err(SurfaceError::Io)?;\n        w.flush().map_err(SurfaceError::Io)\n    }\n\n    fn resize(&self, size: SurfaceSize) -> Result<(), SurfaceError> {\n        // Hold the master lock so resize never races a concurrent reader/resize\n        // (Spike #5: resize races the reader on Windows).\n        self.master\n            .lock()\n            .unwrap()\n            .resize(size.into())\n            .map_err(|e| SurfaceError::Io(io_other(e)))\n    }\n}\n\n[Showing lines 173-246 of 258. Use :247 to continue]"}],"details":{"truncation":{"content":"    }\r\n\r\n    /// Take the master reader. Returns [`SurfaceError::ReaderTaken`] on the\r\n    /// second call — output is drained by exactly one consumer (the A2 drain\r\n    /// thread in the hosted path; a test consumer otherwise).\r\n    pub fn take_reader(&self) -> Result<Box<dyn Read + Send>, SurfaceError> {\r\n        self.reader\r\n            .lock()\r\n            .unwrap()\r\n            .take()\r\n            .ok_or(SurfaceError::ReaderTaken)\r\n    }\r\n\r\n    /// The shared writer handle — cloned to the A2 DSR drain thread so its\r\n    /// auto-answer goes through the same serialized writer the caller injects\r\n    /// input through (no torn writes).\r\n    pub(crate) fn shared_writer(&self) -> SharedWriter {\r\n        Arc::clone(&self.writer)\r\n    }\r\n\r\n    /// Poll for child exit without blocking.\r\n    pub fn try_wait(&self) -> Result<Option<ExitStatus>, SurfaceError> {\r\n        self.child\r\n            .lock()\r\n            .unwrap()\r\n            .try_wait()\r\n            .map_err(SurfaceError::Io)\r\n    }\r\n\r\n    /// Block until the child exits, yielding its status.\r\n    pub fn wait(&self) -> Result<ExitStatus, SurfaceError> {\r\n        self.child.lock().unwrap().wait().map_err(SurfaceError::Io)\r\n    }\r\n\r\n    /// Terminate the child. Safe to call from another thread while `wait`\r\n    /// blocks (uses the split `ChildKiller`). Confirm the reap via\r\n    /// [`PtySession::wait`] / [`PtySession::try_wait`].\r\n    pub fn kill(&self) -> Result<(), SurfaceError> {\r\n        let res = self.killer.lock().unwrap().kill();\r\n        #[cfg(windows)]\r\n        {\r\n            // portable-pty 0.8's Windows `ChildKiller` returns the spurious\r\n            // `last_os_error` even when `TerminateProcess` *succeeds* (the\r\n            // upstream success check is inverted; `WinChild::kill` swallows it,\r\n            // the cloned killer does not). Its Result is therefore untrustworthy\r\n            // — kill is best-effort; confirm death by reaping.\r\n            let _ = res;\r\n            Ok(())\r\n        }\r\n        #[cfg(unix)]\r\n        {\r\n            res.map_err(SurfaceError::Io)\r\n        }\r\n    }\r\n\r\n    /// The child's OS process id, if the backend exposes one.\r\n    pub fn process_id(&self) -> Option<u32> {\r\n        self.pid\r\n    }\r\n}\r\n\r\n// [impl->REQ-TERM-1]\r\nimpl SessionSurface for PtySession {\r\n    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\r\n        let mut w = self.writer.lock().unwrap();\r\n        w.write_all(bytes).map_err(SurfaceError::Io)?;\r\n        w.flush().map_err(SurfaceError::Io)\r\n    }\r\n\r\n    fn resize(&self, size: SurfaceSize) -> Result<(), SurfaceError> {\r\n        // Hold the master lock so resize never races a concurrent reader/resize\r\n        // (Spike #5: resize races the reader on Windows).\r\n        self.master\r\n            .lock()\r","truncated":true,"truncatedBy":"lines","totalLines":86,"totalBytes":2787,"outputLines":74,"outputBytes":2787,"lastLinePartial":false,"firstLineExceedsLimit":false},"totalLines":258,"displayContent":{"text":"impl PtySession {\n…\n    pub fn spawn_program_in_env<P, A, S, D, E, K, V>(\n…\n    {\n…\n    }\r\n\r\n    /// Take the master reader. Returns [`SurfaceError::ReaderTaken`] on the\r\n    /// second call — output is drained by exactly one consumer (the A2 drain\r\n    /// thread in the hosted path; a test consumer otherwise).\r\n    pub fn take_reader(&self) -> Result<Box<dyn Read + Send>, SurfaceError> {\r\n        self.reader\r\n            .lock()\r\n            .unwrap()\r\n            .take()\r\n            .ok_or(SurfaceError::ReaderTaken)\r\n    }\r\n\r\n    /// The shared writer handle — cloned to the A2 DSR drain thread so its\r\n    /// auto-answer goes through the same serialized writer the caller injects\r\n    /// input through (no torn writes).\r\n    pub(crate) fn shared_writer(&self) -> SharedWriter {\r\n        Arc::clone(&self.writer)\r\n    }\r\n\r\n    /// Poll for child exit without blocking.\r\n    pub fn try_wait(&self) -> Result<Option<ExitStatus>, SurfaceError> {\r\n        self.child\r\n            .lock()\r\n            .unwrap()\r\n            .try_wait()\r\n            .map_err(SurfaceError::Io)\r\n    }\r\n\r\n    /// Block until the child exits, yielding its status.\r\n    pub fn wait(&self) -> Result<ExitStatus, SurfaceError> {\r\n        self.child.lock().unwrap().wait().map_err(SurfaceError::Io)\r\n    }\r\n\r\n    /// Terminate the child. Safe to call from another thread while `wait`\r\n    /// blocks (uses the split `ChildKiller`). Confirm the reap via\r\n    /// [`PtySession::wait`] / [`PtySession::try_wait`].\r\n    pub fn kill(&self) -> Result<(), SurfaceError> {\r\n        let res = self.killer.lock().unwrap().kill();\r\n        #[cfg(windows)]\r\n        {\r\n            // portable-pty 0.8's Windows `ChildKiller` returns the spurious\r\n            // `last_os_error` even when `TerminateProcess` *succeeds* (the\r\n            // upstream success check is inverted; `WinChild::kill` swallows it,\r\n            // the cloned killer does not). Its Result is therefore untrustworthy\r\n            // — kill is best-effort; confirm death by reaping.\r\n            let _ = res;\r\n            Ok(())\r\n        }\r\n        #[cfg(unix)]\r\n        {\r\n            res.map_err(SurfaceError::Io)\r\n        }\r\n    }\r\n\r\n    /// The child's OS process id, if the backend exposes one.\r\n    pub fn process_id(&self) -> Option<u32> {\r\n        self.pid\r\n    }\r\n}\r\n\r\n// [impl->REQ-TERM-1]\r\nimpl SessionSurface for PtySession {\r\n    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\r\n        let mut w = self.writer.lock().unwrap();\r\n        w.write_all(bytes).map_err(SurfaceError::Io)?;\r\n        w.flush().map_err(SurfaceError::Io)\r\n    }\r\n\r\n    fn resize(&self, size: SurfaceSize) -> Result<(), SurfaceError> {\r\n        // Hold the master lock so resize never races a concurrent reader/resize\r\n        // (Spike #5: resize races the reader on Windows).\r\n        self.master\r\n            .lock()\r\n            .unwrap()\n            .resize(size.into())\n            .map_err(|e| SurfaceError::Io(io_other(e)))\n    }\n}","startLine":51,"lineNumbers":[51,null,136,null,151,null,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]},"fileSize":10388,"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-term\\src\\pty.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":258,"totalBytes":2787,"outputLines":74,"outputBytes":2787,"shownRange":{"start":173,"end":246},"nextOffset":247}}},"isError":false,"timestamp":1789295315557}}
{"type":"message","id":"b552bd32","parentId":"c39e2aff","timestamp":"2026-09-13T10:28:35.563Z","message":{"role":"toolResult","toolCallId":"call_YHsfW71T8210ZYZlNRtvgpXV|fc_07ba52f17df332c2016aa67ad3787c87d2b9836dd821624502","toolName":"grep","content":[{"type":"text","text":" 236|    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*237|        let mut w = self.writer.lock().unwrap();\n*238|        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239|        w.flush().map_err(SurfaceError::Io)\n 240|    }\n 241|"}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-term\\src\\pty.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":[".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","count":2}],"truncated":false,"displayContent":" 236│    fn write_input(&self, bytes: &[u8]) -> Result<(), SurfaceError> {\n*237│        let mut w = self.writer.lock().unwrap();\n*238│        w.write_all(bytes).map_err(SurfaceError::Io)?;\n 239│        w.flush().map_err(SurfaceError::Io)\n 240│    }\n 241│"},"isError":false,"timestamp":1789295315563}}
{"type":"message","id":"284e8e68","parentId":"b552bd32","timestamp":"2026-09-13T10:28:35.566Z","message":{"role":"toolResult","toolCallId":"call_LvDkGvFJuq6LXmfuhEi0BYbg|fc_07ba52f17df332c2016aa67ad3788087d2aa9d0ebb8f21fa4d","toolName":"grep","content":[{"type":"text","text":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## translation.rs\n 115|            // `ctrl+[` → ESC, etc. Only single-char chords are mapped.\n*116|            if let Some(rest) = lower.strip_prefix(\"ctrl+\").or_else(|| lower.strip_prefix(\"c-\")) {\n 117|                return ctrl_byte(rest).map(|b| vec![b]);\n 118|            }\n 119|            // A single literal character → its own bytes (e.g. `{key:\"y\"}`)."}],"details":{"scopePath":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs, .worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.worktrees\\300-input-acceptance\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":1,"fileCount":1,"files":[".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs"],"fileMatches":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","count":1}],"truncated":false,"displayContent":"# .worktrees/300-input-acceptance/crates/spt-daemon/src/\n## translation.rs\n 115│            // `ctrl+[` → ESC, etc. Only single-char chords are mapped.\n*116│            if let Some(rest) = lower.strip_prefix(\"ctrl+\").or_else(|| lower.strip_prefix(\"c-\")) {\n 117│                return ctrl_byte(rest).map(|b| vec![b]);\n 118│            }\n 119│            // A single literal character → its own bytes (e.g. `{key:\"y\"}`)."},"isError":false,"timestamp":1789295315565}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_TfnXuP4aboKgps9E2xsYoBnB|fc_07ba52f17df332c2016aa67ad6757887d2be9ced6e58a1662d","toolName":"yield","startedAt":"2026-09-13T10:29:46.629Z"},"id":"018387f7","parentId":"284e8e68","timestamp":"2026-09-13T10:29:46.629Z"}
{"type":"message","id":"b949c115","parentId":"018387f7","timestamp":"2026-09-13T10:29:46.629Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_TfnXuP4aboKgps9E2xsYoBnB|fc_07ba52f17df332c2016aa67ad6757887d2be9ced6e58a1662d","name":"yield","arguments":{"type":null,"data":{"summary":"Own-delivery custody should be attached to the existing per-session InputWriter FIFO and finalized only after successful physical writes, with translation-origin tags applied before enqueue. Current checkout has KeyCmd::Text, not Write, and no separate typeahead/post-commit delivery path. Controller-floor flushes must remain explicitly non-delivery. No files changed or validation commands run.","files":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","description":"HostedSession/InputWriter and physical writer seam: 4109–4277; controller floor flush: 4588–4598; translation construction: 4630–4697; settle probe: 4757–4783; text chunking: 4843–4859; sequence application: 4884–4944; worker/re-drive: 4952–5182; writer construction: 8314–8320; controller input: 8746–8848."},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","description":"Command forms 64–79; exact key-to-byte mapping 93–155; controller-only InjectFloor state 169–217; stdout JSON decode 276–293; binary stdin write (not PTY delivery) 309–317."},{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","description":"Shared writer handle 191–196 and physical SessionSurface::write_input 236–240 establish locking and success/partial-failure limits."},{"path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md","description":"Section 8 explicitly requires exclusion using bytes core actually wrote, not payload shape."}],"architecture":"Translation commands and controller input share one bounded, nonblocking InputWriter FIFO. The lone broker input_writer drains raw Vec<u8> records into PtySession::write_input; origin and sequence metadata are currently absent. Translation floor buffering separates controller bytes during injection, but does not retain delivery history. The InputWriter survives translation respawn and therefore is the minimal session-lifetime custody owner.","report":"## Concrete minimal recording seam\n\nChange the internal InputRecord representation from raw Vec<u8> to a tagged record identifying ordinary/controller/control bytes versus translation-delivery bytes, with a per-delivery/attempt identity and sequence boundaries where needed. Keep the existing FIFO and single physical writer. Attach a small session-local shared delivery ledger to InputWriter, accessible through HostedSession.input; do not put it on Translation, because build_translation replaces that child/state on respawn while reusing the existing InputWriter (4630–4697).\n\nTag delivery at drive_one_sequence's Key and Text branches. Preserve exact ordered bytes, including text split across chunk records and mapped key bytes; retain Text-versus-Key information so semantic candidates can be related to their actual byte source rather than guessing from event-envelope shape. In input_writer, record successful writes only after SessionSurface::write_input returns Ok. A dropped enqueue, disconnected writer, or failed write must not create a claim that the whole delivery landed. Propagate/retain an attempt-invalid marker if any tagged chunk fails enqueue or write; otherwise later successful chunks must not accidentally certify the omitted full payload. Existing bool enqueue results are currently ignored at all production callsites.\n\nA commit marker must be ordered in the same FIFO if used to finalize a full sequence, before flushing controller input. It writes no PTY bytes. Do not silently treat a dropped commit marker as a completed record. However, commit-only publication has a receipt race: Enter can cause USER_INPUT before the binary emits commit or the writer processes its marker. Successful text/submit boundaries need to become visible incrementally rather than relying solely on a future commit. There remains a narrower interval between a physical write making input visible to the harness and the writer publishing successful completion; resolving that without delaying input/receipt is not proven by the existing API. Do not mark enqueued bytes as successful to conceal this interval.\n\n## Every production broker enqueue/write site\n\n| Location | Bytes | Custody |\n|---|---|---|\n| broker.rs:4907–4910, drive_one_sequence Key | key_to_bytes result | Translation delivery; record exact successful bytes with sequence identity. Includes Enter/CR and choreography/control keys. Unknown key emits nothing. |\n| broker.rs:4912–4916 → 4843–4846, Text / enqueue_text_chunked | UTF-8 text split by chunk_text | Translation delivery; accumulate successful ordered chunks without assuming one chunk equals one submission. |\n| broker.rs:4923–4926, Commit | None | Logical terminator only; currently any parsed Commit, including commit:false, ends the sequence. Does not establish successful enqueue/write. |\n| broker.rs:4593, flush_inject_floor | Controller bytes buffered while floor held | Never own delivery. Same helper runs after commit, timeout, disconnect, and event-send failure. |\n| broker.rs:4769, settle_before_inject | INJECT_SETTLE_PROBE | Core terminal readiness control, not peer-message text; keep separate from delivery candidates. |\n| broker.rs:8818, dispatch_input journaled branch | Decoded InputReq bytes | Ordinary input, not own translation delivery. Includes human, RC, shell/system and legacy paths. |\n| broker.rs:8843, dispatch_input unjournaled branch | Decoded InputReq bytes | Same non-delivery classification. |\n| broker.rs:4274–4277, input_writer | All accepted FIFO records | Only broker physical write call; classify before this convergence, confirm success here. |\n\nThe broker's single write_input caller is not the sole physical writer in all of spt-term: PtySession::shared_writer (pty.rs:191–196) is cloned for drain DSR auto-answer. Those terminal replies serialize on the same underlying writer but are not peer-message delivery. Hooking all low-level writer traffic would capture unrelated protocol bytes and lose origin discrimination.\n\n## Post-commit, release, typeahead and retry\n\nIn this inspected checkout, KeyCmd has Key, Text, Delay, Commit (translation.rs:64–79); there is no Write variant. Targeted search followed by a broader crates search found no typeahead/post_commit implementation. The available equivalent is InjectFloor.buffer: it contains controller-origin input exclusively, taken by take_or_release in a loop and enqueued after the injected commands (translation.rs:169–217; broker.rs:4588–4598). This is human/input custody, not an extension of the core delivery.\n\nAfter Commit, drive_one_sequence immediately flushes/releases and returns. It does not apply remaining commands as post-commit delivery. run_inject_worker drains/discards any pending cmd_rx records before the next event (5002–5005). An actual echo retry calls drive_one_sequence again (5019–5068), so instrumenting its two translated byte branches covers re-drive without another write seam. Timed-out/disconnected attempts may already have written bytes; committed status alone does not describe whether a submission occurred. A queued EVENT or successful child.send is even earlier and is not delivery proof.\n\n## Existing normalization and state\n\nThere is no delivery-to-USER_INPUT normalizer in these target files. Text passes from serde JSON string to text.into_bytes unchanged; chunk_text preserves concatenation exactly. TranslationChild trims the outer JSON line, not the decoded text value (276–293). key_to_bytes trims/lowercases the key name and maps enter/return/cr to CR, newline/lf to LF, named terminal keys to CSI and ctrl chords to control bytes (93–155). These are command decoding rules, not evidence that a reported payload retained terminal framing.\n\nBracketed paste wrapping is explicitly deferred in broker.rs:694–696 and 750–754. A broader search finds wrap_bracketed_paste in crates/spt/src/rc.rs:651–667, but that wraps controller clipboard input and is not a delivery normalizer. No existing stripping of ESC[200~/ESC[201~ or CR-to-LF conversion was found in the delivery path. Any new exact-byte-derived report comparison normalization must be stated as an added comparison rule, not reused existing behavior. Do not broadly trim whitespace, strip payload-looking wrappers or match a prefix.\n\nThe existing sent_text Vec (4899, 4915; SeqOutcome at 4865–4875) concatenates Text commands before enqueue, excludes all Keys, and is used solely for echo checking. It is therefore neither a complete actual byte record nor proof of successful writes. echo_verified (781–786) checks only a bounded text head in output; echo_verify_after (4812–4835) may assume success when output is absent. Neither is suitable authority for full-byte custody. InputWriter.backpressure only reports queue pressure. InjectFloor holds a boolean and controller Vec. Worker-local respooled_once/strikes serve retry bookkeeping, not provenance.\n\n## Lock ordering\n\n- dispatch_input clones input/translation/log under sessions, releases sessions, then checks controller under log, releases log, sends the content-free child ping, and separately locks floor for buffer_if_held (8746–8804).\n- flush_inject_floor calls take_or_release under floor and releases it before enqueue (4588–4598). Preserve this; never record these bytes as delivery just because they were emitted by the inject worker.\n- drive_one_sequence opens floor under a short lock, releases it before child.send and all enqueue operations. TranslationChild::send holds only its stdin mutex during the binary write.\n- input_writer currently takes no sessions/log/floor lock. PtySession::write_input takes its private writer mutex, executes write_all and flush, and returns (pty.rs:236–240).\n- Recommended ledger locking is after the physical call returns and only for short in-memory updates; lookup clones the session's input handle before consulting the ledger. Avoid acquiring sessions/log/floor from the writer and never hold a ledger mutex across a potentially wedged PTY write.\n\n## Proof limits\n\nThis is static source mapping only, as requested; no builds/tests/validation or runtime experiments were performed. write_input Ok establishes write_all plus flush completed, not that the terminal/harness accepted the same semantic payload. An Err can occur after a partial write, or after write_all succeeded but flush failed; the current Result exposes no committed byte count. Exact custody for those partial/error bytes requires a deeper write-accounting API, not a broker-only assumption. Terminal editing keys, stash/restore, bracketed paste interpretation and downstream text transformations can make raw stream bytes differ from reported submission text. Full command-byte recording preserves evidence but is not a general terminal editor emulator. Byte equality also cannot distinguish a later human deliberately submitting text identical to a prior core delivery; session-local exact-byte exclusion has that intrinsic ceiling. No adapter changes or token mechanism are necessary for the recommended recording seam."},"error":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":1303,"output":2318,"cacheRead":44544,"cacheWrite":0,"totalTokens":48165,"reasoningTokens":9,"cost":{"input":0.013030000000000002,"output":0.1159,"cacheRead":0.044544,"cacheWrite":0,"total":0.17347400000000002}},"stopReason":"toolUse","timestamp":1789295315571,"responseId":"resp_07ba52f17df332c2016aa67ad3dc5887d29b3be1f361acfa4b","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_07ba52f17df332c2016aa67ad5091487d2af76340b202d0a56","type":"reasoning","content":[],"encrypted_content":"gAAAAABqpnrW3B3PguGjorB08q6V3hB9O_mvB9EUFIUt8WJrTgwn-Zxe1zVTXhk6Un0DjxBqOIYDU4UuYBUJOkY8ovr3RtcM2PtVtJWTuBe8MoEOhRjgTUZuHVaUEK9Dka1Z2hCmEFK8Da20-YJnX6gx-n2uaTsgWQlbIXDbNY_wmZnsI7iD24QD4a2Q6Wt1JYRAkxgtdpERn8lgmd865tbfatYqZxrWPeOL7YqUkKwn1p9IzVyWAXRTr4DV42g6GF0wB1gHPDIctwL73MKgTWYOSoAMG_rxLNmqmpuaFrvpEv4PG4CFgUqS3AJ7S1Pb7qZStngZSOOHan2Wxx9xLBkDSWwYTpMppV7xT4qpY4dz9leKonOjN4uO1clOFucFsYaPVuMdaYmLAUKXHnC4WliA_vGRR7dx65PYe1smukxJAybWwAdsucemlaCiumKG_yBBjBcv1v1ma43rxYJ9tvw_FyJGGhYmKmPdMhAC1t6l6hefNLF4378oR1DsAGUeexoLyuTeygUm7Kg8st2rRiZGEWC1hY78pOPDOuOCwRwP-z5o5gY2ijcLhpJ9RuhnWMlxtOIF8G2-7GviAvy1qfcb5JVgcMtDa6l6iH8yd6FiXxVTrPEb_j-hBfMRd5Ud6faSJHVAQMPrd0LKos991a595cDjyxJX_-K8IwDh6NsAX_hFD7ZIW3XlGh9jkAi7woeqFw43VEJEA3zcM-FBew-k0qnyRQkQB31AASVqSxhz4vqV8f21690zS98njxlKzq_lIv18rz1V3kyUhngl1ASOtF9tjvO0ULmpF7vcxxg91YQg-ScErGCIxdgQumTOafLstTKcgH96OC30mSat31YUylo4Lh7vcfOH6hAJKwPV2IxufXRei35zEV5jL-ChKijJMP63QD9oOL8R4c4SBW0i76pqLilj6XD8ppa0x7oLoAvGsdncPrD0aA_LNxs2W3D8rRvRGYubvc-4dZQx7PihfvVmDYBGO80evnlatSMILJI1KfIA4B4ElOgNtA21KONlvllNFnz4UiHsZikB9gLVP4QkbVYBMua6Gx9SNHNPYN3-I9QzJ-vFeAcRV8d7Fv5CjAa7krEBxmUtLJ6u9PDjUi5SwIwqefoplOrH103PY5fX3mWBx1462DiIcyPsrWHR0GYw7M75gmCR7nZJ-KXw7MkNzHSt_s3dlmzg-PJWgS6J0CDdGCP2ZQ1CMKh4bpdZW56yzRSNCXPA_p3BMtmYjixoadY5YG3NJkU4aGVcOQk1YJxlBCnvgfCHFGJbIkoWMFi4-WSIjEmJ7I9R5Tm0RfzLHU9cZoAu5Kr6kICcJf2FV4jXAaY=","internal_chat_message_metadata_passthrough":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"summary":[],"metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}},{"id":"fc_07ba52f17df332c2016aa67ad6757887d2be9ced6e58a1662d","type":"function_call","status":"completed","arguments":"{\"type\":null,\"data\":{\"summary\":\"Own-delivery custody should be attached to the existing per-session InputWriter FIFO and finalized only after successful physical writes, with translation-origin tags applied before enqueue. Current checkout has KeyCmd::Text, not Write, and no separate typeahead/post-commit delivery path. Controller-floor flushes must remain explicitly non-delivery. No files changed or validation commands run.\",\"files\":[{\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs\",\"description\":\"HostedSession/InputWriter and physical writer seam: 4109–4277; controller floor flush: 4588–4598; translation construction: 4630–4697; settle probe: 4757–4783; text chunking: 4843–4859; sequence application: 4884–4944; worker/re-drive: 4952–5182; writer construction: 8314–8320; controller input: 8746–8848.\"},{\"path\":\".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs\",\"description\":\"Command forms 64–79; exact key-to-byte mapping 93–155; controller-only InjectFloor state 169–217; stdout JSON decode 276–293; binary stdin write (not PTY delivery) 309–317.\"},{\"path\":\".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs\",\"description\":\"Shared writer handle 191–196 and physical SessionSurface::write_input 236–240 establish locking and success/partial-failure limits.\"},{\"path\":\".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md\",\"description\":\"Section 8 explicitly requires exclusion using bytes core actually wrote, not payload shape.\"}],\"architecture\":\"Translation commands and controller input share one bounded, nonblocking InputWriter FIFO. The lone broker input_writer drains raw Vec<u8> records into PtySession::write_input; origin and sequence metadata are currently absent. Translation floor buffering separates controller bytes during injection, but does not retain delivery history. The InputWriter survives translation respawn and therefore is the minimal session-lifetime custody owner.\",\"report\":\"## Concrete minimal recording seam\\n\\nChange the internal InputRecord representation from raw Vec<u8> to a tagged record identifying ordinary/controller/control bytes versus translation-delivery bytes, with a per-delivery/attempt identity and sequence boundaries where needed. Keep the existing FIFO and single physical writer. Attach a small session-local shared delivery ledger to InputWriter, accessible through HostedSession.input; do not put it on Translation, because build_translation replaces that child/state on respawn while reusing the existing InputWriter (4630–4697).\\n\\nTag delivery at drive_one_sequence's Key and Text branches. Preserve exact ordered bytes, including text split across chunk records and mapped key bytes; retain Text-versus-Key information so semantic candidates can be related to their actual byte source rather than guessing from event-envelope shape. In input_writer, record successful writes only after SessionSurface::write_input returns Ok. A dropped enqueue, disconnected writer, or failed write must not create a claim that the whole delivery landed. Propagate/retain an attempt-invalid marker if any tagged chunk fails enqueue or write; otherwise later successful chunks must not accidentally certify the omitted full payload. Existing bool enqueue results are currently ignored at all production callsites.\\n\\nA commit marker must be ordered in the same FIFO if used to finalize a full sequence, before flushing controller input. It writes no PTY bytes. Do not silently treat a dropped commit marker as a completed record. However, commit-only publication has a receipt race: Enter can cause USER_INPUT before the binary emits commit or the writer processes its marker. Successful text/submit boundaries need to become visible incrementally rather than relying solely on a future commit. There remains a narrower interval between a physical write making input visible to the harness and the writer publishing successful completion; resolving that without delaying input/receipt is not proven by the existing API. Do not mark enqueued bytes as successful to conceal this interval.\\n\\n## Every production broker enqueue/write site\\n\\n| Location | Bytes | Custody |\\n|---|---|---|\\n| broker.rs:4907–4910, drive_one_sequence Key | key_to_bytes result | Translation delivery; record exact successful bytes with sequence identity. Includes Enter/CR and choreography/control keys. Unknown key emits nothing. |\\n| broker.rs:4912–4916 → 4843–4846, Text / enqueue_text_chunked | UTF-8 text split by chunk_text | Translation delivery; accumulate successful ordered chunks without assuming one chunk equals one submission. |\\n| broker.rs:4923–4926, Commit | None | Logical terminator only; currently any parsed Commit, including commit:false, ends the sequence. Does not establish successful enqueue/write. |\\n| broker.rs:4593, flush_inject_floor | Controller bytes buffered while floor held | Never own delivery. Same helper runs after commit, timeout, disconnect, and event-send failure. |\\n| broker.rs:4769, settle_before_inject | INJECT_SETTLE_PROBE | Core terminal readiness control, not peer-message text; keep separate from delivery candidates. |\\n| broker.rs:8818, dispatch_input journaled branch | Decoded InputReq bytes | Ordinary input, not own translation delivery. Includes human, RC, shell/system and legacy paths. |\\n| broker.rs:8843, dispatch_input unjournaled branch | Decoded InputReq bytes | Same non-delivery classification. |\\n| broker.rs:4274–4277, input_writer | All accepted FIFO records | Only broker physical write call; classify before this convergence, confirm success here. |\\n\\nThe broker's single write_input caller is not the sole physical writer in all of spt-term: PtySession::shared_writer (pty.rs:191–196) is cloned for drain DSR auto-answer. Those terminal replies serialize on the same underlying writer but are not peer-message delivery. Hooking all low-level writer traffic would capture unrelated protocol bytes and lose origin discrimination.\\n\\n## Post-commit, release, typeahead and retry\\n\\nIn this inspected checkout, KeyCmd has Key, Text, Delay, Commit (translation.rs:64–79); there is no Write variant. Targeted search followed by a broader crates search found no typeahead/post_commit implementation. The available equivalent is InjectFloor.buffer: it contains controller-origin input exclusively, taken by take_or_release in a loop and enqueued after the injected commands (translation.rs:169–217; broker.rs:4588–4598). This is human/input custody, not an extension of the core delivery.\\n\\nAfter Commit, drive_one_sequence immediately flushes/releases and returns. It does not apply remaining commands as post-commit delivery. run_inject_worker drains/discards any pending cmd_rx records before the next event (5002–5005). An actual echo retry calls drive_one_sequence again (5019–5068), so instrumenting its two translated byte branches covers re-drive without another write seam. Timed-out/disconnected attempts may already have written bytes; committed status alone does not describe whether a submission occurred. A queued EVENT or successful child.send is even earlier and is not delivery proof.\\n\\n## Existing normalization and state\\n\\nThere is no delivery-to-USER_INPUT normalizer in these target files. Text passes from serde JSON string to text.into_bytes unchanged; chunk_text preserves concatenation exactly. TranslationChild trims the outer JSON line, not the decoded text value (276–293). key_to_bytes trims/lowercases the key name and maps enter/return/cr to CR, newline/lf to LF, named terminal keys to CSI and ctrl chords to control bytes (93–155). These are command decoding rules, not evidence that a reported payload retained terminal framing.\\n\\nBracketed paste wrapping is explicitly deferred in broker.rs:694–696 and 750–754. A broader search finds wrap_bracketed_paste in crates/spt/src/rc.rs:651–667, but that wraps controller clipboard input and is not a delivery normalizer. No existing stripping of ESC[200~/ESC[201~ or CR-to-LF conversion was found in the delivery path. Any new exact-byte-derived report comparison normalization must be stated as an added comparison rule, not reused existing behavior. Do not broadly trim whitespace, strip payload-looking wrappers or match a prefix.\\n\\nThe existing sent_text Vec (4899, 4915; SeqOutcome at 4865–4875) concatenates Text commands before enqueue, excludes all Keys, and is used solely for echo checking. It is therefore neither a complete actual byte record nor proof of successful writes. echo_verified (781–786) checks only a bounded text head in output; echo_verify_after (4812–4835) may assume success when output is absent. Neither is suitable authority for full-byte custody. InputWriter.backpressure only reports queue pressure. InjectFloor holds a boolean and controller Vec. Worker-local respooled_once/strikes serve retry bookkeeping, not provenance.\\n\\n## Lock ordering\\n\\n- dispatch_input clones input/translation/log under sessions, releases sessions, then checks controller under log, releases log, sends the content-free child ping, and separately locks floor for buffer_if_held (8746–8804).\\n- flush_inject_floor calls take_or_release under floor and releases it before enqueue (4588–4598). Preserve this; never record these bytes as delivery just because they were emitted by the inject worker.\\n- drive_one_sequence opens floor under a short lock, releases it before child.send and all enqueue operations. TranslationChild::send holds only its stdin mutex during the binary write.\\n- input_writer currently takes no sessions/log/floor lock. PtySession::write_input takes its private writer mutex, executes write_all and flush, and returns (pty.rs:236–240).\\n- Recommended ledger locking is after the physical call returns and only for short in-memory updates; lookup clones the session's input handle before consulting the ledger. Avoid acquiring sessions/log/floor from the writer and never hold a ledger mutex across a potentially wedged PTY write.\\n\\n## Proof limits\\n\\nThis is static source mapping only, as requested; no builds/tests/validation or runtime experiments were performed. write_input Ok establishes write_all plus flush completed, not that the terminal/harness accepted the same semantic payload. An Err can occur after a partial write, or after write_all succeeded but flush failed; the current Result exposes no committed byte count. Exact custody for those partial/error bytes requires a deeper write-accounting API, not a broker-only assumption. Terminal editing keys, stash/restore, bracketed paste interpretation and downstream text transformations can make raw stream bytes differ from reported submission text. Full command-byte recording preserves evidence but is not a general terminal editor emulator. Byte equality also cannot distinguish a later human deliberately submitting text identical to a prior core delivery; session-local exact-byte exclusion has that intrinsic ceiling. No adapter changes or token mechanism are necessary for the recommended recording seam.\"},\"error\":null}","call_id":"call_TfnXuP4aboKgps9E2xsYoBnB","internal_chat_message_metadata_passthrough":{"create_time":1789295316.084192,"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"},"name":"yield","metadata":{"turn_id":"fcdd2d9a-5275-47f9-8165-505340d88616"}}]},"duration":71054.42240000004,"ttft":1481.5399000000325,"completedAt":1789295386628,"contextSnapshot":{"promptTokens":45847,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"8ba3b03f","parentId":"b949c115","timestamp":"2026-09-13T10:29:46.636Z","message":{"role":"toolResult","toolCallId":"call_TfnXuP4aboKgps9E2xsYoBnB|fc_07ba52f17df332c2016aa67ad6757887d2be9ced6e58a1662d","toolName":"yield","content":[{"type":"text","text":"Result submitted."}],"details":{"data":{"summary":"Own-delivery custody should be attached to the existing per-session InputWriter FIFO and finalized only after successful physical writes, with translation-origin tags applied before enqueue. Current checkout has KeyCmd::Text, not Write, and no separate typeahead/post-commit delivery path. Controller-floor flushes must remain explicitly non-delivery. No files changed or validation commands run.","files":[{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/broker.rs","description":"HostedSession/InputWriter and physical writer seam: 4109–4277; controller floor flush: 4588–4598; translation construction: 4630–4697; settle probe: 4757–4783; text chunking: 4843–4859; sequence application: 4884–4944; worker/re-drive: 4952–5182; writer construction: 8314–8320; controller input: 8746–8848."},{"path":".worktrees/300-input-acceptance/crates/spt-daemon/src/translation.rs","description":"Command forms 64–79; exact key-to-byte mapping 93–155; controller-only InjectFloor state 169–217; stdout JSON decode 276–293; binary stdin write (not PTY delivery) 309–317."},{"path":".worktrees/300-input-acceptance/crates/spt-term/src/pty.rs","description":"Shared writer handle 191–196 and physical SessionSurface::write_input 236–240 establish locking and success/partial-failure limits."},{"path":".worktrees/300-input-acceptance/docs/INPUT-PROVENANCE-CONTRACT.md","description":"Section 8 explicitly requires exclusion using bytes core actually wrote, not payload shape."}],"architecture":"Translation commands and controller input share one bounded, nonblocking InputWriter FIFO. The lone broker input_writer drains raw Vec<u8> records into PtySession::write_input; origin and sequence metadata are currently absent. Translation floor buffering separates controller bytes during injection, but does not retain delivery history. The InputWriter survives translation respawn and therefore is the minimal session-lifetime custody owner.","report":"## Concrete minimal recording seam\n\nChange the internal InputRecord representation from raw Vec<u8> to a tagged record identifying ordinary/controller/control bytes versus translation-delivery bytes, with a per-delivery/attempt identity and sequence boundaries where needed. Keep the existing FIFO and single physical writer. Attach a small session-local shared delivery ledger to InputWriter, accessible through HostedSession.input; do not put it on Translation, because build_translation replaces that child/state on respawn while reusing the existing InputWriter (4630–4697).\n\nTag delivery at drive_one_sequence's Key and Text branches. Preserve exact ordered bytes, including text split across chunk records and mapped key bytes; retain Text-versus-Key information so semantic candidates can be related to their actual byte source rather than guessing from event-envelope shape. In input_writer, record successful writes only after SessionSurface::write_input returns Ok. A dropped enqueue, disconnected writer, or failed write must not create a claim that the whole delivery landed. Propagate/retain an attempt-invalid marker if any tagged chunk fails enqueue or write; otherwise later successful chunks must not accidentally certify the omitted full payload. Existing bool enqueue results are currently ignored at all production callsites.\n\nA commit marker must be ordered in the same FIFO if used to finalize a full sequence, before flushing controller input. It writes no PTY bytes. Do not silently treat a dropped commit marker as a completed record. However, commit-only publication has a receipt race: Enter can cause USER_INPUT before the binary emits commit or the writer processes its marker. Successful text/submit boundaries need to become visible incrementally rather than relying solely on a future commit. There remains a narrower interval between a physical write making input visible to the harness and the writer publishing successful completion; resolving that without delaying input/receipt is not proven by the existing API. Do not mark enqueued bytes as successful to conceal this interval.\n\n## Every production broker enqueue/write site\n\n| Location | Bytes | Custody |\n|---|---|---|\n| broker.rs:4907–4910, drive_one_sequence Key | key_to_bytes result | Translation delivery; record exact successful bytes with sequence identity. Includes Enter/CR and choreography/control keys. Unknown key emits nothing. |\n| broker.rs:4912–4916 → 4843–4846, Text / enqueue_text_chunked | UTF-8 text split by chunk_text | Translation delivery; accumulate successful ordered chunks without assuming one chunk equals one submission. |\n| broker.rs:4923–4926, Commit | None | Logical terminator only; currently any parsed Commit, including commit:false, ends the sequence. Does not establish successful enqueue/write. |\n| broker.rs:4593, flush_inject_floor | Controller bytes buffered while floor held | Never own delivery. Same helper runs after commit, timeout, disconnect, and event-send failure. |\n| broker.rs:4769, settle_before_inject | INJECT_SETTLE_PROBE | Core terminal readiness control, not peer-message text; keep separate from delivery candidates. |\n| broker.rs:8818, dispatch_input journaled branch | Decoded InputReq bytes | Ordinary input, not own translation delivery. Includes human, RC, shell/system and legacy paths. |\n| broker.rs:8843, dispatch_input unjournaled branch | Decoded InputReq bytes | Same non-delivery classification. |\n| broker.rs:4274–4277, input_writer | All accepted FIFO records | Only broker physical write call; classify before this convergence, confirm success here. |\n\nThe broker's single write_input caller is not the sole physical writer in all of spt-term: PtySession::shared_writer (pty.rs:191–196) is cloned for drain DSR auto-answer. Those terminal replies serialize on the same underlying writer but are not peer-message delivery. Hooking all low-level writer traffic would capture unrelated protocol bytes and lose origin discrimination.\n\n## Post-commit, release, typeahead and retry\n\nIn this inspected checkout, KeyCmd has Key, Text, Delay, Commit (translation.rs:64–79); there is no Write variant. Targeted search followed by a broader crates search found no typeahead/post_commit implementation. The available equivalent is InjectFloor.buffer: it contains controller-origin input exclusively, taken by take_or_release in a loop and enqueued after the injected commands (translation.rs:169–217; broker.rs:4588–4598). This is human/input custody, not an extension of the core delivery.\n\nAfter Commit, drive_one_sequence immediately flushes/releases and returns. It does not apply remaining commands as post-commit delivery. run_inject_worker drains/discards any pending cmd_rx records before the next event (5002–5005). An actual echo retry calls drive_one_sequence again (5019–5068), so instrumenting its two translated byte branches covers re-drive without another write seam. Timed-out/disconnected attempts may already have written bytes; committed status alone does not describe whether a submission occurred. A queued EVENT or successful child.send is even earlier and is not delivery proof.\n\n## Existing normalization and state\n\nThere is no delivery-to-USER_INPUT normalizer in these target files. Text passes from serde JSON string to text.into_bytes unchanged; chunk_text preserves concatenation exactly. TranslationChild trims the outer JSON line, not the decoded text value (276–293). key_to_bytes trims/lowercases the key name and maps enter/return/cr to CR, newline/lf to LF, named terminal keys to CSI and ctrl chords to control bytes (93–155). These are command decoding rules, not evidence that a reported payload retained terminal framing.\n\nBracketed paste wrapping is explicitly deferred in broker.rs:694–696 and 750–754. A broader search finds wrap_bracketed_paste in crates/spt/src/rc.rs:651–667, but that wraps controller clipboard input and is not a delivery normalizer. No existing stripping of ESC[200~/ESC[201~ or CR-to-LF conversion was found in the delivery path. Any new exact-byte-derived report comparison normalization must be stated as an added comparison rule, not reused existing behavior. Do not broadly trim whitespace, strip payload-looking wrappers or match a prefix.\n\nThe existing sent_text Vec (4899, 4915; SeqOutcome at 4865–4875) concatenates Text commands before enqueue, excludes all Keys, and is used solely for echo checking. It is therefore neither a complete actual byte record nor proof of successful writes. echo_verified (781–786) checks only a bounded text head in output; echo_verify_after (4812–4835) may assume success when output is absent. Neither is suitable authority for full-byte custody. InputWriter.backpressure only reports queue pressure. InjectFloor holds a boolean and controller Vec. Worker-local respooled_once/strikes serve retry bookkeeping, not provenance.\n\n## Lock ordering\n\n- dispatch_input clones input/translation/log under sessions, releases sessions, then checks controller under log, releases log, sends the content-free child ping, and separately locks floor for buffer_if_held (8746–8804).\n- flush_inject_floor calls take_or_release under floor and releases it before enqueue (4588–4598). Preserve this; never record these bytes as delivery just because they were emitted by the inject worker.\n- drive_one_sequence opens floor under a short lock, releases it before child.send and all enqueue operations. TranslationChild::send holds only its stdin mutex during the binary write.\n- input_writer currently takes no sessions/log/floor lock. PtySession::write_input takes its private writer mutex, executes write_all and flush, and returns (pty.rs:236–240).\n- Recommended ledger locking is after the physical call returns and only for short in-memory updates; lookup clones the session's input handle before consulting the ledger. Avoid acquiring sessions/log/floor from the writer and never hold a ledger mutex across a potentially wedged PTY write.\n\n## Proof limits\n\nThis is static source mapping only, as requested; no builds/tests/validation or runtime experiments were performed. write_input Ok establishes write_all plus flush completed, not that the terminal/harness accepted the same semantic payload. An Err can occur after a partial write, or after write_all succeeded but flush failed; the current Result exposes no committed byte count. Exact custody for those partial/error bytes requires a deeper write-accounting API, not a broker-only assumption. Terminal editing keys, stash/restore, bracketed paste interpretation and downstream text transformations can make raw stream bytes differ from reported submission text. Full command-byte recording preserves evidence but is not a general terminal editor emulator. Byte equality also cannot distinguish a later human deliberately submitting text identical to a prior core delivery; session-local exact-byte exclusion has that intrinsic ceiling. No adapter changes or token mechanism are necessary for the recommended recording seam."},"status":"success"},"isError":false,"timestamp":1789295386630}}
{"type":"custom_message","customType":"irc:incoming","content":"<irc>\nIncoming IRC message from agent `Main`:\n\nPlease finalize findings now. Plan writer-owned per-session delivery history with success-only bytes and record origin enum, match exact accumulated delivery text payload (exclude paste/commit framing, no payload-shape test). Need enumerate minimal callsites plus mixed human typeahead rules.\nIf response expected, reply via `hub` (`op: \"send\"`, `to: \"Main\"`) when available; otherwise what you `yield` or say last this turn is delivered to `Main` when you stop.\n</irc>","display":true,"details":{"id":"157de18f12c48166","from":"Main","message":"Please finalize findings now. Plan writer-owned per-session delivery history with success-only bytes and record origin enum, match exact accumulated delivery text payload (exclude paste/commit framing, no payload-shape test). Need enumerate minimal callsites plus mixed human typeahead rules."},"attribution":"agent","id":"50f81585","parentId":"8ba3b03f","timestamp":"2026-09-13T10:29:49.771Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_GkmSE5OBCZh6ko3aky2P0q1B|fc_07ba52f17df332c2016aa67b1f9f5887d28f54ea855fe57988","toolName":"hub","startedAt":"2026-09-13T10:29:58.098Z","intent":"Confirming final custody findings"},"id":"c9feda54","parentId":"50f81585","timestamp":"2026-09-13T10:29:58.098Z"}
{"type":"message","id":"3dd51625","parentId":"c9feda54","timestamp":"2026-09-13T10:29:58.099Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_GkmSE5OBCZh6ko3aky2P0q1B|fc_07ba52f17df332c2016aa67b1f9f5887d28f54ea855fe57988","name":"hub","arguments":{"i":"Confirming final custody findings","op":"send","to":"Main","message":"Final structured findings submitted. Minimal tags: drive_one_sequence Key 4907–4910 + Text 4912–4916→enqueue_text_chunked 4843–4846; successful recording at input_writer 4274–4277. Commit 4923–4926 writes no bytes; mark boundary before floor flush. flush_inject_floor 4593 and dispatch_input 8818/8843 MUST remain non-delivery (buffered typeahead is human/input even when worker flushes it). Settle probe 4769 separate control. Checkout has no post-commit applying/typeahead API or Write variant: leftover commands discarded at 5002–5005. No existing CR/paste delivery normalization. Caution: successful Text records alone can publish prior to Enter; commit-only finalization races early USER_INPUT; write_input Err may already have written partial/all bytes (flush error). Full report includes exact ranges and limits."},"intent":"Confirming final custody findings"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":2601,"output":222,"cacheRead":45696,"cacheWrite":0,"totalTokens":48519,"cost":{"input":0.026010000000000002,"output":0.0111,"cacheRead":0.045696,"cacheWrite":0,"total":0.082806}},"stopReason":"toolUse","timestamp":1789295389779,"responseId":"resp_07ba52f17df332c2016aa67b1dffc887d2ad5001e1acef1197","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_07ba52f17df332c2016aa67b1f9f5887d28f54ea855fe57988","type":"function_call","status":"completed","arguments":"{\"i\":\"Confirming final custody findings\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Final structured findings submitted. Minimal tags: drive_one_sequence Key 4907–4910 + Text 4912–4916→enqueue_text_chunked 4843–4846; successful recording at input_writer 4274–4277. Commit 4923–4926 writes no bytes; mark boundary before floor flush. flush_inject_floor 4593 and dispatch_input 8818/8843 MUST remain non-delivery (buffered typeahead is human/input even when worker flushes it). Settle probe 4769 separate control. Checkout has no post-commit applying/typeahead API or Write variant: leftover commands discarded at 5002–5005. No existing CR/paste delivery normalization. Caution: successful Text records alone can publish prior to Enter; commit-only finalization races early USER_INPUT; write_input Err may already have written partial/all bytes (flush error). Full report includes exact ranges and limits.\"}","call_id":"call_GkmSE5OBCZh6ko3aky2P0q1B","internal_chat_message_metadata_passthrough":{"create_time":1789295390.294299,"turn_id":"7430a9ee-5151-4434-a833-5abe2ce00c04"},"name":"hub","metadata":{"turn_id":"7430a9ee-5151-4434-a833-5abe2ce00c04"}}]},"duration":8317.67459999997,"ttft":1863.9271999999764,"completedAt":1789295398097,"contextSnapshot":{"promptTokens":48297,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"message","id":"093b5c6c","parentId":"3dd51625","timestamp":"2026-09-13T10:29:58.101Z","message":{"role":"toolResult","toolCallId":"call_GkmSE5OBCZh6ko3aky2P0q1B|fc_07ba52f17df332c2016aa67b1f9f5887d28f54ea855fe57988","toolName":"hub","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"DeliveryCustody","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1789295398099}}
{"type":"message","id":"fe253133","parentId":"093b5c6c","timestamp":"2026-09-13T10:30:01.500Z","message":{"role":"assistant","content":[{"type":"text","text":"Findings delivered, including all write sites, controller-typeahead exclusion, locking, and success-accounting limits.","textSignature":"{\"v\":1,\"id\":\"msg_07ba52f17df332c2016aa67b27e1ec87d2aed0e18435a81b86\",\"phase\":\"final_answer\"}"}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-6-astra","usage":{"input":413,"output":26,"cacheRead":48128,"cacheWrite":0,"totalTokens":48567,"cost":{"input":0.00413,"output":0.0013000000000000002,"cacheRead":0.048128,"cacheWrite":0,"total":0.053557999999999995}},"stopReason":"stop","timestamp":1789295398108,"responseId":"resp_07ba52f17df332c2016aa67b264f8887d286401ce1ce43b494","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"msg_07ba52f17df332c2016aa67b27e1ec87d2aed0e18435a81b86","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Findings delivered, including all write sites, controller-typeahead exclusion, locking, and success-accounting limits."}],"internal_chat_message_metadata_passthrough":{"create_time":1789295398.502399,"turn_id":"7430a9ee-5151-4434-a833-5abe2ce00c04"},"phase":"final_answer","role":"assistant","metadata":{"turn_id":"7430a9ee-5151-4434-a833-5abe2ce00c04"}}]},"duration":3390.586499999976,"ttft":1792.8113000000012,"completedAt":1789295401499,"contextSnapshot":{"promptTokens":48541,"nonMessageTokens":10276,"compactionEpoch":0}}}
{"type":"custom","customType":"session_exit","data":{"reason":"dispose","kind":"normal","recordedAt":"2026-09-13T10:37:01.528Z"},"id":"dbaacc20","parentId":"fe253133","timestamp":"2026-09-13T10:37:01.528Z"}
