---
name: cc-acceptance-surface-provenance
description: "Claude Code's only acceptance surface is UserPromptSubmit — it proves who pressed Enter, never who typed the text; measured UPS coverage + the transcript instrument that measures it."
metadata: 
  node_type: memory
  type: project
  originSessionId: 19d48c98-d787-4436-90c2-da3f52339ae1
  modified: 2026-09-12T10:03:48.774Z
---

2026-09-12 — assessed spt-core's INPUT-PROVENANCE-CONTRACT (#300, doyle via todlando) against claude-spt.

**Verdict sent:** claude-spt can do capture-at-commit, one-call report, never-substitute, report-once.
It CANNOT establish the ORIGINATING controller.

⭐⭐ **`UserPromptSubmit` fires at COMMIT — the wrong END of the window.** Between keystrokes and the
hook sits CC's input box: a client-side, editable, **unbounded-lifetime** buffer. A handoff can fall
anywhere inside compose time. Worse, `spt rc` drives the SAME PTY, so two controllers can CO-AUTHOR
one submission and the harness records one string with no per-character origin. No adapter fix exists.

⭐ Capture and report both happen INSIDE the one hook invocation, so the contract's hard part
(carrying an association through a queue) does not arise at all. The hard part is upstream of the hook.

⭐ **UNMEASURED and I refused to assert it:** whether UPS fires at ENQUEUE or DEQUEUE for a message
typed while busy. `hook.rs` (~:3149) *claims* a folded stub's UPS "never fires" and the park-drain
workaround exists because bodies stalled — but a workaround's existence is not a measurement of hook
position. Needs an operator typing into a busy session with the trace armed. See
[[v0250-symmetric-event-postentersettle]], [[v0200-stub-delivery-shipped]].

**The instrument (reusable):** walk a session `.jsonl`; a real submission is a `type:"user"` entry with
no `tool_result`; it is UPS-covered if an `attachment` with `hookName` starting `UserPromptSubmit`
appears before the next user entry. Scripts: `scratchpad/pair_audit.py`, `stub_audit.py`.

**Measured on this node (20 transcripts):** 302 submissions, 227 UPS-covered. All 75 uncovered are
harness-synthesised entries OR **built-in local slash commands** (`/compact`, `/reload-plugins`
mid-session are the clean cases) — those fire NO UserPromptSubmit. Separately: 133 PTY-typed stub
submissions, 103 covered; all 30 uncovered are **safe-mode sessions (hooks off by design)**, not a
defect. So "no token" must be a FIRST-CLASS outcome, not an edge case.

⭐⭐ **Instrument limit I stated in the report rather than hid:** this observes hook OUTPUT, not hook
INVOCATION — a UPS that fired and emitted nothing is invisible. Positive control given alongside the
verdict (227/302 and 103/133 DID pair), per [[v02532-verdict-not-catchall]].

**Offered to doyle as feasibility input, not built:** the adapter's per-acceptance reports FENCE the
timeline into compose intervals; core (which owns the rc attachment) can refuse by name any submission
whose interval SPANS a seat change. An interval history check, not a sample of "who is attached now" —
which the contract forbids outright.
