Found by adversarial review of #249 (Codex finding 1 "Critical", finding 2 "High"; Grok
finding 1 "Medium"). Both reviewers reached the endpoint issue independently.
#249 improved this area — before it, _EXPORT_SQL["RELATES"] filtered only
WHERE r.deployment_id = :deployment_id AND r.invalidated_at IS NULL, with no lineage
enforcement whatsoever. It now requires surviving evidence on a live document. These are
the gaps that remain, and they are pre-existing rather than introduced.
1. Document-level liveness is weaker than claim visibility
src/rememberstack/spine/projection.py — _EXPORT_SQL["RELATES"] and the watermark query.
The export tests relation_evidence -> documents.deleted_at IS NULL. The authoritative
test is relation_evidence -> claims_visible_history. If one version of an otherwise
live lineage is deleted, its claims and evidence links remain for audit but
claims_visible_history hides them — while this export still projects the relation
because the parent document is live.
Consequence: a fresh P2 snapshot can expose deleted-version facts through Cypher or
historical traversal, which is a D48 hard-forget concern. relation_evidence.claim_id
is also never checked, so a malformed association pointing at another document's claim is
authorized.
Proposed fix (Codex): require a fully visible (deployment_id, claim_id, doc_id) claim
coordinate matching v_memory_fact_visible.
2. Endpoint gate ignores surviving provenance
The endpoint joins prove only that the survivor's base row is active and unmerged.
entities_current additionally requires a surviving mention or a legitimate
document-entity bridge. After the last source is forgotten an entity may stay active
until asynchronous GC retires it, so this export keeps publishing that node and any
incident edge while graph_edges_visible_history excludes it immediately.
P2 and live SQL therefore disagree on that edge. #249 documents the divergence in a
comment rather than papering over it; this issue is to close it.
Codex additionally notes the Entity export is the missed query that must agree.
3. Do it without reintroducing the hang
The reason the export uses base tables at all is that entities_current /
graph_edges_visible_history expand into multi-million-cost nested loops and hang P2
rebuilds (design/operations/p2-projection-hang-beam-smoke.md).
Both reviewers propose the same shape: materialize the visible relation-ID set and the
visible survivor-entity set once inside the export transaction, index them, and reuse them
across Entity, both RELATES endpoint joins, and the watermark. That gets view parity
with bounded plans instead of per-row semi-joins.
4. Verify at scale
No EXPLAIN was run on a BEAM-scale corpus for #249's queries. Grok would not certify the
new EXISTS as "reviewed at scale" without one. Run
EXPLAIN (ANALYZE, BUFFERS) on RELATES and the watermark under the export session
settings (jit=off, collapse limits at 1, no parallel workers).
Note both reviewers independently concluded #249's relation-scoped EXISTS is not the
old pathology — its access key is the hash-partitioned relation_evidence primary-key
prefix, not a recursive survivor CTE times per-entity provenance.
Found by adversarial review of #249 (Codex finding 1 "Critical", finding 2 "High"; Grok
finding 1 "Medium"). Both reviewers reached the endpoint issue independently.
#249 improved this area — before it,
_EXPORT_SQL["RELATES"]filtered onlyWHERE r.deployment_id = :deployment_id AND r.invalidated_at IS NULL, with no lineageenforcement whatsoever. It now requires surviving evidence on a live document. These are
the gaps that remain, and they are pre-existing rather than introduced.
1. Document-level liveness is weaker than claim visibility
src/rememberstack/spine/projection.py—_EXPORT_SQL["RELATES"]and the watermark query.The export tests
relation_evidence -> documents.deleted_at IS NULL. The authoritativetest is
relation_evidence -> claims_visible_history. If one version of an otherwiselive lineage is deleted, its claims and evidence links remain for audit but
claims_visible_historyhides them — while this export still projects the relationbecause the parent document is live.
Consequence: a fresh P2 snapshot can expose deleted-version facts through Cypher or
historical traversal, which is a D48 hard-forget concern.
relation_evidence.claim_idis also never checked, so a malformed association pointing at another document's claim is
authorized.
Proposed fix (Codex): require a fully visible
(deployment_id, claim_id, doc_id)claimcoordinate matching
v_memory_fact_visible.2. Endpoint gate ignores surviving provenance
The endpoint joins prove only that the survivor's base row is
activeand unmerged.entities_currentadditionally requires a surviving mention or a legitimatedocument-entity bridge. After the last source is forgotten an entity may stay
activeuntil asynchronous GC retires it, so this export keeps publishing that node and any
incident edge while
graph_edges_visible_historyexcludes it immediately.P2 and live SQL therefore disagree on that edge. #249 documents the divergence in a
comment rather than papering over it; this issue is to close it.
Codex additionally notes the
Entityexport is the missed query that must agree.3. Do it without reintroducing the hang
The reason the export uses base tables at all is that
entities_current/graph_edges_visible_historyexpand into multi-million-cost nested loops and hang P2rebuilds (
design/operations/p2-projection-hang-beam-smoke.md).Both reviewers propose the same shape: materialize the visible relation-ID set and the
visible survivor-entity set once inside the export transaction, index them, and reuse them
across
Entity, bothRELATESendpoint joins, and the watermark. That gets view paritywith bounded plans instead of per-row semi-joins.
4. Verify at scale
No
EXPLAINwas run on a BEAM-scale corpus for #249's queries. Grok would not certify thenew
EXISTSas "reviewed at scale" without one. RunEXPLAIN (ANALYZE, BUFFERS)onRELATESand the watermark under the export sessionsettings (
jit=off, collapse limits at 1, no parallel workers).Note both reviewers independently concluded #249's relation-scoped
EXISTSis not theold pathology — its access key is the hash-partitioned
relation_evidenceprimary-keyprefix, not a recursive survivor CTE times per-entity provenance.