Summary
PR #243 stopped new two-value :introduced-by corruption and added a repair to the Stage B correction sweep. But the repair reaches only graphs that keep ingesting new commits above the old ceiling. A graph whose ingestion has already completed cannot be repaired at all, and that is the modal state for a memory graph in the field.
There is currently no supported way to recover such a graph short of deleting it and re-ingesting from scratch.
Why a completed graph is unreachable
A finished run parks :ingestion/correction-sweep-through (mcp_server.py:8796) at frontier-high's :hi-hash. On the next run, _correction_sweep_select_position (mcp_server.py:8854) computes ceiling_pos from that bound and returns None on its first call:
if pos > ceiling_pos:
return None # reached frontier-high's own :hi-hash; nothing left to correct
(mcp_server.py:8922)
So _correction_sweep_apply — which owns the repair — runs zero times. Measured during #235: watermark at linearization position 13 of 13, one select call, zero apply calls. Re-running ingestion repairs nothing.
The behaviour is correct for the sweep's original purpose (it has genuinely finished its work). It is simply not a recovery path.
What already works
- Prevention — every future ingestion, on any graph.
- Repair — for graphs that keep receiving new commits above the old ceiling, and then only for entities that are candidates in those commits; also on an interrupted/resumed run, where Stage B still has region to sweep.
What is missing
An explicit path to re-sweep a range of an already-completed graph, so its corrupted entities collapse to a single :introduced-by.
Design notes
Detection is cheap and should probably come first. The whole-graph audit is one query — the same one #235's oracle uses:
[:find ?e (count ?c) :where [?e :introduced-by ?c]]
Filter to count > 1. A read-only "audit" step that reports how many entities are affected (and whether any are) is useful on its own, and lets a user decide whether a reset is worth its cost. It also answers a question nobody can answer today: how widespread this actually is in real graphs.
Re-sweeping is not cheap. _correction_sweep_apply was 131.8s of a 720.6s 450-commit run in #235's A/B — a full re-sweep is a substantial fraction of an ingestion. Worth considering whether the reset can be scoped to the commits that actually introduced the affected entities, rather than resetting the whole watermark. The audit query above yields the entity idents; their :introduced-by values name the candidate commits directly.
Safety constraints:
- Moving
:ingestion/correction-sweep-through backwards must not cause duplicate facts. The sweep is idempotent per entity by design (case 1 confirms, case 3 guards self-introduction), but that should be verified deliberately rather than assumed, especially interacting with _forward_apply(lifecycle_only=True) which the 2d Stage B loop runs after every swept commit.
- The repair itself is already inert when
pos_by_commit_ident is None or empty, so a reset path must be sure to supply it — see _correction_sweep_apply's "Reach, stated plainly" docstring section.
- Whatever the surface (MCP tool, CLI flag, env var), it should be explicit and hard to trigger accidentally: it re-does work and moves a durability watermark backwards.
Prior art in this file: _lineage_confirmed_through_update and _watermark_update are the existing patterns for reading and moving these bookkeeping idents, and _correction_sweep_through_update (mcp_server.py:8813) is the exact writer to reuse.
Scope question worth settling first
Is a one-off recovery script sufficient (run once by whoever has an affected graph), or does this warrant a supported tool? That depends on the audit's answer. If real graphs turn out to be widely affected, it is a tool; if it is rare, a documented script may be the honest scope.
Related: #235 (the corruption and its partial repair), #222.
Summary
PR #243 stopped new two-value
:introduced-bycorruption and added a repair to the Stage B correction sweep. But the repair reaches only graphs that keep ingesting new commits above the old ceiling. A graph whose ingestion has already completed cannot be repaired at all, and that is the modal state for a memory graph in the field.There is currently no supported way to recover such a graph short of deleting it and re-ingesting from scratch.
Why a completed graph is unreachable
A finished run parks
:ingestion/correction-sweep-through(mcp_server.py:8796) at frontier-high's:hi-hash. On the next run,_correction_sweep_select_position(mcp_server.py:8854) computesceiling_posfrom that bound and returns None on its first call:(
mcp_server.py:8922)So
_correction_sweep_apply— which owns the repair — runs zero times. Measured during #235: watermark at linearization position 13 of 13, oneselectcall, zeroapplycalls. Re-running ingestion repairs nothing.The behaviour is correct for the sweep's original purpose (it has genuinely finished its work). It is simply not a recovery path.
What already works
What is missing
An explicit path to re-sweep a range of an already-completed graph, so its corrupted entities collapse to a single
:introduced-by.Design notes
Detection is cheap and should probably come first. The whole-graph audit is one query — the same one #235's oracle uses:
Filter to
count > 1. A read-only "audit" step that reports how many entities are affected (and whether any are) is useful on its own, and lets a user decide whether a reset is worth its cost. It also answers a question nobody can answer today: how widespread this actually is in real graphs.Re-sweeping is not cheap.
_correction_sweep_applywas 131.8s of a 720.6s 450-commit run in #235's A/B — a full re-sweep is a substantial fraction of an ingestion. Worth considering whether the reset can be scoped to the commits that actually introduced the affected entities, rather than resetting the whole watermark. The audit query above yields the entity idents; their:introduced-byvalues name the candidate commits directly.Safety constraints:
:ingestion/correction-sweep-throughbackwards must not cause duplicate facts. The sweep is idempotent per entity by design (case 1 confirms, case 3 guards self-introduction), but that should be verified deliberately rather than assumed, especially interacting with_forward_apply(lifecycle_only=True)which the 2d Stage B loop runs after every swept commit.pos_by_commit_identis None or empty, so a reset path must be sure to supply it — see_correction_sweep_apply's "Reach, stated plainly" docstring section.Prior art in this file:
_lineage_confirmed_through_updateand_watermark_updateare the existing patterns for reading and moving these bookkeeping idents, and_correction_sweep_through_update(mcp_server.py:8813) is the exact writer to reuse.Scope question worth settling first
Is a one-off recovery script sufficient (run once by whoever has an affected graph), or does this warrant a supported tool? That depends on the audit's answer. If real graphs turn out to be widely affected, it is a tool; if it is rare, a documented script may be the honest scope.
Related: #235 (the corruption and its partial repair), #222.