# W3 ruling — digest snapshot generation-union (flynn filing)

**Author:** doyle · **Date:** 2026-07-07 · **REQ:** `REQ-DIGEST-GENERATION-SUPERSEDE` (minted inactive)
**Filing:** spt-mobile `docs/bug-reports/spt-core-digest-gen-union.md` (flynn), spt-mobile `d0aa3f4`
**Operator directive:** root-cause + fix IN this milestone.

## Root cause (spt-core-side — confirmed, not a consumer bug)

A one-shot `endpoint digest <id> --json` snapshot returns each logical activity row
**twice** after a checkpoint/resume — once per seq-generation.

- The snapshot spans the last K sessions: `digest.rs::activity_spanned` (`SPAN_SESSIONS = 5`)
  runs the `[digest]` extractor **per session file** and tags each row
  `seq = (ledger_ordinal << 32) | localseq` (REQ-DIGEST-CURSOR, digest.rs:286).
- A checkpoint/resume (self-`/clear` + Psyche rebuild) makes the harness **replay** the
  prior generation's transcript into the **new** session file. So the ancestor's rows exist
  in **both** the ancestor file **and** the resume file at the **same localseq**.
- The span **unions** them: one logical row surfaces under two full seqs —
  `(gen23, local208)` **and** `(gen25, local208)`, identical `text`/`ts`/`localseq`.
- Consumers dedup by **exact seq** (the documented authoritative key); the two copies carry
  genuinely different full seqs → nothing collapses → duplicate rows in every snapshot /
  `--after` view. `--follow from:0` is **CLEAN** (current-generation only) — the **span** is
  the sole culprit.

flynn's window evidence: 86 `(ts,text,localseq)` collisions across two generations; one
window retained gens 22–26 simultaneously (gen25 ≈167 rows replays gen23 ≈93); ToolSprints
double (`[Read, Read]`).

## Why the clean structural fix does NOT apply (yet)

The obvious structural fix — "a resume session supersedes the ancestor it replayed, so drop
the ancestor from the span" — needs to KNOW a session carried content forward.
**spt-core cannot tell** from the ledger: `api boundary clear` records
`SessionTrigger::Clear` for **both** a fresh `/clear` (disjoint, spanning is wanted) **and**
a carry-forward checkpoint (reporting.rs:94). Distinguishing them needs new boundary metadata
+ adapter cooperation on the checkpoint emit. **Deferred** — not blocking this fix.

## Ruling — flynn Option 1 (Supersede), projection-local

Fix lives entirely in `digest.rs` (the span), source-independent, ships regardless of harness:

1. **Collapse cross-generation replay dupes** on the raw span items. An Activity record from
   an **older** ordinal is dropped when an identical **logical** record (`role`, `ts`, `text`,
   `tool`) exists under a **newer** ordinal.
2. **Keep the newest-ordinal occurrence** — the surviving `seq` is the live generation, so the
   snapshot and `--follow` (current-gen) agree on seq. This is what makes consumer exact-seq
   dedup correct again: one localseq → one row.
3. **Cross-generation ONLY.** Never dedup within a single ordinal — a session cannot replay
   itself; identical within-gen rows are real events and must survive.
4. **Order:** supersede runs on the raw span items **before** the window fold
   (`project_timeline`), so `window_turns` counts real turns, not phantom dupes.
5. **Boundaries:** a boundary divider adjacent to a now-fully-superseded (emptied) ancestor
   must not be left orphaned — splice/keep a boundary only between sessions that retain ≥1 row.
6. **Out of scope:** REQ-TERM-7 context entries come from the single `digest.log`, not a
   per-session span → never gen-doubled → untouched.

### Why spt-core may content-match when flynn's consumer may not

flynn's `REQ-HAZARD-DUP-ROWS` bars the **consumer** from fuzzy matching — consumers must not
second-guess the authoritative feed. spt-core **is** the projection authority and matches on
**exact equality** of full-fidelity records (not fuzzy). Legitimate false-collapse (two truly
distinct events sharing identical `role`+`ts`+`text`+`tool` across a gen boundary) is rare and
a benign digest cosmetic; showing every row twice is the actual reported defect.

## Build direction (todlando)

- **Where:** `crates/spt-daemon/src/digest.rs` — a supersede pass over `activity_spanned`'s
  items (or folded into its assembly). No `brainproc.rs` touch → **no conflict with
  PROMOTE-DRAINED**; sequence independently.
- **Activate** `REQ-DIGEST-GENERATION-SUPERSEDE` (`impl, unit, int`) in the work-start commit
  (rule 5). Tag evidence on the real code.
- **Int rig (RED-first):** a two-session span where session B's extracted lines are a superset
  replay of A (same `ts`/`text` at same localseq) + B's own new tail. Pre-fix: every A row
  appears twice. Post-fix: each logical row once, under B's generation, B's tail intact, the
  `/clear` boundary marker preserved when A retains rows.
- **Regression belts (memory [[shared-seam-change-run-all-seam-tests]]):** run the whole digest
  suite — `digest.rs` units, `tests/digest.rs`, `tests/two_origin_spanning.rs`,
  `extractor_spans_sessions_with_a_boundary_marker` (disjoint sessions → supersede is a no-op →
  must stay green).
- **Gate:** folds into the W3 digest-subsystem sweep (both legs). No CLI change → no xtask gen.

## Reply to flynn (after build)

Supersede shipped spt-core-side (Option 1); one localseq → one row in the snapshot; no
consumer change needed, no seq-internal handle exposed. Their sibling `input_seq == null`
tail-drop fix stays independent (correctly diagnosed).
