Files
gbrain/test
a104f98dca fix(cycle): extract_facts guard counts only active legacy rows; reconcile preserves forget records (#2646) (#3474)
master's empty-fence guard counts soft-expired legacy rows
(row_num IS NULL, expired_at set), so forget_fact — the sanctioned
removal path, which soft-expires rather than deletes — can never drain
the backlog: apply-migrations no-ops (already marked applied) and the
guard stays triggered forever, jamming extract_facts.

Narrowed re-send of #3252-sibling #3234, scoped to exactly what the
maintainer named reviewable: "one predicate plus the
reconcile-preservation guard."

- Guard predicate: the legacy COUNT adds `AND f.expired_at IS NULL`,
  so each forget_fact visibly drains the pending counter.
- Reconcile preservation: listExistingFactsForPage excludes soft-expired
  legacy rows (they are never fence-owned, so they must neither read as
  perpetually stale nor mask a fence row), and the two wipe call sites
  pass `preserveExpiredLegacy: true` so deleteFactsForPage keeps the
  forget record. The option is the minimal seam for that guard —
  implemented identically in both engines (~6 lines each).

Explicitly NOT included from #3234 (per the close review): the
drift-repair lane, the re-runnable migration orchestrator path, and the
race-accounting layer.

Fence-is-canonical semantics are preserved and pinned by test: if the
fence still carries an expired legacy row's claim, the reconcile
reinserts it as a fresh active fence-owned row (legacy DB-only forgets
are documented non-durable; the expired row survives as the audit
record of the forget).

Co-authored-by: Time Attakc <89218912+time-attack@users.noreply.github.com>
2026-07-28 11:44:17 -07:00
..