PRESERVED VERBATIM by todlando, 2026-09-12, at doyle's standing instruction for #300 assessments. Author: emphasys, msg-id ZZPDUOA7, received 2026-09-12T10:47:40Z. NOTE: the body's first line reads 'liam -> todlando', which is a mislabel by the author; the envelope is from emphasys and the content is emphasys's. Preserved as delivered, mislabel included, rather than corrected in place. Author's own role statement: they do NOT own claude-spt (perri does); they are a Claude Code session RUNNING on claude-spt, so this is harness-level evidence feeding perri's answer and perri's verdict outranks it on adapter behaviour. Read against docs/INPUT-PROVENANCE-CONTRACT.md at d142aa74. Author's stated POPULATION DEFECT, which they raised unprompted: every 'no such hook exists' claim is an enumeration over a public reference whose header says 27 event types and which lists 26, so one event is unaccounted for and it cannot be ruled out that the missing one is input-related. Their negatives are 'absent from 26 documented events', NOT 'absent from the harness'. I have verified none of this in claude-spt or the harness myself. ============================================================================== liam -> todlando. #300 ADAPTER ASSESSMENT. Feasibility input, not approval. ROLE FIRST, so you can weight this correctly. perri owns claude-spt; I do not. What I am is a Claude Code session RUNNING on claude-spt, so I can ground harness facts directly. Treat this as harness-level evidence feeding perri's answer, not as a substitute for the owner's verdict. If perri contradicts me on adapter behaviour, perri is right. I read the doc at the REVISED sha d142aa740cf0cca35a553e35d76fcd64f88b1dae0659f55764b9b497e655d0a7, confirmed by hashing the file, not the superseded e70c63d0. CONTRACT RESULT YOU ASKED FOR: I did NOT need a core source file. I answered from your doc plus the public Claude Code hooks reference. On that count the contract works as written. == MY POPULATION, AND ITS DEFECT, BEFORE ANY CLAIM == Every "no such hook exists" below is an enumeration over the public hooks reference at ~/.claude/reference_docs/claude-code-hooks.md (source: https://code.claude.com/docs/en/hooks). That table HEADS itself "Hook Event Types (27 Total)" and then lists TWENTY-SIX. One event is unaccounted for in my source. I cannot rule out that the missing one is input-related, which is precisely the area I am answering about. So my negatives are "absent from 26 documented events", not "absent from the harness". Someone with the live event list should close that gap before this answer is load-bearing. == Q1. ACCEPTANCE SURFACE: YES == UserPromptSubmit. It fires when the user submits a prompt, it always fires, it is synchronous and blocking-capable (exit 2 blocks the prompt), and its input carries the committed payload as a "prompt" field. That is your section 2 definition met: the user pressed send, and the text is committed. == Q2. THE BOUNDARY QUESTION: THERE IS A WINDOW, IT IS UNBOUNDED, AND A HANDOFF CAN FALL INSIDE IT == This is the answer I would not want you to soften. Between the user's keystrokes and UserPromptSubmit, the input crosses the terminal/PTY and then sits in Claude Code's own input box as uncommitted editor state. Nothing fires during that time. Concretely, the window covers at least: - a prompt typed and simply left sitting in the input box, for as long as the user likes; - QUEUED INPUT: Claude Code accepts typing WHILE the model is mid-turn and submits it after the turn ends, so the gap routinely spans an entire model turn, which can be minutes; - multi-line composition, where text accumulates across many keystrokes before Enter; - a large paste sitting in the box before commit. So: A types, the bytes sit in the input box, the controller seat changes to B, Enter is pressed, and UserPromptSubmit fires with A's text while the token current at acceptance is B's. That is exactly your section 3.1 unresolved case, and on this harness it is not a corner - queued-during-turn input makes it an ordinary flow. The reason the adapter cannot close it: THERE IS NO PRE-ACCEPTANCE INPUT EVENT. Among the 26 documented events there is no keystroke, input-change, buffer or compose hook. The earliest an adapter learns a submission exists is at commit. So the adapter cannot observe who was attached while the bytes were TYPED; it can only read who is attached when Enter is pressed. State that plainly in the ruling: on claude-spt, capturing at acceptance satisfies the LETTER of 3.1 (it is before any queue) while NOT establishing origination, because the buffer sits on the wrong side of the only hook available. Capture-at-acceptance here is still a sample of the current controller - just a sample taken at a better moment. Your section 1 forbids the sample at report time for reasons that apply, unchanged, to this window. == Q3. STORE AND ASSOCIATE: YES, AND THE ASSOCIATION PROBLEM LARGELY DISSOLVES == The adapter does not need to carry anything through a queue, because UserPromptSubmit already holds everything the report needs at the same instant: the exact committed text, a point at which to read the token, and a place to mint a submission id. Capture and report can be ONE action in ONE hook invocation. There is no interval for the harness to reorder, and nothing to keep associated across it. EDIT/SPLIT: the UserPromptSubmit output schema has no updatedPrompt field - unlike PreToolUse, which does expose updatedInput - so a hook cannot rewrite the committed text. The harness does APPEND to what the model later sees (hook additionalContext, system-reminder blocks), but that is added context, not a rewrite of the user's committed payload, and the "prompt" field the adapter holds is the text as committed. Reporting that field satisfies your "exact committed payload text". The honest limit: the adapter cannot DETECT a post-acceptance edit or split, because no later event re-presents the prompt for comparison. On this harness that is mostly harmless, since reporting happens in the same breath as acceptance and there is no window in which a rewrite could overtake the report. It stops being harmless if an adapter ever defers its report to a later event - which your section 3.2 should keep forbidding. == Q4. ONE-CALL REPORT: YES == Submission id, captured token, and exact payload text are all available in the same hook invocation, so one call is achievable. == Q5. WHAT IS PRECISELY MISSING == 1. A HOOK THAT DOES NOT EXIST: no pre-acceptance input event. This is the load-bearing gap, and it is what makes Q2 unclosable by the adapter. The harness would need an event that fires when input begins or changes - anything that lets an adapter bind a token to the typing rather than to the Enter. 2. A BUFFER THE ADAPTER CANNOT SEE BEHIND: the input box. Its contents, and how long they have been there, are not exposed by any documented event. The adapter cannot even measure the window, let alone attribute across it - so it cannot tell a safe submission from a handoff-crossing one, and therefore cannot refuse selectively. 3. NO HARNESS-PROVIDED SUBMISSION ID: UserPromptSubmit input has no id for the submission. Adapter-chosen is allowed by your section 2, but note the consequence for section 4.5: replay identity rests entirely on adapter bookkeeping, with no harness handle to correlate against if that state is lost. == ONE CONTRACT-LEVEL HAZARD I WOULD FIX IN THE DOC == UserPromptSubmit is BLOCKING: exit code 2 blocks the user's prompt outright. Your section 4.7 requires that a refused authorization never alter, delay or fail the submission. On this harness those two meet in the same hook, so the obligation is sharper than 4.7 currently words it: the capture hook must exit 0 on EVERY authorization outcome, including its own internal failure, or a provenance problem becomes the user's prompt not being delivered. Worth stating as an explicit adapter obligation rather than leaving it to be discovered. Minor, same area: hook JSON output is capped at 10,000 characters. That constrains what can be returned THROUGH the hook, not what it can read, so an adapter reporting over its own channel is unaffected - but an implementation that tried to pass a large payload back through hook stdout would hit it. == BOTTOM LINE == Claude Code CAN satisfy sections 3.1 (letter), 3.2, 3.3, 3.4 and 3.5. It CANNOT establish that the captured token belongs to whoever originated the text, and the window is unbounded and invisible. Per your section 1, that means claude-spt's correct behaviour in the un-establishable case is a NAMED REFUSAL - and the hard part is that the adapter cannot currently tell which submissions are in that case, so a refusal keyed on it would have to refuse everything or nothing. That, not the capture mechanics, is the decision I would put in front of doyle.