Decision log¶
Append-only. One entry per decision. Superseding a decision never edits the old entry's
body: flip its Status line to superseded by Dn (date: why) and write the new entry
saying what changed. Git provides the versioning; this convention provides the audit
trail. Referenced from the PRD.
D1: Entities are views over append-only links¶
Status: active | Decided: 2026-09-01 | By: Agus
Entities are views over append-only links to partial entities; merging is linking, unmerging is superseding. Data is never destroyed, only composed.
D2: Two link kinds as first-class rows¶
Status: active | Decided: 2026-09-01 | By: Agus
Member and evidence links are first-class rows (EntityLink); queryable identity comes
from active member links only.
D3: Names are hard claims¶
Status: active | Decided: 2026-09-01 | By: Agus
Names are hard claims; every partial entity carries at least one hard claim.
D4: Two-dimensional claim classification¶
Status: active | Decided: 2026-09-01 | By: Agus
Claims classify on two dimensions: queryability (hard identifying / hard non-identifying / soft) and temporality (constant / temporal, with exclusive or accumulative cardinality). Mismatch handling is time-aware and asymmetric.
D5: Context embedding in v1¶
Status: active | Decided: 2026-09-01 | By: Agus
Eager, versioned source and event summaries ship in v1 (evidence in R1).
D6: Resolution judge scope and gating¶
Status: active | Decided: 2026-09-01 | By: Agus
The resolution judge lives in the entity-resolution phase only; verdicts are promote / supersede / inconclusive; shadow mode until it clears an eval dataset.
D7: Sequential applier per organization¶
Status: active | Decided: 2026-09-01 | By: Agus
The applier is sequential per organization.
D8: Semi-hard claims out of v1¶
Status: active | Decided: 2026-09-01 | By: Agus
Semi-hard claim types are out of v1; soft claims are stored, not processed.
D9: Stack¶
Status: active | Decided: 2026-09-01 | By: Agus
Python 3.12+, FastAPI, pydantic v2, Postgres, SQLAlchemy 2 + Alembic; search in-Postgres for v1 (transactional reindex requirement); LLM calls via OpenRouter. Observability tools: PostHog, Langfuse, BetterStack from the start.
D10: The human-capability ceiling and the review-workload metric¶
Status: active | Decided: 2026-09-01 | By: Agus
Indra auto-resolves only where a human could disambiguate the reference from the same context; the design's job is to capture that context (D5) and put it in front of the model. What no layer (deterministic, heuristic, stochastic) can decide queues for human review. The success metric is total human workload at fixed precision, not raw queue rate: a prepared queued case costs one accept/deny decision, a wrong merge that escapes costs a full investigation, so precision outweighs queue-minimization. The workload target is parameterized from the M1 report, never guessed.
D11: Changed-source re-ingest semantics¶
Status: active | Decided: 2026-09-01 | By: Agus
A changed source (same external_id/external_system, new content) recomposes into the
same event, never a new one, and forces re-extraction. Reconciliation is diff-based:
compare new extraction against prior output, keep what is unchanged, supersede the stale
claims and links for what changed. Neither blind append nor blind replace. Claims from a
superseded source version stop contributing to critical mass. Driving case: calendar
events mutate constantly. Mechanics, including the relation to extractor_version
re-runs, are owed by the architecture doc.
D12: Action log and undo scope¶
Status: active | Decided: 2026-09-01 | By: Agus
Every mutating action (merge, link create/supersede, promotion, bootstrap import, review verdict) is recorded in an action log with actor, timestamp, and inputs, command-pattern style; humans and the system write the same log, distinguished by actor. In v1, resolution actions expose one-step undo and an undo is itself a logged action; ingestion and bootstrap import are re-runnable, not undoable. The supersede model (D1) gives data-level reversibility; the action log is the operation-level record and the entry point for undo.
D13: One LLM interface, provider- and mode-agnostic¶
Status: active | Decided: 2026-09-01 | By: Agus
All LLM processing is called behind one interface that hides the actual call, so provider (OpenRouter or a specific vendor) can be switched once models are settled. The interface is batch-shaped: sequential calls (testing) and batch calls (production data) are a configuration switch, not a code change. Cost visibility (per-stage metrics, caps, alerts) is P0; production batch processing is P1 and gates any full-corpus run.
D14: No erasure in v1: accepted risk¶
Status: active | Decided: 2026-09-01 | By: Agus
Right-to-erasure is out of scope for v1 and the storage model does no preparatory work for it (no payload/topology separation, no crypto-shredding). This collides with D1 (data is never destroyed) by design and is accepted as a risk: if an erasure obligation lands, the retrofit is expensive and this decision is the first to revisit.
D15: Build order and milestone shape¶
Status: active | Decided: 2026-09-01 | By: Agus
Milestones are cut on pipeline stages, not on modules. A module is an ownership and contract boundary and carries its own internal milestones; the PRD tracks only the system gates where a stage joins the one before it and something end to end becomes true. The first wave is the ingestion and extraction stages. The entity store, applier, bootstrap, and matching are deferred as one block behind the M1 report: matching against an empty graph is a no-match generator, so entities must exist first, and the block's internal order is store and applier, then bootstrap import, then matching. Team reality drives the shape: v1 is built by two engineers, not by five module owners, so a milestone list with one module per milestone would leave most of it unstaffed. =======
D16 — Relationships are claims with a target¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
A relation is a Claim carrying target_partial_id: a directed edge from the subject
partial (the claim's carrier) to another partial of the same event. No separate
relation shape or list exists. Relations classify on the existing dimensions as
queryable (structured), non-identifying, temporal, so all claim machinery
(supersession per D11, provenance, judge, belief) is written once. Entity-level
relationships are never stored: they are query-time traversals (canonical, member
link, claim, member link, canonical). Usability is derived, the one new invariant:
a relation claim is active iff every endpoint partial holds an active member link to
a canonical entity; latent otherwise, with zero writes on promotion. Full design:
design/relationships.md.
D17 — The LLM is the relation parser¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
There is no deterministic path from text to a typed relation. The extractor's LLM
pass converts relational statements into claims with a target; without it they stay
soft claims (not queryable, not navigable). Relation extraction runs in the same
single pass as entity extraction and inherits extractor_version, eval datasets,
cost caps, and re-runnability. A two-pass split is a remedy if M1 shows mutual
degradation, not a default.
D18 — Relation-type registry v1; other is bounded, D8 stands¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
v1 relation types: employment, investment, partnership, governance.
social and service are excluded (noisy or not useful in Orbit production).
Statements fitting no type are emitted as type: other with the raw label,
preserving the pair structure. other is bounded: the label is stored and never
queried by label (no search adapter, no traversal by raw label), so D8 (semi-hard
claims out of v1) stands, not superseded. Registry v2 types are chosen from
measured other labels after M1.
D19 — Per-type relation attributes with validity intervals¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
Relation attributes are typed per relation type in the registry, not snippet-only
and not a free dict. Reason: the event's occurred_at is the wrong belief anchor
for relations ("John worked at Acme until 2018", said in a 2026 meeting, has
reported_at 2026 and a validity interval ending 2018; only the validity drives
belief). Every temporal relation type gets nullable valid_from/valid_to, filled
only when stated; reported_at (event lineage) is the fallback. Thin v1 schemas:
employment { role?, valid_from?, valid_to? }, investment { amount?, currency?,
round?, date? }, governance { role?, valid_from?, valid_to? },
partnership { valid_from?, valid_to? }.
D20 — Relations are candidate-narrowing signals in matching¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
Relation claims behave in matching exactly like nationality: contextual, non-identifying signals that narrow candidates and never route a match. This is v1 behavior, not deferred.
D21 — Relationship timing, traversal, and v1 conflict handling¶
Status: active | Decided: 2026-09-01 | By: Agus, with team review
Relation claims are emitted from milestone 3 and measured in M1 (yield and precision per type); nothing consumes them until milestone 5. Traversal is computed at query time in v1 (org-scoped SQL joins); materialization is a later optimization that changes no stored model. v1 conflict handling is pure projection: every active relation claim projects as-is with provenance and time anchors, contradictions visible. The post-M1 belief model is heuristics plus LLM judge passes applying the time-aware asymmetric rules (D4), anchored on the validity interval (D19), not the event date.
D22 — Partial entities are typed; classification is per entity kind¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
PartialEntity carries a kind. v0 kinds: person | organization; more kinds are a
registry/ontology version bump, not a migration. Registry classification (identifying
class, and search strategy where it differs) is keyed by (claim type, entity kind):
domain is non-identifying for a person and identifying for an organization. Without
this, relation-type endpoint_kinds (D16) is unenforceable and organizations rarely
reach canonical, leaving relation claims latent (invariant 9). Full argument:
design/v0-review.md, A1.
D23 — Groundedness gate on identifying claims¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
An identifying claim is rejected at the extraction contract boundary unless its value appears literally in the event's source text (modulo the claim type's canonicalization). Non-identifying and soft claims are exempt: they may be legitimately inferred. Rationale: a hallucinated identifying claim is the worst extractor output, a well-formed email that was never in the text routes a silent member merge. PRD requirement E6. design/v0-review.md, A2.
D24 — Relation claims under changed-source reconciliation¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
The reconciliation diff (D11) aligns relation claims across runs by (type,
aligned-target partial, attributes), since they carry no value. When a target partial
is superseded and an aligned successor exists, a relation claim on an unchanged subject
partial is re-issued against the successor and the prior claim superseded
(source_changed), in the same reconciliation transaction.
design/v0-review.md, A3.
D25 — M1 ground truth and stability metrics¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
Precision numbers in M1 require a labeled eval set; its plan (size, sampling, who labels) must exist before the measured corpus is extracted (milestone 3 done-when). M1 additionally reports alignment stability under re-extraction (share of partials that re-align with themselves on an unchanged event) and identifying-claim error rate per source type; the latter decides whether low-trust identifying hits need a trust gate (deferred, D27). design/v0-review.md, A4.
D26 — Organizations first within bootstrap¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
Within D15's internal order (store and applier, then bootstrap, then matching), bootstrap imports organizations before people. Relations are usable only with canonical endpoints (invariant 9) and the object of most employment, investment, and governance claims is an organization; canonical orgs are what turn latent relation claims into traversable relationships. design/v0-review.md, B1.
D27 — Four bottlenecks deferred by design¶
Status: active | Decided: 2026-09-03 | By: Agus, team approved
Named, real, and not v0 work: applier FIFO priority discipline (live resolution vs judge verdicts vs bootstrap imports), judge fan-out caps on common aliases, a materialized relationship-traversal projection, and a trust gate on identifying hits from low-trust sources. Each is recognized on arrival and parameterized from measured data (M1, C1 cost caps); none is built speculatively. Table with arrival conditions and the later fix: design/v0-review.md, section C.
D28: Nine doc types, one place per definition¶
Status: active | Decided: 2026-09-03 | By: Nacho, with Benve's review
The writing-docs layering gains three types: domain (docs/domain.md, concepts and
invariants), scenarios (docs/scenarios/, worked inputs with expected outcomes), and
proposals (docs/proposals/, RFC-shaped docs frozen on ratification). Concepts and
invariants are defined only in domain.md; every other doc links there. Proposals are
linked for history only, never for a definition; a deferred item worked later arrives as
its own proposal. Invariant 10 ratified with this entry: adding new evidence never requires
re-ingesting the original source. Event vs Episode naming is deferred, not a priority
(Nacho, 2026-09-03). Proposal: proposals/docs-structure.md.