Skip to content

[memory][P1] Recap + FTS ledger contaminated by combat/system bookkeeping — distinct from #749 wrapper-heartbeat scope (audit F07-1) #772

Description

@100yenadmin

What is broken

The cold-open recap (previously_on) and the FTS recall ledger are contaminated by combat/system bookkeeping rows. Every run's setup turn recites mechanical bookkeeping ("Tough 1 takes 5 force damage… Turn advances to Tough 2…") instead of story, and recall ranking is polluted — a recall('Rolan') probe returned 4 of the top 6 hits as bookkeeping (worse than the audit's original 3/6 measurement).

Scope note / not a dup: this is distinct from #749/#763, which is merged in HEAD a245a2c and exact-matches only wrapper-heartbeat lines (wrapper_progress.is_wrapper_progress_line). Combat-event rows and session markers pass straight through that filter — no open issue owns combat-row contamination (re-confirmed against the open-issue set).

Why (root cause, file:line)

  • recap.py:18_STORY_KINDS includes "combat", but _log_combat_event rows (servers/engine/server.py:151-153, schema stamped clawdnd.combat_event.v1 at server.py:130) are purely mechanical.
  • ledger.py:191 indexes combat+system rows as kind="events" with who=speaker (ledger.py:196), so bookkeeping enters the FTS index and outranks story.
  • Blast radius is wider than recap+recall: companion_advise (servers/engine/server.py:6851) pulls callbacks from the same contaminated index.

Evidence

  • recap_probe.py RE-RUN on main @ a245a2c: recap opens with combat bookkeeping exactly as claimed; recall('Rolan') = 4/6 top hits bookkeeping. Reproduced byte-for-byte.
  • Real-data census: all 149 system rows in an 810-row corpus are session markers (0 DM-authored today).

How to fix (the spec — CORRECTED by the skeptic pass)

The naive option — "drop system from the indexed tuple entirely" — is WRONG: log_event's docstring (server.py:7202) and SKILL.md:47 explicitly prescribe DM-authored system rows via persist_beat so they feed recall ("a terse mechanical/system note for recall… Loose prose only feeds recall if you log it"). Dropping the kind breaks that documented contract. Corrected spec:

  1. recap.format_recap keeps kind=="combat" rows only when the payload is None or lacks the combat-event schema (clawdnd.combat_event.v1) — i.e. narrative combat beats stay, engine bookkeeping goes.
  2. ledger.backfill skips rows whose payload carries the combat-event schema AND skips the two engine session markers by exact-prefix match ("Session N began" / "Session ended.") — same exact-match discipline as [qa][engine] #743 heartbeat is invisible to the player AND contaminates engine memory (recap/FTS/lean tail) #749 — while keeping all other system rows indexed (preserving the SKILL.md:47 path).

Because all current system rows are markers, observable behavior is identical today, but the spec must not foreclose the documented DM-system-note path.

Test strategy

  • Recap test: a session log mixing story beats + schema-stamped combat events + session markers yields a recap with story only.
  • Ledger test: backfill skips schema-stamped combat rows and exact-prefix markers; a DM-authored kind=system note (non-marker) IS indexed and recallable (guards the SKILL.md:47 contract).
  • Rank regression: re-run the Rolan-style probe fixture — bookkeeping no longer appears in top hits.

Scorecard impact (gate/lens)

story_craft — the cold-open previously_on is consumed by every run's setup turn; contamination is live and reproduced on every current run (unlike the latent unit-07 findings, this one pays out immediately). Also a prerequisite for the retrieval-adoption push (F07-7): recall must return story, not bookkeeping, before mandating its use.

Invariants

Derived-index-only change, no schema change — clean.


effort: S | confidence: high | finding-id: F07-1 | Source: docs/audits/ENGINE-AUDIT-2026-06-11.md (PR #768), unit 07

Metadata

Metadata

Assignees

No one assigned

    Labels

    engine-auditFinding from the 2026-06-11 full-engine adversarial audit

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions