rememberstack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. rememberstack/__init__.py +9 -0
  2. rememberstack/adapters/__init__.py +42 -0
  3. rememberstack/adapters/codex_writer.py +221 -0
  4. rememberstack/adapters/markitdown_converter.py +42 -0
  5. rememberstack/adapters/openrouter.py +136 -0
  6. rememberstack/adapters/selfhost/__init__.py +54 -0
  7. rememberstack/adapters/selfhost/forget.py +66 -0
  8. rememberstack/adapters/selfhost/git.py +374 -0
  9. rememberstack/adapters/selfhost/lance.py +328 -0
  10. rememberstack/adapters/selfhost/minio.py +279 -0
  11. rememberstack/adapters/selfhost/mounts.py +249 -0
  12. rememberstack/adapters/selfhost/object_store.py +130 -0
  13. rememberstack/adapters/selfhost/projection.py +80 -0
  14. rememberstack/adapters/selfhost/queue.py +137 -0
  15. rememberstack/adapters/selfhost/telemetry.py +45 -0
  16. rememberstack/adapters/selfhost/watcher.py +70 -0
  17. rememberstack/adapters/testing/__init__.py +15 -0
  18. rememberstack/adapters/testing/cost_meter.py +13 -0
  19. rememberstack/adapters/testing/model_provider.py +83 -0
  20. rememberstack/adapters/testing/queue.py +43 -0
  21. rememberstack/adapters/testing/telemetry.py +22 -0
  22. rememberstack/client.py +19 -0
  23. rememberstack/core/__init__.py +127 -0
  24. rememberstack/core/blockizer.py +189 -0
  25. rememberstack/core/chunker.py +216 -0
  26. rememberstack/core/consumption_skill.py +275 -0
  27. rememberstack/core/conversion.py +76 -0
  28. rememberstack/core/core_manifest.py +598 -0
  29. rememberstack/core/extension_packs.py +124 -0
  30. rememberstack/core/forget.py +17 -0
  31. rememberstack/core/knowledge_authored.py +276 -0
  32. rememberstack/core/knowledge_compile.py +215 -0
  33. rememberstack/core/knowledge_fact_sheet.py +210 -0
  34. rememberstack/core/knowledge_hashing.py +68 -0
  35. rememberstack/core/knowledge_planner.py +64 -0
  36. rememberstack/core/knowledge_writer.py +175 -0
  37. rememberstack/core/ranking.py +200 -0
  38. rememberstack/core/recipe_linter.py +149 -0
  39. rememberstack/core/section_snap.py +209 -0
  40. rememberstack/core/storage_routing.py +27 -0
  41. rememberstack/eval/__init__.py +53 -0
  42. rememberstack/eval/consumption.py +141 -0
  43. rememberstack/eval/contradiction.py +184 -0
  44. rememberstack/eval/harness.py +136 -0
  45. rememberstack/eval/lifecycle.py +400 -0
  46. rememberstack/eval/operational_scale.py +49 -0
  47. rememberstack/eval/resolution.py +255 -0
  48. rememberstack/eval/retrieval_spikes.py +50 -0
  49. rememberstack/eval/skeleton.py +231 -0
  50. rememberstack/llm/__init__.py +1 -0
  51. rememberstack/model/__init__.py +589 -0
  52. rememberstack/model/adjudication.py +100 -0
  53. rememberstack/model/auth.py +27 -0
  54. rememberstack/model/blocks.py +30 -0
  55. rememberstack/model/chunks.py +190 -0
  56. rememberstack/model/claims.py +162 -0
  57. rememberstack/model/client.py +98 -0
  58. rememberstack/model/clustering.py +54 -0
  59. rememberstack/model/component_version.py +124 -0
  60. rememberstack/model/consumption.py +88 -0
  61. rememberstack/model/conversion.py +31 -0
  62. rememberstack/model/deployment.py +53 -0
  63. rememberstack/model/documents.py +168 -0
  64. rememberstack/model/envelope.py +513 -0
  65. rememberstack/model/evaluation.py +72 -0
  66. rememberstack/model/forget.py +143 -0
  67. rememberstack/model/git.py +13 -0
  68. rememberstack/model/knowledge.py +840 -0
  69. rememberstack/model/knowledge_authored.py +325 -0
  70. rememberstack/model/knowledge_planner.py +431 -0
  71. rememberstack/model/lifecycle.py +42 -0
  72. rememberstack/model/model_provider.py +78 -0
  73. rememberstack/model/mounts.py +24 -0
  74. rememberstack/model/object_store.py +21 -0
  75. rememberstack/model/operational_scale.py +59 -0
  76. rememberstack/model/operations.py +153 -0
  77. rememberstack/model/processing.py +228 -0
  78. rememberstack/model/queue.py +73 -0
  79. rememberstack/model/recipes.py +83 -0
  80. rememberstack/model/relations.py +79 -0
  81. rememberstack/model/resolution.py +83 -0
  82. rememberstack/model/retrieval_spikes.py +62 -0
  83. rememberstack/model/sections.py +120 -0
  84. rememberstack/model/telemetry.py +30 -0
  85. rememberstack/ports/__init__.py +29 -0
  86. rememberstack/ports/auth.py +16 -0
  87. rememberstack/ports/connector.py +23 -0
  88. rememberstack/ports/cost_meter.py +17 -0
  89. rememberstack/ports/forget.py +20 -0
  90. rememberstack/ports/git.py +20 -0
  91. rememberstack/ports/model_provider.py +28 -0
  92. rememberstack/ports/mounts.py +16 -0
  93. rememberstack/ports/object_store.py +27 -0
  94. rememberstack/ports/p1_index.py +92 -0
  95. rememberstack/ports/purge.py +93 -0
  96. rememberstack/ports/queue.py +23 -0
  97. rememberstack/ports/telemetry.py +21 -0
  98. rememberstack/profiles/__init__.py +22 -0
  99. rememberstack/profiles/selfhost.py +324 -0
  100. rememberstack/profiles/selfhost_forget.py +158 -0
  101. rememberstack/profiles/selfhost_operations.py +95 -0
  102. rememberstack/py.typed +1 -0
  103. rememberstack/spine/__init__.py +93 -0
  104. rememberstack/spine/admission.py +26 -0
  105. rememberstack/spine/backfill.py +168 -0
  106. rememberstack/spine/catalog_contract.py +742 -0
  107. rememberstack/spine/chunk_catalog.py +237 -0
  108. rememberstack/spine/claim_catalog.py +298 -0
  109. rememberstack/spine/clustering.py +740 -0
  110. rememberstack/spine/component_versions.py +208 -0
  111. rememberstack/spine/consumption.py +81 -0
  112. rememberstack/spine/deployment_bootstrap.py +445 -0
  113. rememberstack/spine/document_catalog.py +621 -0
  114. rememberstack/spine/entity_registry.py +205 -0
  115. rememberstack/spine/extension_packs.py +220 -0
  116. rememberstack/spine/fact_catalog.py +571 -0
  117. rememberstack/spine/forget.py +1753 -0
  118. rememberstack/spine/knowledge.py +5467 -0
  119. rememberstack/spine/lifecycle.py +1071 -0
  120. rememberstack/spine/migrations/__init__.py +1 -0
  121. rememberstack/spine/migrations/_helpers.py +153 -0
  122. rememberstack/spine/migrations/env.py +58 -0
  123. rememberstack/spine/migrations/script.py.mako +27 -0
  124. rememberstack/spine/migrations/versions/__init__.py +1 -0
  125. rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
  126. rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
  127. rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
  128. rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
  129. rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
  130. rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
  131. rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
  132. rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
  133. rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
  134. rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
  135. rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
  136. rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
  137. rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
  138. rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
  139. rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
  140. rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
  141. rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
  142. rememberstack/spine/observation_adjudication.py +778 -0
  143. rememberstack/spine/operations.py +298 -0
  144. rememberstack/spine/projection.py +662 -0
  145. rememberstack/spine/recipes.py +276 -0
  146. rememberstack/spine/resolver.py +763 -0
  147. rememberstack/spine/review.py +650 -0
  148. rememberstack/spine/settings.py +22 -0
  149. rememberstack/spine/supersession.py +510 -0
  150. rememberstack/spine/sync.py +128 -0
  151. rememberstack/spine/work_ledger.py +816 -0
  152. rememberstack/surfaces/__init__.py +110 -0
  153. rememberstack/surfaces/cli.py +447 -0
  154. rememberstack/surfaces/consumption_skill.py +87 -0
  155. rememberstack/surfaces/graph_queries.py +698 -0
  156. rememberstack/surfaces/http_api.py +377 -0
  157. rememberstack/surfaces/mcp.py +67 -0
  158. rememberstack/surfaces/query_engine.py +1591 -0
  159. rememberstack/surfaces/recipe_executor.py +185 -0
  160. rememberstack/surfaces/recipe_surface.py +219 -0
  161. rememberstack/surfaces/remote_mcp.py +133 -0
  162. rememberstack/surfaces/sdk.py +324 -0
  163. rememberstack/workers/__init__.py +155 -0
  164. rememberstack/workers/base.py +312 -0
  165. rememberstack/workers/e0.py +577 -0
  166. rememberstack/workers/e1.py +425 -0
  167. rememberstack/workers/e2.py +525 -0
  168. rememberstack/workers/e3.py +434 -0
  169. rememberstack/workers/forget.py +299 -0
  170. rememberstack/workers/knowledge_authored.py +146 -0
  171. rememberstack/workers/knowledge_driver.py +735 -0
  172. rememberstack/workers/knowledge_fact_sheet.py +123 -0
  173. rememberstack/workers/knowledge_planner.py +325 -0
  174. rememberstack/workers/knowledge_writer.py +393 -0
  175. rememberstack/workers/operations.py +42 -0
  176. rememberstack/workers/p1.py +234 -0
  177. rememberstack/workers/p2.py +513 -0
  178. rememberstack/workers/p2_analytics.py +276 -0
  179. rememberstack/workers/p3.py +673 -0
  180. rememberstack/workers/reconcile.py +485 -0
  181. rememberstack/workers/sync.py +168 -0
  182. rememberstack-0.1.0.dist-info/METADATA +213 -0
  183. rememberstack-0.1.0.dist-info/RECORD +186 -0
  184. rememberstack-0.1.0.dist-info/WHEEL +4 -0
  185. rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
  186. rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,411 @@
