Domain¶
The words and the rules. Every concept the service is built from is defined here and nowhere else; every other doc uses these terms and links back rather than redefining them (D28). The invariants are the constitution: the architecture cites them by number, the scenarios exercise them, and code that violates one is wrong even if its tests pass.
Created 2026-09-03 (moved verbatim from architecture.md, where these sections lived since
2026-09-01; invariant 10 added). Updated 2026-09-03. Diagrams: Excalidraw board
a8d17080-29dd-47c2-98d7-b1fcaee9c0ab.
Concepts¶
Defined in pipeline order. Each term is used freely after its definition.
- Source: one immutable ingested item (an email, a meeting recording, a calendar event,
a document, a note). Carries
external_id,external_system,content_hash, andorganization_id. Re-ingesting identical content is a no-op; changed content mints a new row that supersedes the prior version (status: active | superseded,superseded_by, same model as EntityLink). Content is immutable; version state lives beside it. Only the active version composes into events. - Event: a composition of one or more sources that belong together (a meeting recording
plus its calendar event; a note plus its attachments). The unit the extractor reads.
Carries a
revisionthat increments whenever its active source set changes; extraction output is keyed by it (see changed-source reconciliation). - SourceSummary / EventSummary: derived situating context. One per source (generated
once, sources are immutable) and one per event, keyed by id plus
summarizer_version. They feed contextual embeddings and the resolution judge; claims reach their context through lineage, nothing attaches to Claim itself. - Partial entity: what the extractor believes one event says about one entity. Carries
a
kind(v0:person | organization; more kinds are a registry bump, D22) and claims. A partial entity must carry at least one hard claim (invariant 2 below). - Claim: a statement a source makes about a partial entity.
- Hard claim: queryable and normalizable. Types today:
email,domain,alias,language,nationality. The type registry is versioned and shared (see interfaces.md). - Soft claim: free-form ("wants to invest 50M in cybersecurity"). Stored, not processed. Deferred to profile generation (PRD non-goals).
- Relation claim: a claim carrying
target_partial_id, a directed edge from its carrier (the subject) to another partial of the same event. Types v1:employment,investment,partnership,governance, plus boundedother. Classified queryable (structured), non-identifying, temporal. Relation types declareendpoint_kinds,directional, and a per-type attributes schema with nullable validity intervals. Full design, decided with the team 2026-09-01 (D16-D21): relationships.md. - Classification runs on two orthogonal dimensions, and every claim type carries both:
- Queryability: hard vs soft, as above. Hard claims subdivide into identifying
(resolve to a single entity: email, external ids) and non-identifying (shareable
across entities: alias, nationality, language). The identifying class is keyed by
(claim type, entity kind) where it differs (D22):
domainis identifying for an organization and non-identifying for a person. - Groundedness: an identifying claim's value must appear literally in the event's source text (modulo canonicalization) or the extractor output is rejected at the contract boundary (D23, PRD E6). A hallucinated identifying claim would route a silent member merge; non-identifying and soft claims may be legitimately inferred.
- Temporality: constant (inherent, does not change: name, nationality) vs temporal (changes or expands over time: location, spoken languages). Temporal types further declare a cardinality: exclusive (one value at a time: location) or accumulative (values add up: languages, aliases).
- Queryability: hard vs soft, as above. Hard claims subdivide into identifying
(resolve to a single entity: email, external ids) and non-identifying (shareable
across entities: alias, nationality, language). The identifying class is keyed by
(claim type, entity kind) where it differs (D22):
- Every claim is time-anchored through lineage: its event's
occurred_at. For relation claims that anchor isreported_atonly; when the attributes state a validity interval (valid_from/valid_to), the validity drives belief and consistency, not the event date (D19). - Entity: a canonical or unresolved node in the knowledge base. An entity is a view over its active links, not a container of copied data.
- EntityLink: the first-class edge between an entity and a partial entity.
kind: member | evidence,driving_claim_id,status: active | superseded,superseded_by,superseded_reason. Links are never deleted. - Member: the partial is part of the entity's identity. Its hard claims become the entity's queryable identity.
- Evidence: associated but not identity-defining. Its claims never extend the queryable identity. Evidence may point at several candidate entities at once and counts toward promotion.
Invariants¶
- Data is never destroyed, only composed. Sources are immutable; links are superseded, never deleted; every stage's output is keyed by its input and its version, so any stage can re-run without overwriting prior output.
- A partial entity carries at least one hard claim. Enforced at extraction. This keeps every node queryable and stops the LLM minting entities from loose soft facts ("likes football" alone is not an entity).
- Queryable identity comes from active member links only. Evidence never leaks into the search projection. This blocks transitive poisoning: one wrong name-level link must not inject an unverified email into an entity's identity.
- At most one active member link per partial entity (database constraint). Evidence links: one per candidate entity, several allowed.
- A clear no-match is not an error. The search interface distinguishes
hits | no_match | error. Promotion to canonical is legal only on positive no-match assertions; a failed or unresponsive search never counts. - Every merge records the claim that drove it. Wrong merges are queryable by their driving claim and undone by superseding links.
- Resolutions apply sequentially per organization. Extraction and matching may run in parallel; the applier is a per-org FIFO consumer. This prevents duplicate unresolved entities when two events mention the same unknown person concurrently.
- Multi-tenant from the first migration. Every row is scoped by
organization_id. -
Relationship usability is derived, never stored. A relation claim is active iff every endpoint partial holds an active member link to a canonical entity, latent otherwise. No stored relationship state exists at entity level; entity-level relationships are query-time traversals through member links (relationships.md, D16).
-
Adding new evidence never requires re-ingesting the original source. The source payload is stored once and every later stage reads from storage: summaries, extraction runs, claim projections, links, and judge verdicts all attach to stored sources and events under their own versions. Indra re-reads an external system only when that system changed the content (I4), never because Indra wants more from it. Ratified with D28; previously implied by invariant 1 and the projection rules.
Code that violates an invariant is wrong even if its tests pass.
What each concept rests on¶
| Concept | Decisions | Mechanics |
|---|---|---|
| Source, versions | D11, D14 | changed-source reconciliation, PRD I1, I4 |
| Event, revision | D11, D15 | pipeline, contract B1 |
| Summaries | D5, D10 | R1 report |
| Partial entity, kind | D3, D22, D23 | contract B2, PRD E3, E6, E7 |
| Claim classification | D4, D8 | claim-type registry |
| Relation claim | D16-D21, D24 | registry, matching; history in relationships proposal |
| Entity, EntityLink | D1, D2, D6, D7, D12 | matching and resolution, PRD S1-S5, R1-R3 |