{
  "summary": "Found 8 concrete milestone defects: 2 P1 and 6 P2.\n\n1. **[P1] Cancelled/failed turns can still execute peer shortforms and return partial text as a successful peer outcome.**\n   - **Current symbols/lines:** `extractReplyAfterAssistantCount()` at `adapter/strings/omp-spt.mjs:146-149` returns the last assistant’s text without examining `stopReason`. `completeTurn()` at `adapter/strings/omp-spt.mjs:1847-1855` uses that text as the inbound message outcome, and `adapter/strings/omp-spt.mjs:1878` unconditionally passes the same text to `dispatchShortforms()`. The shortform side effect is launched at `adapter/strings/omp-spt.mjs:1804-1822`.\n   - **Reproducer:** (1) Start an activated session and an agent turn. (2) Let the assistant stream `@<release-agent deploy build @>` or any nonempty partial reply. (3) Press Esc after the closing `@>` is present, or let the provider fail after emitting partial text. OMP records an assistant message with `stopReason: \"aborted\"` or `\"error\"` and emits `agent_end`; `completeTurn()` treats its content as normal. (4) `spt send release-agent ...` runs despite cancellation; if an inbound item was in `current`, its sender receives the partial text rather than an abnormal-turn failure.\n   - **Impact:** The user’s cancellation control does not cancel a newly introduced cross-agent side effect. A partially generated command can reach another live agent after the operator aborts, and peer custody is falsely reported as successfully completed.\n   - **Discrete fix:** In `completeTurn()`, identify the terminal assistant added in this turn and branch on `stopReason`. For `aborted`/`error`, settle `current` with an explicit abnormal-turn failure and skip `dispatchShortforms()` entirely. Add cases to `testAbnormalTurnsRestoreReceivability()` (`tests/omp-extension.mjs:1826-1858`) containing nonempty aborted/error assistant messages, plus a complete shortform, and assert no shortform send occurs.\n\n2. **[P1] One assistant response can synchronously spawn an unbounded number of `spt send` children.**\n   - **Current symbols/lines:** `parsePeerShortforms()` at `adapter/strings/omp-spt.mjs:249-268` has no shortform, target, body-size, or total-delivery limit. `dispatchShortforms()` expands every target at `adapter/strings/omp-spt.mjs:1807-1809`, then `Promise.all(deliveries.map(...))` at `adapter/strings/omp-spt.mjs:1810-1822` invokes every `runCommand()` concurrently. In production each call spawns a separate `spt` process through `runSpt()`.\n   - **Reproducer:** Complete one successful turn with assistant content of the form `@<p0000,p0001,...,p9999 message @>` (or many separate shortforms). Before the first child settles, the synchronous `map` starts every send. Per-command 5-second timeouts do not limit simultaneous process count.\n   - **Impact:** A model mistake or adversarial prompt can exhaust process handles, file descriptors, memory, or CPU and crash/freeze the OMP host. This violates the parity record’s retained bounded-resource guarantees.\n   - **Discrete fix:** Define explicit maximums for total deliveries and body bytes, reject/truncate excess with a visible status, and execute accepted sends through a small fixed-width worker pool. Extend `testPeerShortformParsingAndDispatch()` (`tests/omp-extension.mjs:1860-1966`) with over-limit input and a peak-concurrency assertion; the existing three-recipient test proves parallelism but not a bound.\n\n3. **[P2] Assistant-count reply baselines become invalid after native checkpoint or automatic history compaction.**\n   - **Current symbols/lines:** Baselines are raw counts in `assistantMessageCount()` / `extractReplyAfterAssistantCount()` at `adapter/strings/omp-spt.mjs:139-149`; an active-injected item stores the pre-rewrite count at `adapter/strings/omp-spt.mjs:1747-1760`; completion reuses it at `adapter/strings/omp-spt.mjs:1847-1855`. `spt_checkpoint` invokes native history rewriting at `adapter/strings/omp-spt.mjs:1187-1190`, but no checkpoint/compaction path rebases `current.assistantBaseline` or `knownAssistantCount`.\n   - **Reproducer:** (1) Build a session with 50 historical assistant messages. (2) Accept/inject a peer envelope, which stores `current.assistantBaseline = 50`. (3) During that live turn, invoke `spt_checkpoint`, or let OMP perform a mid-run compaction, reducing retained assistant messages to one summary-era assistant. (4) Produce a valid final answer, leaving two assistant messages total. (5) At completion, `assistants.length <= baseline`, so extraction returns `\"\"` and the peer receives `turn ended without an assistant response`.\n   - **Impact:** Checkpointing or ordinary context maintenance can turn a successful peer response into a false failure and lose the actual reply, directly breaking checkpoint continuity and custody correlation.\n   - **Discrete fix:** Stop correlating turns by transcript-wide assistant counts. Use a stable current-turn/message identity or terminal assistant supplied by the event; if count-based extraction must remain, explicitly rebase both turn and item baselines whenever native compaction rewrites history. Add a test that injects with a high baseline, replaces the context with a compacted shorter history, and then completes with a valid assistant reply. Current `testNativeCheckpointTool()` at `tests/omp-extension.mjs:1969-2051` never changes the message history, so it cannot expose this.\n   - **Host contract evidence:** OMP documents `context` as a per-call ephemeral rewrite and `ctx.compact()` as native session compaction; its current implementation replaces agent history during compaction: https://github.com/can1357/oh-my-pi/blob/e8d0a93d/packages/coding-agent/src/session/agent-session.ts#L9609-L9930\n\n4. **[P2] Session immutability is not enforced while an ordinary-session bind is in flight.**\n   - **Current symbols/lines:** `activateEndpoint()` reserves `id`, `sid`, and starts `bindPromise` at `adapter/strings/omp-spt.mjs:1022-1044`, but activation is not marked complete until `adapter/strings/omp-spt.mjs:1058`. `blockSessionChange()` at `adapter/strings/omp-spt.mjs:1728-1735` permits switching whenever both `activated` and `token` are still false; it ignores `activationPromise`, `bindPromise`, and the already reserved identity/session.\n   - **Reproducer:** (1) In an ordinary session A, invoke `/live agent-a`. (2) Hold the public `spt api ... bind` operation before it returns `token=...`. (3) Fire `session_before_switch` or `session_before_branch` and switch to session B; the guard returns `undefined`. (4) Resolve the bind. The extension starts a listener with session A’s captured `sid`, but its runtime/UI now belong to the switched OMP session, after which changes are blocked.\n   - **Impact:** The extension can become authenticated to one OMP session while consuming/injecting in another, violating ADR-0015’s immutable endpoint/session binding and risking cross-session message delivery.\n   - **Discrete fix:** Treat activation as an identity/session reservation from before bind starts: block switch/branch whenever `activationPromise` or `bindPromise` exists (or use a dedicated `activationInFlight` state). If switching must win, abort and fully settle the bind before allowing it. Add a deferred-bind case to `testNativeActivationCommandsAndErrors()` (`tests/omp-extension.mjs:1435-1525`) asserting both pre-events return `{ cancel: true }` until activation either succeeds or cleanly fails.\n\n5. **[P2] `/live --auto` treats an endpoint with no recorded working directory as compatible with every project.**\n   - **Current symbols/lines:** Candidate compatibility at `adapter/strings/omp-spt.mjs:876-886` rejects only when `currentDirectory && info.cwd && normalized paths differ`. A missing, null, or empty `info.cwd` bypasses the project boundary and returns the candidate.\n   - **Reproducer:** In project B, have the public endpoint list return an inactive `live_agent` whose endpoint-info reports `{ adapter: \"omp-spt\" }` with absent/null `cwd`, and whose digest is the newest. Run `/live --auto` and confirm the offered identity. It is selected and bound even though project compatibility was never established.\n   - **Impact:** A stale/legacy identity from another project can be auto-resumed and consume the wrong endpoint continuity/history. This contradicts docs/PARITY.md:30’s “most-recent compatible live identity”; the tests already establish cwd equality as a compatibility dimension at `tests/omp-extension.mjs:1564-1598` but omit unknown cwd.\n   - **Discrete fix:** When `ctx.cwd` is available, require endpoint-info to contain a nonempty `cwd` and require normalized equality; unknown provenance must fail closed and require explicit `/live <id>`. Add null, empty, and absent-cwd candidate cases to `testPlatformAwareActivationCandidatePaths()`.\n\n6. **[P2] Ready/live candidate discovery has a second unbounded subprocess fan-out.**\n   - **Current symbols/lines:** `compatibleCandidates()` runs one endpoint-info command per local inactive endpoint via unbounded `Promise.all(local.map(...))` at `adapter/strings/omp-spt.mjs:868-897`. `/live --auto` then runs one digest command per compatible endpoint via another unbounded `Promise.all` at `adapter/strings/omp-spt.mjs:950-977`.\n   - **Reproducer:** Accumulate thousands of stale inactive endpoint records in the local public spt-core registry, then invoke bare `/ready`, bare `/live`, or `/live --auto`. The command handler launches all endpoint-info children simultaneously; auto-resume launches a second full wave of digest children.\n   - **Impact:** A normal discovery command can exhaust local process handles/resources and freeze OMP. Each child is individually timed out, but simultaneous resource use is not bounded.\n   - **Discrete fix:** Scan endpoint-info and digest requests through the same fixed-width concurrency limiter, preserving result ordering/tie rules. Add a large synthetic endpoint listing and track peak concurrent `onRun` calls in activation tests; assert the peak never exceeds the configured limit.\n\n7. **[P2] Optional update detection can stall every activated session’s first turn for the general 15-second command timeout.**\n   - **Current symbols/lines:** `DEFAULT_COMMAND_TIMEOUT_MS` is 15,000 ms at `adapter/strings/omp-spt.mjs:352`; `updateProbeTimeoutMs` is 1,500 ms at `adapter/strings/omp-spt.mjs:605`, but only the GitHub fetch uses it at `adapter/strings/omp-spt.mjs:609-623`. The three local update commands at `adapter/strings/omp-spt.mjs:806-813` omit timeout overrides, and the first `before_agent_start` synchronously awaits the whole probe at `adapter/strings/omp-spt.mjs:1792-1795`.\n   - **Reproducer:** Activate any endpoint with default `checkUpdates: true`; make `spt --version`, `spt --json notif list`, or `spt adapter version omp-spt` hang. Submit the first prompt. `before_agent_start` blocks model startup until the slow command reaches the general 15-second timeout (plus child termination), despite the advertised 1.5-second probe budget.\n   - **Impact:** A best-effort notice adds a reproducible ~15-second cold-turn latency on a broken/slow CLI path. `testStartupBriefHintsAndUpdateNotices()` (`tests/omp-extension.mjs:1687-1770`) covers immediate success/failure only, not hangs or latency bounds.\n   - **Discrete fix:** Pass `{ timeoutMs: updateProbeTimeoutMs }` to every local update-probe `runCommand`, or race the entire notice promise against that budget and let a later turn carry the result. Add a fake-clock test proving first-turn injection settles within the probe budget.\n\n8. **[P2] Explicit shortforms in earlier assistant messages of a successful multi-step turn are silently dropped.**\n   - **Current symbols/lines:** `extractReplyAfterAssistantCount()` at `adapter/strings/omp-spt.mjs:146-149` returns only `assistants.at(-1)`. `completeTurn()` stores that single message as `currentTurnReply` at `adapter/strings/omp-spt.mjs:1847-1849` and parses only it at `adapter/strings/omp-spt.mjs:1878`.\n   - **Reproducer:** In one successful turn, the assistant first emits text `@<peer-a inspect artifact @>` alongside a tool call; after the tool result it emits a second assistant message `Inspection complete.` The final transcript has two new assistant messages, but only the second reaches `parsePeerShortforms()`, so no send or status occurs.\n   - **Impact:** The documented explicit assistant-output command is unreliable in ordinary tool-using turns; a visible, syntactically valid command can have no side effect and no failure status.\n   - **Discrete fix:** On successful terminal completion, parse all assistant text belonging to the current turn, in message order, rather than only the last assistant. Keep the abnormal-turn gate from finding 1 so aborted/error turns dispatch none, and use stable turn identity rather than the compaction-sensitive count from finding 3. Add a two-assistant/tool-result case to `testPeerShortformParsingAndDispatch()`; its existing cases at `tests/omp-extension.mjs:1887-1939` put commands only in the sole/final assistant message.",
  "files": [
    {
      "path": "adapter/strings/omp-spt.mjs",
      "description": "All eight defects originate in the new extension paths: abnormal completion/shortform dispatch, count-based reply correlation, activation reservation and compatibility filtering, unbounded command fan-out, and blocking update probes."
    },
    {
      "path": "tests/omp-extension.mjs",
      "description": "Milestone tests exercise happy paths but omit nonempty aborted/error assistants, compaction-induced history shrinkage, in-flight bind session changes, unknown-cwd auto-resume candidates, fan-out caps, hung update probes, and shortforms in pre-final assistant messages."
    },
    {
      "path": "docs/adr/0015-extension-owned-session-activation.md",
      "description": "Findings 4-6 violate the activation ADR’s current-session binding, immutable identity, and compatible explicit auto-resume requirements."
    },
    {
      "path": "docs/adr/0017-active-turn-delivery-uses-safe-boundaries.md",
      "description": "Finding 3 breaks exactly-once reply correlation when active-turn custody spans a native history rewrite."
    },
    {
      "path": "docs/adr/0018-checkpoint-resets-context-natively.md",
      "description": "Finding 3 is exposed directly by checkpoint’s required native context reset."
    },
    {
      "path": "docs/PARITY.md",
      "description": "Contract source for immutable binding, compatible auto-resume, abnormal-turn settlement, bounded custody guarantees, update notices, checkpoint continuity, and peer shortform behavior."
    }
  ],
  "architecture": "The milestone adds two ingress/egress state machines to the existing single-custody listener: ordinary-session activation (`compatibleCandidates` → selection/auto-resume → bind/state/listener) and per-turn processing (`context` injection → `completeTurn` settlement → peer-shortform sends/status). `spt_checkpoint` introduces a native history-rewrite boundary inside the same active-turn state. The highest-risk defects occur where these state machines use transient booleans/counts instead of explicit lifecycle states: activation is reserved before it is guarded; abnormal assistant status is discarded; transcript-wide assistant counts are treated as monotonic across compaction; and subprocess concurrency is timed out but not capped."
}