# UPS-IDENTITY-PLAN — verified identity fast path · carrier custody · stage timings · shortform reframe

**Date:** 2026-07-15 · **Author:** perri · **Status:** planned (hertz RCA bundle read + source-verified;
convergence message sent to hertz; operator-directed shortform reframe folded in as Item E).
**Basis:** `.claude/reports/2026-07-10-user-prompt-hook-timeout/` (RCA + fix/publish proposal, hertz).

**Field problem (verified in source):** `handle_user_prompt_submit` reads `SPT_ENDPOINT_ID`
(`hook.rs:987`) but still calls `self_id` → `spt whoami --json` (`hook.rs:1085`, `hook.rs:579-585`).
spt-core ≥0.31.0 aliases `whoami` to the full enriched endpoint list → `latest_project_ref` per perch →
synchronous Git fanout (measured 45.6s on HFENDULEAM; CC hook ceiling ~30s). Deployah/doyle "selectivity"
is load-threshold behavior, same path. `/sptc:live` turns pay the fanout twice (`hook.rs:1077-1083` list
+ identity list).

**Scope widening vs. hertz's proposal (my challenge):** `self_id` is called from SEVEN more handlers —
PreToolUse (1254), Stop (1297), Notification (1368), SessionEnd (1417), SubagentStart (1446), and the
SessionStart boundary/seed legs (689, 844). PreToolUse fires on EVERY tool call; Stop marks idle
(timeout there = the stuck-busy black-hole class). Fix = ONE shared verified resolver used by all
handlers, not a UPS-only patch. Same proof, same fallback, one seam.

---

## Item A — carrier write custody (prerequisite commit)

Current: `hook.rs:764-770` persists `state/session/<eid>.sid` = current sid on EVERY SessionStart,
even when the strict bind/boundary registration FAILED (errors only logged at 708-712 / 737-739).
That both (a) invalidates the carrier as a fast-path proof and (b) is a live bug: a failed boundary
leaves the perch on the OLD sid but advances the carrier — the NEXT rotation presents the wrong proof.

Fix: thread registration success out of the bind/boundary arms; write the carrier ONLY after a
successful strict registration (first-bind bare success included). On failure the carrier keeps the
prior sid (still the true auth proof). Seed leg (no endpoint id) unchanged — never writes.

Invariant: **carrier(eid, sid) is authoritative iff written post-successful-registration.**

## Item B — verified direct identity resolver (shared)

New resolver, replaces bare `self_id` at all call sites:

1. `SPT_ENDPOINT_ID` present AND `read_adapter_state(sid_state_rel(eid)) == payload session_id`
   → use eid directly (`verified-env` path). Zero whoami / list / endpoint-info / project / Git.
2. Env absent, carrier missing, or mismatch → `core-fallback`: existing `self_id` whoami path
   (harness-hosted live sessions, leaked-env nested sessions, stale carriers). Stays
   deadline-vulnerable until spt-core ships the identity-only API — documented, not hidden.

Proof properties: a subagent/psyche session inheriting a leaked `SPT_ENDPOINT_ID` has a different
payload sid → mismatch → fallback (composes with the v0.18.8 identity-scrub). `/resume` re-bind
writes the resumed sid post-success (Item A) → fast path holds after resume.

`/sptc:live` turns: roster list stays (one explicit `endpoint list --show-all`); identity must ride
the resolver — never a second list.

## Item C — hook-stage timings (hook-trace.log)

Monotonic start/end per stage, UPS first: `skill`, `wake`, `park-drain`, `live-roster`,
`identity` (+ `path=verified-env|core-fallback`), `busy`, `poll`, `nudge`, `emit`, `total`.
No prompt/message bodies logged (existing hook-trace.log privacy line holds).

## Item D — no double-list on /live

Covered by B: exactly one explicit roster list per live invocation under verified-env.

## Item E — shortform messaging reframe (operator-directed)

Reframe from "primary for one message to many agents" → "fastest method for short messages and
replies to one or more agents; longer messages should leverage the `spt send` CLI."

- `adapter/strings/briefs/messaging-perch.md:9` — rewrite the closing framing sentence.
- `plugin/sptc/skills/send/SKILL.md:36-38` — rewrite the bolded use-case paragraph.

## Cross-repo ask (file to doyle, not blocking)

spt-core identity-only API: `session ID -> endpoint ID | null` — no `cmd_endpoint_list`, no registry
projection, no project history, no Git, no network; payload SID passable explicitly; stable JSON.
`api endpoint-info` disqualified (calls `latest_project_ref`, `api/reporting.rs:953`). Once on the
adapter's core floor: replace the whoami fallback, delete the enriched-list dependency.
PROJECT-INDEX remains complementary (list/picker perf), not the hook fix.

## Requirements (activate in traceable-reqs.toml with the change)

- `REQ-UPS-IDENTITY-FASTPATH` — spt-hosted turn-start identity is constant-time: verified-env path
  makes zero list/whoami/Git calls. [impl, unit]
- `REQ-HAZARD-CARRIER-CUSTODY` — the session carrier never advances past a failed registration. [impl, unit]
- `REQ-HOOK-STAGE-TIMING` — UPS stages emit timing records, no bodies. [impl, unit]
- `REQ-DIST-WHOAMI-JSON` unchanged (fallback contract still holds).

## Test matrix (hertz's six + scope widening)

1. verified-env: env set, carrier==payload sid → direct id feeds busy+poll; assert ZERO
   whoami/list/endpoint-info/project/Git calls (HookEnv recorder).
2. carrier mismatch: env set, carrier differs → direct path rejected, fallback invoked, wrong
   endpoint never marked busy/polled.
3. registration refusal custody: strict bind/boundary failure → carrier NOT advanced.
4. registration success custody: carrier atomically updated to new sid.
5. missing env id → fallback compatible (harness-hosted unchanged).
6. live invocation: exactly one explicit list, no identity list under verified-env.
7. (widened) Stop/PreToolUse/SessionEnd/SubagentStart resolve via verified-env with zero core calls.

## Version · publish

- **v0.21.0** (behavior change across all hook handlers + new trace fields; no hooks.json/skeleton
  change → binary-only update, no plugin reload needed). hertz proposed patch-bump — minor is safer
  given the all-handler resolver swap; converge with hertz.
- Gate: `sh ci/run-gates.sh` green · `traceable-reqs check` green · focused hook unit tests ·
  hook-dispatch acceptance (normal + stale shapes) · local adapter-channel install · verified-env UPS
  measured well under the CC ceiling with zero Git children (test perch, NOT deployah/doyle).
- Publish Windows + supported Linux artifacts together; release notes record the harness-hosted
  fallback limitation until the core identity-only API lands.
- Activation: endpoints bounce to pick up the persistent binary (same as v0.16.2 precedent).

## Sequencing

A → B (+D) → C → E → tests/REQs woven per-item (same-commit tagging) → gates → changelog → publish
(pending hertz convergence ack on scope/version + operator activation).
