Skip to content

D107 spike 1: deterministic relation seeding under D88 concurrency (amend D88 relation upsert) #365

Description

@fazpu

Part of #364 (D107 temporal clocks). Gates WP-T.1. Lands as a design amendment + decision, reviewed by Codex, before any WP-T.1 code.

Problem (design §12.1; raised by Codex review round 5 on #362)

D107 seeds a fact's verdict window once from its seed claim — "the claim whose processing created the row" — and records it in seed_claim_id and the add adjudication's triggering_claim_id. For observations this is deterministic: D90 drains staging per entity in the total order (asserted_at NULLS LAST, claim_id, statement), so the seed is the first claim in that order. For relations it is not: D88 lets concurrent claim jobs run upsert relations for this claim directly (FactCatalog.upsert_relation, fact_catalog.py:39-105), and D88's decision text says "relation evidence attach is commutative under concurrency". A per-key advisory lock serialises whichever worker arrives first, not the first claim in the specified total order. Recording seed_claim_id atomically therefore records a race: two schedules can pick different seed windows (and, for occurrences, different occurrence identities), which breaks D7 rebuild determinism and D88's own order-independence goal.

The same path is why D107 §4.2 requires the relation write to become staged: today upsert_relation finds a live triple and attaches evidence before the relation ladder runs (supersession.py:169-178 treats the same redirected object as an exact no-op), which would collapse two same-triple occurrences before any verdict.

What the spike must decide

  1. A durable, claim-idempotent relation staging relation (the analogue of normalize_observation_staging), with the fields the verdict needs (claim id, triple, canonical D41 window, kind, said-on).
  2. A post-barrier per-block drain in the chosen total order — per (subject, predicate, object) block, or per subject entity as D90 does — including cross-version single-flight behaviour so two versions of one document cannot interleave (mirror D90 §5.5).
  3. The atomic new transaction: fact row + add adjudication (triggering_claim_id populated) + evidence link; the atomic evidence attach; idempotency on (triggering_claim_id, adjudicator generation) so retries and concurrent normalisers replay the recorded verdict.
  4. Interaction with the D88 barrier/fan-out (e3_claim_level_normalize_fanout_design.md §§5.3–5.5) and with the D90 obs flush: does relation drain run in the same unit, after it, or as its own stage (adjudicate_supersession today runs after normalize)?
  5. Amend D88's "commutative relation upsert" contract and e3_claim_level_normalize_fanout_design.md; roll the normaliser/adjudicator generations and the LoCoMo protocol.

Acceptance

A same-triple occurrence claim is held unattached until its verdict; two normalisers racing on one block produce the same seed and the same rows in any schedule; a retried claim replays its verdict; the D90 reversed-order acceptance case has a relation twin.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions