{
  "status": "ISSUES_FOUND",
  "findings": [
    {
      "path": "adapter/strings/omp-spt.mjs",
      "symbol": "runSpt",
      "lines": "111-128",
      "severity": "P1",
      "confidence": 10,
      "impact": "A normal outbound-command failure can terminate the entire OMP process, bypassing the custody retry/fail-closed path and losing the only local chance to report the accepted message outcome.",
      "failure_scenario": "Point `OMP_SPT_SPT_BIN` at an executable that starts and immediately exits without reading stdin. `settleItem()` calls `runSpt(..., payload)`, `child.stdin.end(input)` writes to the closed pipe, and the stdin Socket emits `EPIPE`. Only `child.on(\"error\")` is registered; an unhandled `child.stdin` error is an uncaught exception in Node. The tests replace `runSptCommand`, so they never exercise the real pipe.",
      "fix": "Register stdin error handling before writing, reject the command promise on write/EPIPE errors, and make close/error/stdin completion settle exactly once. Add a real-child regression test whose stdin closes immediately."
    },
    {
      "path": "adapter/strings/omp-spt.mjs",
      "symbol": "runSpt / teardownSession / stopResources",
      "lines": "111-128,272-324,328-340",
      "severity": "P1",
      "confidence": 10,
      "impact": "Bind, delivery, state transitions, fatal shutdown, and normal OMP shutdown can hang forever. A listener that ignores termination can also survive or keep the Node event loop alive.",
      "failure_scenario": "Use an `spt` process that never exits. A hung bind leaves `session_start` pending; subsequent `session_shutdown` waits forever at `await bindPromise`. A hung `send` leaves custody permanently unsettled. A hung `session-end` prevents teardown completion. `failClosed` waits for that teardown before calling `ctx.shutdown()`. Separately, listener teardown only calls `child.kill()` and neither verifies the return value nor waits for close/escalates.",
      "fix": "Give every spawned CLI command a command-specific hard deadline, bound captured output, terminate then force-kill on deadline, and track/join children during teardown. `stopResources` must await listener exit with escalation. Teardown must have an overall deadline so `ctx.shutdown()` still executes after reporting failure."
    },
    {
      "path": "adapter/strings/omp-spt.mjs",
      "symbol": "drainEvents / listener data queue",
      "lines": "23-44,154,172,482-497",
      "severity": "P1",
      "confidence": 9,
      "impact": "Any reachable peer, burst, oversized frame, or corrupt listener can grow endpoint memory without limit until OOM. Shutdown then serially processes the entire accepted backlog, multiplying termination latency.",
      "failure_scenario": "Keep one OMP turn active and stream many valid `<EVENT type=\"msg\">` envelopes. Every full envelope is retained in `queue` with no item/byte cap. Alternatively stream an opening `<EVENT` with no close; `listenerBuffer` grows without a frame-size limit. Neither path applies stream backpressure.",
      "fix": "Track queued and buffered bytes, impose explicit frame/item/total-byte bounds, pause `child.stdout` before the high-water mark and resume as custody advances, and fail closed on an oversized single frame. Never drop an already accepted event; leave excess upstream or return explicit overload outcomes."
    },
    {
      "path": "tools/omp-spt/src/echo_commune_omp.rs",
      "symbol": "run scratch prompt",
      "lines": "167-179",
      "severity": "P1",
      "confidence": 9,
      "impact": "The last 48 KiB of an OMP transcript, including source, secrets, and peer messages, can be exposed to other local users. A killed/hung summarizer leaves the sensitive file behind, and the predictable name permits precreation/symlink attacks where platform protections do not block them.",
      "failure_scenario": "On Unix with the common umask 022, `std::fs::write(temp_dir()/omp-spt-echo-{id}-{pid}.md, prompt)` creates a typically 0644 file in shared `/tmp`. Another user can derive the PID/name and read it for the duration of the model turn. Cleanup occurs only after `cmd.output()` returns.",
      "fix": "Use an exclusive cryptographically random temporary file with owner-only 0600 permissions and an RAII cleanup guard. Avoid a predictable `{id}-{pid}` name; keep cleanup on all ordinary error paths and tolerate secure leftovers after SIGKILL."
    },
    {
      "path": "tools/omp-spt/src/echo_commune_omp.rs",
      "symbol": "compose_prompt / run",
      "lines": "47-58,167-200",
      "severity": "P1",
      "confidence": 8,
      "impact": "A peer-controlled transcript can poison the durable project or cross-project mind that later sessions trust, turning one inbound message into persistent instruction injection.",
      "failure_scenario": "A peer sends text instructing the end-of-session summarizer to emit attacker-chosen instructions inside `<live-context>`. `compose_prompt` concatenates the raw transcript tail into the same user-level prompt as the summarization instruction; no higher-priority policy or trust labeling exists. The resulting two-tag stdout is accepted as the durable delta based only on OMP success/nonempty output.",
      "fix": "Put the summarization contract in a higher-priority OMP system prompt, parse JSONL into explicit role-tagged untrusted data, state that transcript instructions must never be followed, strictly parse and size-bound the exact two-tag result, and ensure resumed consumers treat the delta as quoted data rather than executable instructions. Add adversarial transcript fixtures."
    },
    {
      "path": "tools/omp-spt/src/launch_omp.rs",
      "symbol": "propagated_exit_code",
      "lines": "304-306",
      "severity": "P2",
      "confidence": 10,
      "impact": "Windows OMP crashes are reported as successful endpoint exits, hiding the failure from broker/recovery/operations logic.",
      "failure_scenario": "An access violation exits with Windows status `0xC0000005`, exposed by Rust as negative `i32` `-1073741819`. `.clamp(0, 255)` converts it to `0`, so `launch-omp` exits success. `STATUS_CONTROL_C_EXIT` and other high-bit exception statuses have the same defect.",
      "fix": "Return success only for raw code 0. Preserve 1..=255 if desired, but map every other nonzero/absent code to `ExitCode::FAILURE`; add a Windows regression using `ExitStatusExt::from_raw(0xC0000005)`."
    },
    {
      "path": "tools/omp-spt/src/echo_commune_omp.rs",
      "symbol": "run history ingestion",
      "lines": "127-168",
      "severity": "P2",
      "confidence": 10,
      "impact": "The advertised 48 KiB summarizer bound does not bound memory. Large but valid long-lived session transcripts can OOM the helper during shutdown/commune generation.",
      "failure_scenario": "Create a multi-gigabyte session JSONL. `read_to_string` loads the entire file into `history`; stdin is likewise read to EOF into an unbounded String. Only afterward does `bounded_tail(&history, 48 KiB)` discard the prefix.",
      "fix": "For files, seek from the end and read only enough bytes to locate a complete-line tail. For stdin, retain a bounded rolling buffer/line ring while reading. Reject or explicitly truncate a single over-limit line without first allocating the whole input."
    },
    {
      "path": "adapter/strings/omp-spt.mjs",
      "symbol": "drainEvents / listener malformed-frame handling",
      "lines": "23-44,482-495",
      "severity": "P2",
      "confidence": 9,
      "impact": "One malformed/truncated frame can silently corrupt one sender's prompt and consume another sender's valid delivery, violating correlated custody while the endpoint remains advertised healthy.",
      "failure_scenario": "Feed `<EVENT type=\"msg\" from=\"a\">partial` followed by `<EVENT type=\"msg\" from=\"b\">ok</EVENT>`. `drainEvents` pairs a's opener with b's closer, emits one corrupted a envelope, and permanently consumes b. A missing `from` frame is only logged and continued, also losing custody without failing the protocol.",
      "fix": "Use the listener's actual line/framing contract or a validating state machine. Detect nested openers, malformed attributes, missing senders, and frame-size overruns; on protocol corruption fail closed rather than resynchronizing by consuming a later close tag. Add deterministic malformed-frame tests."
    }
  ]
}