1
+ """Create claim, relation, observation, and evidence structures."""
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from rememberstack.spine.migrations._helpers import apply_ddl
6
+ from rememberstack.spine.migrations._helpers import drop_tables
7
+
8
+ revision: str = "p0_02_0004"
9
+ down_revision: str | None = "p0_02_0003"
10
+ branch_labels: str | Sequence[str] | None = None
11
+ depends_on: str | Sequence[str] | None = None
12
+
13
+ _DDL = r"""-- ─────────────────────────────────────────────────────────────────────────
14
+ -- claims — immutable verifiable propositions (D31/D32). THREE immutable time axes (concepts §5, D41):
15
+ -- asserted_at = assertion-EVENT time (when the source spoke, ≈ published_at); claim_valid_from/until
16
+ -- (+ precision/kind) = the world-time interval the SOURCE asserted the proposition held (valid-time
17
+ -- as EVIDENCE, not current belief); ingested_at = when WE extracted it (transaction-time). None ever
18
+ -- change, and claim validity is never superseded (D3) — adjudicated validity lives only on relations.
19
+ -- A row in claims is an ACCEPTED claim: the deterministic grounding gate (anchor + window
20
+ -- membership, D32 layers 1-2) MUST pass, enforced by the CHECK — a claim that fails the gate is
21
+ -- never produced (it becomes a ledger entry or is discarded), so the flags exist for audit and are
22
+ -- always true here. Large (~5×10⁷) ⇒ monthly partition by ingested_at; logical FKs (D23).
23
+ -- ─────────────────────────────────────────────────────────────────────────
24
+ CREATE TABLE claims (
25
+ claim_id uuid NOT NULL,
26
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments
27
+ doc_id uuid NOT NULL, -- LOGICAL FK → documents (provenance always attached — requirements_v3)
28
+ chunk_id uuid NOT NULL, -- LOGICAL FK → chunks (the target chunk of the context bundle)
29
+ section_id uuid, -- LOGICAL FK → document_sections (denormalized role/path; explains a Selection decision)
30
+ claim_text text NOT NULL, -- the STANDALONE assertion — what retrieval/E3/reasoning use (decontextualized, D32)
31
+ source_span text NOT NULL, -- the verbatim slice of the chunk the claim derives from (provenance/audit, D32)
32
+ char_start integer NOT NULL, -- source_span offset into document.md (anchor check: a real in-bounds slice, D32 layer 1)
33
+ char_end integer NOT NULL,
34
+ added_context jsonb NOT NULL DEFAULT '[]', -- [{text, source_kind: header|neighbour|prefix|hint, source_ref}] — each substring decontextualization ADDED (D32 layer 2)
35
+ temporal_class claim_temporal_class, -- static | dynamic | atemporal — the "temporally classified" requirement (see reconciliation note)
36
+ is_attributed boolean NOT NULL DEFAULT false, -- preserves a "X said Y" attribution (entailment rule: entails "X said Y", not "Y" — D32)
37
+ -- grounding verdicts (D32). Deterministic layers 1-2 are an ACCEPTANCE GATE (must be true here);
38
+ -- the LLM layers 3-4 are advisory/sampled and may be false on a kept-but-borderline claim:
39
+ anchor_ok boolean NOT NULL, -- layer 1: source_span is a real in-bounds slice of the chunk (deterministic)
40
+ window_membership_ok boolean NOT NULL, -- layer 2: every added_context substring verbatim-exists in its declared bundle source (deterministic; rejects fabrication)
41
+ entailment_self_verdict boolean, -- layer 3: in-call self-assertion the bundle entails the claim (~free, optimistic)
42
+ audit_status grounding_audit_status NOT NULL DEFAULT 'unaudited', -- layer 4: unaudited | sampled_pass | sampled_fail | escalated (sampled, not per-claim)
43
+ kept_flagged boolean NOT NULL DEFAULT false, -- D35 low-confidence Selection outcome: kept but marked-for-review (mirrors a selection_keep_flagged ledger row — see invariant below)
44
+ is_current_testimony boolean NOT NULL DEFAULT true, -- D54 CACHE of testimony currency (the ledger below is truth): false once a newer extraction generation covers this chunk, or (living mode) the chunk left the current version. Bookkeeping, NEVER validity — no adjudication, claims stay immutable in every D3 sense
45
+ asserted_at timestamptz, -- ASSERTION-EVENT time: when the source asserted this (≈ the version's source_modified_at/published_at, D55) — immutable; NOT the fact's world-time (that is claim_valid_*, D41)
46
+ -- D41 source-asserted world-validity INTERVAL — immutable evidence about WHEN (not current belief).
47
+ -- Overlap of these intervals across sources is EXPECTED (it is evidence), so there is deliberately
48
+ -- NO uniqueness/EXCLUDE, NO invalidated_at, NO status here — the opposite of relations (§9):
49
+ claim_valid_from timestamptz, -- world-time start the SOURCE attributed (NULL = unbounded-before/unknown); immutable
50
+ claim_valid_until timestamptz, -- world-time end (NULL = open-per-source OR unknown; disambiguated by claim_valid_precision)
51
+ claim_valid_precision claim_valid_precision NOT NULL DEFAULT 'unknown', -- unknown|instant|day|month|quarter|year|open — "FY2023" stores a normalized [start,end] without lying about granularity
52
+ claim_valid_kind claim_valid_kind, -- which world-interval this is: proposition_validity|event_time|measurement_period|effective_period (so a measurement period is never conflated with an event date or with asserted_at)
53
+ extractor_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions (extractor); replay-on-rebuild key (D33)
54
+ embedding_ref text, -- opaque Lance key (claims are searchable in P1)
55
+ embedding_version text, -- LOGICAL FK → pipeline_component_versions (embedder)
56
+ ingested_at timestamptz NOT NULL DEFAULT now(), -- transaction-time + partition key; immutable
57
+ PRIMARY KEY (claim_id, ingested_at),
58
+ CHECK (char_end >= char_start),
59
+ CHECK (anchor_ok AND window_membership_ok), -- a claims row is an ACCEPTED claim; the deterministic grounding gate passed (D32)
60
+ -- D41 precision/bounds coherence (claim validity carries no status/invalidated_at — it is immutable):
61
+ CHECK (claim_valid_until IS NULL OR claim_valid_from IS NULL OR claim_valid_until >= claim_valid_from),
62
+ CHECK (claim_valid_precision <> 'unknown' OR (claim_valid_from IS NULL AND claim_valid_until IS NULL)),
63
+ CHECK (claim_valid_precision <> 'open' OR (claim_valid_from IS NOT NULL AND claim_valid_until IS NULL)),
64
+ CHECK (claim_valid_precision <> 'instant' OR (claim_valid_from IS NOT NULL AND claim_valid_until = claim_valid_from)),
65
+ -- a bounded precision must actually carry both bounds (else it silently degrades to unknown/open):
66
+ CHECK (claim_valid_precision NOT IN ('day','month','quarter','year') OR (claim_valid_from IS NOT NULL AND claim_valid_until IS NOT NULL))
67
+ ) PARTITION BY RANGE (ingested_at);
68
+ COMMENT ON TABLE claims IS
69
+ 'E2 immutable verifiable propositions (D31/D32). Stores standalone claim_text + verbatim source_span + offsets + added_context for provenance-and-entailment grounding. Three immutable time axes (D41): asserted_at (assertion event), claim_valid_from/until (+precision/kind = source-asserted world-interval — evidence, not belief), ingested_at (system); never superseded (supersession is on relations, D3). A row here passed the deterministic grounding gate. Monthly-partitioned, logical FKs.';
70
+ CREATE INDEX ix_claims_doc ON claims (deployment_id, doc_id);
71
+ CREATE INDEX ix_claims_chunk ON claims (chunk_id);
72
+ CREATE INDEX ix_claims_flagged ON claims (deployment_id) WHERE kept_flagged = true; -- review surface (D35)
73
+ CREATE INDEX ix_claims_current ON claims (deployment_id, doc_id) WHERE is_current_testimony; -- the D54 hot filter (counts; default claim search)
74
+ CREATE INDEX ix_claims_audit ON claims (deployment_id) WHERE audit_status = 'sampled_fail'; -- grounding regressions
75
+ -- D41 claim-validity is projected to Lance (P1) as filterable scalar columns (claim_valid_from/until/
76
+ -- precision) beside the claim embedding (same pattern as relation windows, D8); the time-filter path
77
+ -- is Lance, so there is NO new Postgres index by default (preserves D23's btree-light mandate on this
78
+ -- ~5×10⁷ partitioned table). A `claims_as_of(t)` search recipe (D9) answers "what did sources assert
79
+ -- held over T" at the EVIDENCE grain; fact-as-of stays relations-only (D10) and the recipe registry
80
+ -- BARS claims_as_of from answering "currently true". An OPTIONAL partial btree on (deployment_id,
81
+ -- claim_valid_from, claim_valid_until) WHERE claim_valid_precision <> 'unknown' is added only if
82
+ -- PG-side temporal claim filtering is ever load-tested against D23 — a spike (§17), not a default.
83
+
84
+ -- ─────────────────────────────────────────────────────────────────────────
85
+ -- claim_extraction_decisions — the append-only, version-stamped extraction transcript (D33). It
86
+ -- records every Selection DROP (with reason), every low-confidence KEEP-FLAG, and every
87
+ -- decontextualization EDIT. Plain keeps are NOT recorded (they ARE the claims row) — keeping the
88
+ -- table sized for drops+flags+edits, not every keep. Rebuild reads stored claims + these decisions
89
+ -- and never re-calls the model (D7). Large ⇒ monthly partition by decided_at.
90
+ -- INVARIANT: a kept_flagged claim is the pair (claims row with kept_flagged=true) + (a
91
+ -- selection_keep_flagged decision here); the ledger is the replay source from which
92
+ -- claims.kept_flagged is reconstituted on rebuild.
93
+ -- ─────────────────────────────────────────────────────────────────────────
94
+ CREATE TABLE claim_extraction_decisions (
95
+ decision_id uuid NOT NULL,
96
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments
97
+ doc_id uuid NOT NULL, -- LOGICAL FK → documents
98
+ chunk_id uuid NOT NULL, -- LOGICAL FK → chunks
99
+ claim_id uuid, -- LOGICAL FK → claims; set for decontext edits + selection_keep_flagged; NULL for selection_drop (no claim produced)
100
+ decision_type extraction_decision_type NOT NULL, -- selection_drop | selection_keep_flagged | decontext_edit
101
+ source_span text, -- the proposition/sentence the decision was about
102
+ reason selection_drop_reason, -- for drops: opinion|advice|hypothetical|generic|question|intro|conclusion|no_info|ambiguous|references_boilerplate (D31)
103
+ edit_detail jsonb, -- for decontext edits: what was resolved/added and from which bundle source
104
+ protected_class text, -- never-drop class checked/applied (quantity|date|named_entity_predicate|change_of_state) — D35
105
+ extractor_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions; the version that made this decision
106
+ decided_at timestamptz NOT NULL DEFAULT now(), -- partition key
107
+ PRIMARY KEY (decision_id, decided_at)
108
+ ) PARTITION BY RANGE (decided_at);
109
+ COMMENT ON TABLE claim_extraction_decisions IS
110
+ 'Append-only Selection-drop + keep-flag + decontextualization-edit ledger (D33); plain keeps are not recorded. Makes aggressive Selection auditable and recoverable: a better prompt re-examines only the drops; rebuild replays from here without re-calling the model (D7). Monthly-partitioned.';
111
+ CREATE INDEX ix_cxd_chunk ON claim_extraction_decisions (chunk_id);
112
+ CREATE INDEX ix_cxd_drops ON claim_extraction_decisions (deployment_id, reason) WHERE decision_type = 'selection_drop';
113
+
114
+ -- ─────────────────────────────────────────────────────────────────────────
115
+ -- grounding_audits — the sampled independent entailment audit (D32 layer 4). Not per-claim
116
+ -- (self-grading is optimistic; a separate judge re-checks a sample; only a borderline band
117
+ -- escalates). claims.audit_status caches the latest result.
118
+ -- ─────────────────────────────────────────────────────────────────────────
119
+ CREATE TABLE grounding_audits (
120
+ audit_id uuid PRIMARY KEY,
121
+ deployment_id uuid NOT NULL REFERENCES deployments,
122
+ claim_id uuid NOT NULL, -- LOGICAL FK → claims (partitioned)
123
+ verdict grounding_audit_status NOT NULL, -- sampled_pass | sampled_fail | escalated
124
+ judge_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions (independent judge)
125
+ rationale text,
126
+ sampled_at timestamptz NOT NULL DEFAULT now()
127
+ );
128
+ COMMENT ON TABLE grounding_audits IS
129
+ 'Sampled independent entailment audits of claims (D32 layer 4). Offline, not per-claim; feeds claims.audit_status and the grounding eval suite.';
130
+ CREATE INDEX ix_grounding_claim ON grounding_audits (claim_id);
131
+
132
+ -- ─────────────────────────────────────────────────────────────────────────
133
+ -- testimony_currency_events — the D54 currency ledger (append-only; the D33 pattern: this is
134
+ -- truth, claims.is_current_testimony is cache). A transition is BOOKKEEPING, never validity:
135
+ -- no adjudication, no invalidated_at, nothing about the claim changes. Timestamped events keep
136
+ -- transaction-time reconstructions exact (fact-as-of-T still sees old generations).
137
+ -- Written by the reconciliation step of the lifecycle flow (evidence_lifecycle_design §5),
138
+ -- which runs only on COMPLETED basis changes and then recounts affected facts.
139
+ -- ─────────────────────────────────────────────────────────────────────────
140
+ CREATE TABLE testimony_currency_events (
141
+ event_id uuid NOT NULL,
142
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments
143
+ claim_id uuid NOT NULL, -- LOGICAL FK → claims (partitioned)
144
+ doc_id uuid NOT NULL, -- LOGICAL FK → documents (the lineage; the recount scope)
145
+ reconciliation_id uuid NOT NULL, -- identifies ONE reconciliation run = one completed basis change per lineage (a new version's extraction completing, or a version-bump re-extraction completing). Minted when the run starts and stored in processing_state, so a RETRIED run reuses it — its re-emitted events hit the UNIQUE below as no-ops
146
+ became_current boolean NOT NULL, -- false = lost currency; true = regained (un-delete, mode change)
147
+ reason currency_reason NOT NULL, -- reextracted | version_superseded | version_deleted
148
+ from_extractor_version text, -- the superseded generation (reason=reextracted)
149
+ from_version_id uuid, -- the superseded/deleted document version (reason=version_*)
150
+ occurred_at timestamptz NOT NULL DEFAULT now(), -- partition key
151
+ PRIMARY KEY (event_id, occurred_at),
152
+ UNIQUE (claim_id, reconciliation_id, reason, became_current, occurred_at) -- a retried reconciliation re-emits as a no-op, never a duplicate (F11)
153
+ ) PARTITION BY RANGE (occurred_at);
154
+ COMMENT ON TABLE testimony_currency_events IS
155
+ 'Append-only D54 testimony-currency transitions. Truth for claims.is_current_testimony (cache); replayable (D7). Reasons: reextracted (a newer extraction generation covers the chunk), version_superseded (living-mode lineage moved past the claim''s version), version_deleted. Never validity, never supersession — D3 untouched.';
156
+ CREATE INDEX ix_currency_claim ON testimony_currency_events (claim_id);
157
+ CREATE INDEX ix_currency_doc ON testimony_currency_events (deployment_id, doc_id, occurred_at);
158
+ -- ─────────────────────────────────────────────────────────────────────────
159
+ -- relations — distinct bi-temporal facts (D2/D3). The (entity_id,predicate) blocking key for
160
+ -- supersession is the composite index below; it is small (distinct facts, not assertions) —
161
+ -- what makes supersession affordable at scale (concepts §6). The canonical fact LABEL + its
162
+ -- embedding live in Lance (D8); PG keeps the label text + version + a Lance ref. Not partitioned.
163
+ --
164
+ -- "Live belief" = invalidated_at IS NULL (transaction-time), regardless of valid_until: a
165
+ -- believed-historical fact ("Alice worked at Acme 2020-2022", valid_until set, invalidated_at NULL)
166
+ -- is still currently believed. status is a GENERATED mirror of invalidated_at so validity has
167
+ -- exactly one authoritative home (D6) and cannot drift.
168
+ --
169
+ -- Uniqueness: a GiST EXCLUSION constraint forbids two BELIEVED, non-contradictory relations with
170
+ -- the same (s,p,o) AND OVERLAPPING valid-time windows. This is more correct than a partial unique
171
+ -- index: it permits re-occurring facts with non-overlapping windows (Alice worked at Acme twice)
172
+ -- and permits contradictions (carved out via contradiction_group), while forbidding duplicate
173
+ -- overlapping beliefs. Evidence-collapse (D2) finds the believed relation whose window covers the
174
+ -- new claim's time.
175
+ -- ─────────────────────────────────────────────────────────────────────────
176
+ CREATE TABLE relations (
177
+ relation_id uuid PRIMARY KEY, -- the fact's identity; provenance handle in the graph/Lance projections
178
+ deployment_id uuid NOT NULL REFERENCES deployments,
179
+ subject_entity_id uuid NOT NULL, -- canonical subject (composite FK below; only canonical entities enter relations/graph — p2 §2)
180
+ predicate text NOT NULL, -- governed predicate; composite FK below
181
+ object_entity_id uuid NOT NULL, -- canonical object (entity→entity only; literals stay in claims — D2)
182
+ -- bi-temporality (concepts §5): two clocks, different questions.
183
+ valid_from timestamptz, -- VALID-time start: when the fact began holding in the world (NULL = unknown/always)
184
+ valid_until timestamptz, -- VALID-time end: closed by supersession when the fact stops holding ("Alice left Acme")
185
+ ingested_at timestamptz NOT NULL DEFAULT now(), -- TRANSACTION-time: when the system first believed this fact
186
+ invalidated_at timestamptz, -- TRANSACTION-time: when the system learned it was superseded (NULL = still believed)
187
+ evidence_count integer NOT NULL DEFAULT 0, -- cached count of DISTINCT DOCUMENT LINEAGES with current-testimony supporting claims (D54 — invariant under re-extraction/version churn/intra-doc repetition); confidence/salience signal (D2 refined); K3 candidate filter
188
+ contradict_count integer NOT NULL DEFAULT 0, -- cached count of distinct current-testimony lineages contradicting (same D54 rule, stance=contradicts)
189
+ confidence real, -- aggregate confidence over evidence (not an extraction-time guess — concepts §3)
190
+ contradiction_group uuid, -- shared id when two live relations contradict and can't be adjudicated — retrieval shows both sides (concepts §4)
191
+ status relation_status GENERATED ALWAYS AS -- DERIVED mirror of invalidated_at (single validity home, D6): active iff invalidated_at IS NULL, else invalidated
192
+ (CASE WHEN invalidated_at IS NOT NULL THEN 'invalidated'::relation_status ELSE 'active'::relation_status END) STORED,
193
+ -- fact label (D8): the human-readable sentence embedded in Lance; regenerated only on material adjudication change.
194
+ fact_label text, -- "Alice Novak works at Acme as VP of Engineering"
195
+ fact_label_version text, -- LOGICAL FK → pipeline_component_versions (fact_labeler)
196
+ fact_label_embedding_ref text, -- opaque Lance key for the fact-label vector (P1; no vectors in PG/graph — D8)
197
+ normalizer_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions (normalizer); replay-on-rebuild
198
+ created_at timestamptz NOT NULL DEFAULT now(),
199
+ updated_at timestamptz NOT NULL DEFAULT now(),
200
+ UNIQUE (deployment_id, relation_id), -- composite-FK target (tenancy isolation, §0)
201
+ FOREIGN KEY (deployment_id, predicate) REFERENCES predicates (deployment_id, predicate) ON UPDATE CASCADE,
202
+ FOREIGN KEY (deployment_id, subject_entity_id) REFERENCES entities (deployment_id, entity_id),
203
+ FOREIGN KEY (deployment_id, object_entity_id) REFERENCES entities (deployment_id, entity_id),
204
+ CHECK (valid_until IS NULL OR valid_from IS NULL OR valid_until >= valid_from),
205
+ CHECK (invalidated_at IS NULL OR invalidated_at >= ingested_at), -- can't un-learn before learning
206
+ -- At most one BELIEVED, non-contradictory relation per (s,p,o) with overlapping world-time:
207
+ EXCLUDE USING gist (
208
+ deployment_id WITH =, subject_entity_id WITH =, predicate WITH =, object_entity_id WITH =,
209
+ tstzrange(valid_from, valid_until) WITH &&
210
+ ) WHERE (invalidated_at IS NULL AND contradiction_group IS NULL)
211
+ );
212
+ COMMENT ON TABLE relations IS
213
+ 'E3 distinct bi-temporal facts (D2/D3). Identity = (subject,predicate,object) + validity interval; the unit of supersession/contradiction. evidence_count caches corpus redundancy as a confidence signal (D2). status is a generated mirror of invalidated_at (validity has one home, D6). The GiST EXCLUDE forbids overlapping duplicate beliefs while allowing re-occurring facts and carved-out contradictions. fact_label+embedding live in Lance (D8).';
214
+ -- The supersession blocking key (D4) — small, distinct facts; THE index that makes supersession
215
+ -- detection affordable (concepts §6):
216
+ CREATE INDEX ix_relations_block_subj ON relations (deployment_id, subject_entity_id, predicate, object_entity_id);
217
+ CREATE INDEX ix_relations_block_obj ON relations (deployment_id, object_entity_id, predicate); -- reverse blocking ("who works_at acme?")
218
+ CREATE INDEX ix_relations_predicate ON relations (deployment_id, predicate);
219
+ CREATE INDEX ix_relations_contradiction ON relations (contradiction_group) WHERE contradiction_group IS NOT NULL;
220
+ CREATE INDEX ix_relations_live ON relations (deployment_id, subject_entity_id) WHERE invalidated_at IS NULL;
221
+ -- ─────────────────────────────────────────────────────────────────────────
222
+ -- relation_evidence — the many-to-many join claims ⇄ relations (D2). "Where corpus redundancy goes
223
+ -- to die": 200 documents asserting the same fact = one relation + 200 rows here. ~10⁸ rows.
224
+ --
225
+ -- Partitioned by HASH(relation_id), NOT by ingest month — because every hot access is by
226
+ -- relation_id (hydration) and the evidence-once invariant is on (relation_id, claim_id). With the
227
+ -- partition key = relation_id: relation hydration prunes to ONE partition, AND a real
228
+ -- PRIMARY KEY (relation_id, claim_id) enforces "a claim evidences a relation at most once" in-DB
229
+ -- (so relations.evidence_count cannot be inflated by a retry — a re-link is an ON CONFLICT no-op).
230
+ -- This is D23's evidence-join policy. Hash partitions are STATIC (64 migration-created children;
231
+ -- a measured starting point), so no pg_partman rolling-window is needed. The claim_id reverse lookup
232
+ -- ("which relations does this claim evidence")
233
+ -- scans all partitions but is the cold path. FKs remain logical (D23 btree-only/write-amplification).
234
+ -- ─────────────────────────────────────────────────────────────────────────
235
+ CREATE TABLE relation_evidence (
236
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments
237
+ relation_id uuid NOT NULL, -- LOGICAL FK → relations; HASH partition key
238
+ claim_id uuid NOT NULL, -- LOGICAL FK → claims; the asserting claim (immutable evidence). One claim may evidence MANY relations.
239
+ doc_id uuid NOT NULL, -- LOGICAL FK → documents (the claim's LINEAGE, denormalized write-once) — makes the D54 recount a single-table scan per fact (F7)
240
+ stance evidence_stance NOT NULL, -- supports | contradicts (concepts §3/§4)
241
+ normalizer_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions; which normalizer linked them
242
+ created_at timestamptz NOT NULL DEFAULT now(),
243
+ PRIMARY KEY (relation_id, claim_id) -- evidence-once, DB-enforced (partition key relation_id is included); re-link via ON CONFLICT DO NOTHING is a no-op
244
+ ) PARTITION BY HASH (relation_id);
245
+ COMMENT ON TABLE relation_evidence IS
246
+ 'Many-to-many evidence links (D2). Corpus redundancy collapses here into relations.evidence_count. Partitioned by HASH(relation_id) so relation hydration prunes to one partition and PRIMARY KEY (relation_id, claim_id) enforces evidence-once in-DB (D23; §17 item 1 resolved). claim_id reverse lookup scans all partitions (cold path). Logical FKs (D23).';
247
+ -- 64 static hash children created by the migration — required, or inserts fail (F5); mirrors observation_evidence:
248
+ DO $$ BEGIN
249
+ FOR i IN 0..63 LOOP
250
+ EXECUTE format('CREATE TABLE relation_evidence_p%s PARTITION OF relation_evidence '
251
+ 'FOR VALUES WITH (MODULUS 64, REMAINDER %s);', i, i);
252
+ END LOOP;
253
+ END $$;
254
+ CREATE INDEX ix_relevidence_claim ON relation_evidence (claim_id); -- reverse lookup: relations a claim evidences (all-partition scan)
255
+
256
+ -- ─────────────────────────────────────────────────────────────────────────
257
+ -- relation_adjudications — append-only supersession/contradiction transcript (D3/D4). Records WHY a
258
+ -- relation's window closed, a contradiction was flagged, or a merge proposed — by which cascade
259
+ -- rung, with what confidence/evidence. Makes the non-deterministic adjudication replayable on
260
+ -- rebuild (D7) and answers "why did valid_until close on 2026-01-15?". Real composite FK; the
261
+ -- deletion GC retires (not deletes) relations referenced here so the audit trail survives (§13).
262
+ -- ─────────────────────────────────────────────────────────────────────────
263
+ CREATE TABLE relation_adjudications (
264
+ adjudication_id uuid PRIMARY KEY,
265
+ deployment_id uuid NOT NULL REFERENCES deployments,
266
+ relation_id uuid NOT NULL, -- the relation acted upon (composite FK below)
267
+ related_relation_id uuid, -- the other relation in a supersede/contradict pair, if any (composite FK below)
268
+ outcome adjudication_outcome NOT NULL, -- add | noop | supersede | contradict | same_as_merge_proposal (D4 write-time outcomes)
269
+ method adjudication_method NOT NULL, -- novelty_gate | exact | fuzzy | embedding | small_model | frontier_llm (cheap-first cascade, D4)
270
+ confidence real,
271
+ triggering_claim_id uuid, -- LOGICAL FK → claims; the new claim that triggered adjudication
272
+ features jsonb, -- scores/rationale the decision used (audit); scrubbed on hard-forget (§13)
273
+ adjudicator_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions
274
+ decided_by decision_actor NOT NULL DEFAULT 'auto',
275
+ decided_at timestamptz NOT NULL DEFAULT now(),
276
+ superseded_by uuid REFERENCES relation_adjudications, -- a later adjudication that overrode this one
277
+ FOREIGN KEY (deployment_id, relation_id) REFERENCES relations (deployment_id, relation_id),
278
+ FOREIGN KEY (deployment_id, related_relation_id) REFERENCES relations (deployment_id, relation_id)
279
+ );
280
+ COMMENT ON TABLE relation_adjudications IS
281
+ 'Append-only supersession/contradiction decision log (D3/D4). Explains every window closure / contradiction flag / merge proposal, by cascade rung + evidence; replayed on P2 rebuild and used for "what did we believe at T / why" audits.';
282
+ CREATE INDEX ix_adjud_relation ON relation_adjudications (relation_id);
283
+ CREATE INDEX ix_adjud_live ON relation_adjudications (relation_id) WHERE superseded_by IS NULL;
284
+ -- ─────────────────────────────────────────────────────────────────────────
285
+ -- observations — non-graph facts about ONE entity (D43): entity-anchored, bi-temporal, UNTYPED. The
286
+ -- supersession blocking key is subject_entity_id alone (exact, exhaustive per entity); the same-slot /
287
+ -- supersede-vs-coexist judgment is the adjudicator's (D4 cascade), fail-safe to coexist. No governed
288
+ -- attribute vocabulary, no value_domain/cardinality, NO structured value/period columns, no typed
289
+ -- EXCLUDE — the value AND any reporting period live in `statement` and are matched SEMANTICALLY, exactly
290
+ -- like the property (consistent with the untyped design). status is a GENERATED mirror of invalidated_at
291
+ -- (one validity home, D6). The observation LABEL + its embedding live in Lance (D8).
292
+ -- THE NO-CAP RULE (D43): only a CHANGING EFFECTIVE STATE (headcount/balance/status) is capped on
293
+ -- valid-time when superseded; a MEASUREMENT / FIXED-PERIOD figure ("FY2023 revenue") is NEVER capped —
294
+ -- it doesn't stop being true at period-end, stays open, and conflicting same-period figures coexist.
295
+ -- ─────────────────────────────────────────────────────────────────────────
296
+ CREATE TABLE observations (
297
+ observation_id uuid PRIMARY KEY, -- the observation's identity; provenance handle in Lance
298
+ deployment_id uuid NOT NULL REFERENCES deployments,
299
+ subject_entity_id uuid NOT NULL, -- the ANCHOR + supersession blocking key (a resolved canonical entity); composite FK below
300
+ statement text NOT NULL, -- canonical NL form of the observed fact ("Acme's headcount is 600", "Acme's FY2023 revenue was $5M"); embedded in Lance (D8). The VALUE and any reporting period live HERE — there is no structured value/period column (D43 lean); the adjudicator reads them semantically, like the property.
301
+ -- bi-temporality (concepts §5): two clocks, WORLD-VALIDITY OF THE BELIEF.
302
+ valid_from timestamptz, -- VALID-time start: when the belief began holding in the world (NULL = unknown/always); seeded from the claim's asserted validity (D41)
303
+ valid_until timestamptz, -- VALID-time end. NO-CAP RULE (D43): capped ONLY when a CHANGING EFFECTIVE STATE (headcount/balance/status) is superseded by a later value. A MEASUREMENT / FIXED-PERIOD figure ("FY2023 revenue") is NEVER capped here — it doesn't stop being true at period-end; it stays open and conflicting same-period figures coexist. The adjudicator decides state-vs-measurement from `statement` (semantic), not a typed column. (observations_design.md §3)
304
+ ingested_at timestamptz NOT NULL DEFAULT now(), -- TRANSACTION-time: when the system first believed it
305
+ invalidated_at timestamptz, -- TRANSACTION-time: when learned wrong (NULL = still believed). NOT used to "end" a fact — that's valid_until.
306
+ evidence_count integer NOT NULL DEFAULT 0, -- cached count of DISTINCT current-testimony LINEAGES supporting (D54 — mirrors relations)
307
+ contradict_count integer NOT NULL DEFAULT 0, -- cached count of distinct current-testimony lineages contradicting (D54). NB: conflicting OBSERVATIONS are tracked via contradiction_group, a different concept.
308
+ confidence real, -- aggregate confidence over evidence
309
+ contradiction_group uuid, -- shared id when two live observations conflict and both must stand (concepts §4)
310
+ status relation_status GENERATED ALWAYS AS -- DERIVED mirror of invalidated_at (single validity home, D6)
311
+ (CASE WHEN invalidated_at IS NOT NULL THEN 'invalidated'::relation_status ELSE 'active'::relation_status END) STORED,
312
+ obs_label text, -- the human-readable sentence embedded in Lance (often = statement); semantic blocking + retrieval
313
+ obs_label_version text, -- LOGICAL FK → pipeline_component_versions (labeler)
314
+ obs_label_embedding_ref text, -- opaque Lance key for the label vector (P1; no vectors in PG — D8)
315
+ normalizer_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions; replay-on-rebuild
316
+ adjudicator_version text, -- LOGICAL FK → pipeline_component_versions (supersession/contradiction adjudicator, D4)
317
+ created_at timestamptz NOT NULL DEFAULT now(),
318
+ updated_at timestamptz NOT NULL DEFAULT now(),
319
+ UNIQUE (deployment_id, observation_id), -- composite-FK target (tenancy isolation, §0)
320
+ FOREIGN KEY (deployment_id, subject_entity_id) REFERENCES entities (deployment_id, entity_id),
321
+ CHECK (valid_until IS NULL OR valid_from IS NULL OR valid_until >= valid_from),
322
+ CHECK (invalidated_at IS NULL OR invalidated_at >= ingested_at) -- can't un-learn before learning
323
+ -- NOTE: intentionally NO EXCLUDE / uniqueness constraint — there is no typed slot to key one on;
324
+ -- supersession + evidence-collapse are adjudicated (entity-block + semantic + cascade), and
325
+ -- "both-stand" is the safe default.
326
+ );
327
+ COMMENT ON TABLE observations IS
328
+ 'D43 non-graph fact layer: a believed value/statement about ONE entity (entity-anchored, bi-temporal, UNTYPED). Sibling of relations; never projects to the graph (D18). The value AND any reporting period live in `statement` (no structured value/period columns); the adjudicator matches same-entity + same-property + same-period + value-compatibility SEMANTICALLY. Supersession is adjudicated by entity-blocking + the D4 cascade (no typed slot, no EXCLUDE); "never silently resolve" is a binding adjudicator contract (supersede only on a positively-matched prior above margin, with a persisted reason; else coexist) + an eval gate, NOT a schema invariant. NO-CAP RULE: only a changing effective state is capped on valid-time; a measurement/fixed-period figure is never capped and conflicting same-period figures coexist. status is a generated mirror of invalidated_at (one validity home, D6); label+embedding live in Lance (D8).';
329
+ -- The supersession blocking key (D4): all live observations for an entity (exact + exhaustive per entity):
330
+ CREATE INDEX ix_observations_block ON observations (deployment_id, subject_entity_id) WHERE invalidated_at IS NULL;
331
+ CREATE INDEX ix_observations_entity ON observations (deployment_id, subject_entity_id); -- full history incl. capped/invalidated
332
+ CREATE INDEX ix_observations_contradiction ON observations (contradiction_group) WHERE contradiction_group IS NOT NULL;
333
+
334
+ -- ─────────────────────────────────────────────────────────────────────────
335
+ -- observation_evidence — many-to-many join claims ⇄ observations (D2), mirroring relation_evidence.
336
+ -- Corpus redundancy collapses here into observations.evidence_count. HASH(observation_id). Like
337
+ -- relation_evidence (§9), FKs are LOGICAL (D23 — btree-only at 10^8 scale); the integrity guarantee
338
+ -- here is the PRIMARY KEY (evidence-once), NOT referential FKs. (Evidence-collapse of the same value
339
+ -- into ONE observation is adjudicated upstream — best-effort — not enforced by this table, which only
340
+ -- dedups a given (observation, claim) pair.)
341
+ -- ─────────────────────────────────────────────────────────────────────────
342
+ CREATE TABLE observation_evidence (
343
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments
344
+ observation_id uuid NOT NULL, -- LOGICAL FK → observations; HASH partition key
345
+ claim_id uuid NOT NULL, -- LOGICAL FK → claims; the asserting claim (immutable evidence). One claim may evidence many observations.
346
+ stance evidence_stance NOT NULL, -- supports | contradicts (concepts §3/§4)
347
+ normalizer_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions
348
+ doc_id uuid NOT NULL, -- LOGICAL FK → documents (the claim's lineage, write-once) — D54 recount without cross-partition claim joins (F7)
349
+ created_at timestamptz NOT NULL DEFAULT now(),
350
+ PRIMARY KEY (observation_id, claim_id) -- evidence-once, DB-enforced; re-link via ON CONFLICT DO NOTHING is a no-op
351
+ ) PARTITION BY HASH (observation_id);
352
+ -- 64 static hash children created by the migration, same as relation_evidence (§9):
353
+ DO $$ BEGIN
354
+ FOR i IN 0..63 LOOP
355
+ EXECUTE format('CREATE TABLE observation_evidence_p%s PARTITION OF observation_evidence '
356
+ 'FOR VALUES WITH (MODULUS 64, REMAINDER %s);', i, i);
357
+ END LOOP;
358
+ END $$;
359
+ COMMENT ON TABLE observation_evidence IS
360
+ 'Many-to-many evidence links claims ⇄ observations (D2/D43), mirroring relation_evidence. Corpus redundancy collapses into observations.evidence_count. HASH(observation_id), 64 static partitions; PRIMARY KEY (observation_id, claim_id) enforces evidence-once. Logical FKs (D23).';
361
+ CREATE INDEX ix_obsevidence_claim ON observation_evidence (claim_id); -- reverse lookup (all-partition scan, cold path)
362
+
363
+ -- ─────────────────────────────────────────────────────────────────────────
364
+ -- observation_adjudications — append-only supersession/contradiction transcript (D3/D4), mirroring
365
+ -- relation_adjudications. Records WHY an observation's window closed / a contradiction was flagged, by
366
+ -- cascade rung + confidence; makes the non-deterministic adjudication replayable on rebuild (D7).
367
+ -- ─────────────────────────────────────────────────────────────────────────
368
+ CREATE TABLE observation_adjudications (
369
+ adjudication_id uuid PRIMARY KEY,
370
+ deployment_id uuid NOT NULL REFERENCES deployments,
371
+ observation_id uuid NOT NULL, -- the observation acted upon (composite FK below)
372
+ related_observation_id uuid, -- the other observation in a supersede/contradict pair, if any (composite FK below)
373
+ outcome adjudication_outcome NOT NULL, -- add | noop | supersede | contradict | same_as_merge_proposal (D4)
374
+ method adjudication_method NOT NULL, -- novelty_gate | exact | fuzzy | embedding | small_model | frontier_llm (cheap-first cascade, D4)
375
+ confidence real,
376
+ triggering_claim_id uuid, -- LOGICAL FK → claims; the new claim that triggered adjudication
377
+ features jsonb, -- scores/rationale (audit); scrubbed on hard-forget (§13)
378
+ adjudicator_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions
379
+ decided_by decision_actor NOT NULL DEFAULT 'auto',
380
+ decided_at timestamptz NOT NULL DEFAULT now(),
381
+ superseded_by uuid REFERENCES observation_adjudications, -- a later adjudication that overrode this one
382
+ FOREIGN KEY (deployment_id, observation_id) REFERENCES observations (deployment_id, observation_id),
383
+ FOREIGN KEY (deployment_id, related_observation_id) REFERENCES observations (deployment_id, observation_id)
384
+ );
385
+ COMMENT ON TABLE observation_adjudications IS
386
+ 'Append-only supersession/contradiction decision log for observations (D3/D4/D43), mirroring relation_adjudications. Explains every window closure / contradiction flag by cascade rung + evidence; replayed on rebuild and used for "what did we believe at T / why" audits.';
387
+ CREATE INDEX ix_obsadjud_observation ON observation_adjudications (observation_id);
388
+ CREATE INDEX ix_obsadjud_live ON observation_adjudications (observation_id) WHERE superseded_by IS NULL;
389
+ """
390
+ _TABLES = (
391
+ "claims",
392
+ "claim_extraction_decisions",
393
+ "grounding_audits",
394
+ "testimony_currency_events",
395
+ "relations",
396
+ "relation_evidence",
397
+ "relation_adjudications",
398
+ "observations",
399
+ "observation_evidence",
400
+ "observation_adjudications",
401
+ )
402
+
403
+
404
+ def upgrade() -> None:
405
+ """Apply create claim, relation, observation, and evidence structures."""
406
+ apply_ddl(sql=_DDL)
407
+
408
+
409
+ def downgrade() -> None:
410
+ """Revert create claim, relation, observation, and evidence structures."""
411
+ drop_tables(table_names=reversed(_TABLES))