---
name: f032-commune-ingest-blackhole-tests
description: MSG-IDENTITY W3 F-032 commit-before-delete test patterns for REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE in spt-live ingest.rs + pulse.rs
metadata:
  type: project
---

MSG-IDENTITY W3 (F-032, REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE) — commune-ingest black-hole fix, tests shipped on branch build/msg-identity-w3-f032.

Impl seam: `crates/spt-live/src/ingest.rs::ingest_drops`. When project_id is EMPTY and the drop carries a non-empty (post-checkpoint-strip) `<project-context>` slice, the drop is NOT deleted — it is atomically rewritten to a project-only pending form `<project-context>\n{slice}\n</project-context>\n` and `Ingested.preserved = true`. All other drops delete with `preserved = false`. Loud `COMMUNE_PROJECT_DEFERRED:` stderr only when file content actually changes (idempotent on retries).

**Why:** deleting an un-committable project slice (empty/unresolved project_id) black-holed it; the fix commits live now, defers project to a later resolvable ingest.

**How to apply / test patterns that worked:**
- Unit tests live in ingest.rs `mod tests`, use the `with_home` fixture (one process-wide SPT_HOME Mutex in test_support.rs). Helpers `read_live`/`read_project` already defined there.
- The pending form re-parses to itself: `parse_two_slice(pending)` yields `live=None, project=Some(slice)` (parser trims bodies). Idempotency test asserts BYTE-equality of the file across two empty-id ingests AND `writes.is_empty()` on retry (no live re-commit, since the live slice is gone from the pending form) AND read_live unchanged.
- Resolve leg: third ingest with real project_id ⇒ `[TierWrite{Project, Written}]`, drop deleted, `preserved=false`, read_project has text, read_live still has text (nothing lost).
- Marker-only project slice (`!!checkpoint!!`) is empty after `strip_checkpoint_markers` ⇒ NOT preserved (must not strand a contentless drop). Include a live slice to also assert live still commits.
- Untagged body (parser fallback routes whole to live) has no project slice ⇒ never defers, deletes preserved=false.
- Int test: driven through `pulse::tick` (the fn is `tick`, NOT `pulse_tick` as some specs say). spt-live has NO tests/ dir — int test goes in pulse.rs `mod tests`, tagged `[int->...]`. Two pulses (empty pid → resolved pid), asserts durable files at `tracked/{agents,projects}` via `live_context_file`/`project_context_file` + `tracked_dir()`.

Verify: `cargo test -p spt-live --lib` (~15s, 65 tests), `cargo clippy -p spt-live --all-targets -- -D warnings`, `cargo check --workspace` (Ingested gained `preserved` field — consumers in spt-daemon/lifecycle.rs + pulse.rs compile clean). Do NOT run spt-daemon --lib (live-host deadlock).
