Skip to content

feat(relations): bi-temporal curation — read filter + retire/revalidate (#653) - #682

Open
norrietaylor wants to merge 1 commit into
mainfrom
feat/653-curation-bitemporal
Open

feat(relations): bi-temporal curation — read filter + retire/revalidate (#653)#682
norrietaylor wants to merge 1 commit into
mainfrom
feat/653-curation-bitemporal

Conversation

@norrietaylor

Copy link
Copy Markdown
Owner

What

Phase 3 of finishing epic #653graph-maintenance #4 (curation).

Crux finding: invalid_at was write-only. No read path (get_related, list_relations, the metrics subgraph, traversal) filtered on it, so soft-retiring an edge was a complete no-op. This PR makes the bi-temporal window actually govern reads.

Changes

  • store read filterget_related and list_relations exclude soft-retired edges by default via a shared _RELATION_LIVE_CLAUSE (invalid_at null/future and valid_at null/past). Pass include_retired=True to surface them. Because the metrics subgraph (list_relations) and BFS traversal (get_related) build on these, retired edges drop out of metrics/traverse automatically — no separate change needed.
  • store retire/revalidateretire_relation(relation_id, invalid_at=now) soft-retires (idempotent; preferred over the hard remove), and revalidate_relation(relation_id) clears invalid_at (the add_relation upsert cannot reset it to NULL, so this is the supported un-retire path).
  • mcpdistillery_relations action="retire" / "revalidate"; the get action gains an include_retired passthrough.

Safe by construction

Every existing edge has invalid_at IS NULL, so the new filter changes nothing until something is explicitly retired.

Deferred to a follow-up PR (tracked)

Auto-retiring an entry's edges when it is supersedes/corrects-ed (cascade), and scheduled threshold-based retirement. The primitives (retire_relation + read filter) land here; the policy layer follows.

Testing

  • Retired edge excluded from get_related / list_relations / traversal; surfaced with include_retired; revalidate restores it.
  • Future invalid_at stays live (bi-temporal correctness); retire idempotent + missing-id returns False; MCP retire/revalidate roundtrip + validation.
  • Two existing attribute-round-trip tests updated to read with include_retired (their fixtures set a past invalid_at, so the edge is legitimately retired now).
  • Full suite: 3215 passed, 79 skipped; mypy --strict src/ clean; ruff check src/ tests/ clean.

🤖 Generated with Claude Code

…te (#653)

Implements epic #653 graph-maintenance #4 (curation). The crux: invalid_at was
write-only — no read path honored it, so "retiring" an edge was a no-op. This
makes the bi-temporal window actually govern reads.

- store: get_related and list_relations exclude soft-retired edges by default
  (invalid_at in the past, or valid_at in the future) via a shared
  _RELATION_LIVE_CLAUSE; pass include_retired=True to surface them. Because the
  metrics subgraph and BFS traversal both build on these reads, retired edges
  drop out of metrics/traverse automatically.
- store: new retire_relation(relation_id, invalid_at=now) soft-retire (idempotent,
  preferred over the hard remove) and revalidate_relation(relation_id) to clear
  invalid_at (add_relation's upsert cannot reset it to NULL).
- mcp: distillery_relations action="retire" / "revalidate"; the "get" action
  gains an include_retired passthrough.

Deferred to a follow-up: auto-retiring an entry's edges when it is
superseded/corrected, and scheduled threshold-based retirement.

Tests: retired edge excluded from get_related/list_relations/traverse, surfaced
with include_retired, revalidate restores; future invalid_at stays live; retire
idempotent + missing-id; MCP retire/revalidate roundtrip. Two existing
attribute-round-trip tests updated to read with include_retired (their fixtures
set a past invalid_at). Full suite 3215 passed; mypy --strict and ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant