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,391 @@
1
+ """Create projection, knowledge, and retrieval 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_0005"
9
+ down_revision: str | None = "p0_02_0004"
10
+ branch_labels: str | Sequence[str] | None = None
11
+ depends_on: str | Sequence[str] | None = None
12
+
13
+ _DDL = r"""-- ─────────────────────────────────────────────────────────────────────────
14
+ -- projection_snapshots — registry of P1/P2/P3 rebuilds (D7/D40). Immutable versioned snapshots;
15
+ -- a validation gate must pass before is_latest flips (failure ⇒ previous snapshot keeps serving).
16
+ -- ─────────────────────────────────────────────────────────────────────────
17
+ CREATE TABLE projection_snapshots (
18
+ snapshot_id uuid PRIMARY KEY,
19
+ deployment_id uuid NOT NULL REFERENCES deployments,
20
+ plane projection_plane NOT NULL, -- P1_search | P2_graph | P3_corpusfs
21
+ version text NOT NULL, -- monotonic/timestamped snapshot version (also the GCS path segment)
22
+ gcs_uri text NOT NULL, -- gs://…/snapshots/<version>/
23
+ status snapshot_status NOT NULL DEFAULT 'building', -- building | validating | published | superseded | failed
24
+ is_latest boolean NOT NULL DEFAULT false, -- the pointer readers follow; exactly one per (deployment,plane)
25
+ row_counts jsonb, -- per-table counts validated against Postgres (D7 validation gate)
26
+ validation jsonb, -- validation report (pass/fail per check)
27
+ built_from_watermark timestamptz, -- max ingested_at included — bounds projection staleness (freshness SLA = cadence, D7)
28
+ built_at timestamptz NOT NULL DEFAULT now(),
29
+ published_at timestamptz,
30
+ UNIQUE (deployment_id, plane, version),
31
+ UNIQUE (deployment_id, snapshot_id) -- composite-FK target (tenancy isolation, §0)
32
+ );
33
+ COMMENT ON TABLE projection_snapshots IS
34
+ 'Registry of immutable P1/P2/P3 snapshots (D7/D40). Validation gates is_latest; old snapshots are free point-in-time debugging artifacts. Mirrors the GCS latest pointer for operators/workers.';
35
+ CREATE UNIQUE INDEX ux_snapshot_latest ON projection_snapshots (deployment_id, plane) WHERE is_latest;
36
+
37
+ -- ─────────────────────────────────────────────────────────────────────────
38
+ -- communities — detected entity communities per P2 snapshot (D11). Recomputed each rebuild.
39
+ -- ─────────────────────────────────────────────────────────────────────────
40
+ CREATE TABLE communities (
41
+ community_id uuid PRIMARY KEY,
42
+ deployment_id uuid NOT NULL REFERENCES deployments,
43
+ snapshot_id uuid NOT NULL, -- which (P2_graph) rebuild produced this partition (composite FK below)
44
+ label text, -- optional human/LLM topic label (K1 hint)
45
+ size integer NOT NULL, -- member count; an emerging giant community can signal over-merge (health metric, registries §10)
46
+ algorithm community_algorithm NOT NULL,-- leiden | louvain (external pass) — D11
47
+ detected_at timestamptz NOT NULL DEFAULT now(),
48
+ UNIQUE (deployment_id, community_id), -- composite-FK target
49
+ FOREIGN KEY (deployment_id, snapshot_id) REFERENCES projection_snapshots (deployment_id, snapshot_id) ON DELETE CASCADE
50
+ );
51
+ COMMENT ON TABLE communities IS
52
+ 'Externally-detected communities per P2 graph snapshot (D11). Feed K1 refresh triggers ("claims in community C changed") and salience; recomputed each rebuild and GC''d with their snapshot (graph stays a projection). FK references must be a plane=P2_graph snapshot (invariant; the writer only inserts P2 snapshots here).';
53
+ CREATE INDEX ix_communities_snapshot ON communities (snapshot_id);
54
+
55
+ -- ─────────────────────────────────────────────────────────────────────────
56
+ -- entity_graph_metrics — per-entity centrality + community membership per snapshot (D11). PageRank
57
+ -- = salience prior; degree feeds entities.graph_degree (blast-radius) — refreshed ONLY from the
58
+ -- currently-published is_latest P2 snapshot, after the validation gate passes, so the auto-merge
59
+ -- gate is never computed from a stale/unvalidated projection. component_id is a synthetic
60
+ -- per-snapshot WCC grouping label (NOT an FK).
61
+ -- ─────────────────────────────────────────────────────────────────────────
62
+ CREATE TABLE entity_graph_metrics (
63
+ deployment_id uuid NOT NULL REFERENCES deployments,
64
+ entity_id uuid NOT NULL, -- composite FK below
65
+ snapshot_id uuid NOT NULL, -- composite FK below (a P2_graph snapshot)
66
+ community_id uuid, -- this entity's community in this snapshot (composite FK below)
67
+ pagerank double precision, -- salience prior (retrieval rank + K3 filter)
68
+ degree integer, -- relation degree — copied into entities.graph_degree from the latest published snapshot only
69
+ k_core integer, -- k-core number (hub-ness)
70
+ component_id uuid, -- synthetic per-snapshot weakly-connected-component label (NOT an FK; scoped to snapshot_id)
71
+ computed_at timestamptz NOT NULL DEFAULT now(),
72
+ PRIMARY KEY (deployment_id, entity_id, snapshot_id),
73
+ FOREIGN KEY (deployment_id, entity_id) REFERENCES entities (deployment_id, entity_id) ON DELETE CASCADE,
74
+ FOREIGN KEY (deployment_id, snapshot_id) REFERENCES projection_snapshots (deployment_id, snapshot_id) ON DELETE CASCADE,
75
+ FOREIGN KEY (deployment_id, community_id) REFERENCES communities (deployment_id, community_id) ON DELETE SET NULL (community_id)
76
+ );
77
+ COMMENT ON TABLE entity_graph_metrics IS
78
+ 'Per-entity graph analytics written back from each P2 rebuild (D11): PageRank salience, degree (blast-radius), k-core, community, WCC. Read by retrieval ranking, K3 filtering, ER health checks. GC''d when its snapshot is superseded. entities.graph_degree is refreshed only from the published is_latest snapshot.';
79
+ CREATE INDEX ix_egm_entity ON entity_graph_metrics (entity_id);
80
+ CREATE INDEX ix_egm_snapshot ON entity_graph_metrics (snapshot_id);
81
+
82
+ -- ─────────────────────────────────────────────────────────────────────────
83
+ -- knowledge_artifacts — the PG handle on a K-plane git file (D1, D45–D47). page_kind is the
84
+ -- D46 ownership contract: 'compiled' bodies are machine-owned (regenerated when stale; human
85
+ -- input only via the curation sidecar); 'authored' bodies are human/agent-owned (never
86
+ -- machine-written; evidence changes raise review flags, not recompiles). parent_artifact_id is
87
+ -- the tree the driver compiles in dependency order (children before parents — parents consume
88
+ -- child page_summary values, never re-read child files). inputs_hash is the D45 staleness key.
89
+ -- ─────────────────────────────────────────────────────────────────────────
90
+ CREATE TABLE knowledge_artifacts (
91
+ artifact_id uuid PRIMARY KEY,
92
+ deployment_id uuid NOT NULL REFERENCES deployments,
93
+ layer knowledge_layer NOT NULL, -- K1 | K2 | K3 — content tier (D47), one mechanism
94
+ page_kind knowledge_page_kind NOT NULL, -- compiled | authored (D46)
95
+ scope_id uuid, -- non-null for K2 scope artifacts (composite FK below)
96
+ parent_artifact_id uuid, -- tree/DAG position (composite FK below)
97
+ git_path text NOT NULL, -- path of the markdown file in the K repo
98
+ curation_path text, -- compiled pages: the human curation sidecar file (D46)
99
+ kind text, -- 'summary' | 'profile' | 'belief' | 'decision_log' | 'model_page' | ...
100
+ page_summary text, -- writer-emitted 2–3 sentence abstract; what PARENT compiles consume
101
+ content_hash text, -- hash of the git file at last compile/sync (drift + quarantine detection, D46)
102
+ inputs_hash text, -- D45 staleness key: hash(candidate evidence IDs + validity fingerprints,
103
+ -- curation sidecar, child summaries, shared model page, writer prompt/model version)
104
+ writer_version text, -- LOGICAL FK → pipeline_component_versions (knowledge_writer); NULL on authored pages
105
+ last_compiled_at timestamptz,
106
+ status knowledge_artifact_status NOT NULL DEFAULT 'active', -- active | stale | quarantined | tombstoned
107
+ UNIQUE (deployment_id, git_path),
108
+ UNIQUE (deployment_id, artifact_id), -- composite-FK target
109
+ FOREIGN KEY (deployment_id, scope_id) REFERENCES scopes (deployment_id, scope_id) ON DELETE SET NULL (scope_id),
110
+ FOREIGN KEY (deployment_id, parent_artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id),
111
+ CHECK (page_kind = 'compiled' OR writer_version IS NULL) -- authored bodies are never machine-written (D46)
112
+ );
113
+ COMMENT ON TABLE knowledge_artifacts IS
114
+ 'PG handle + compile state per K-plane git file (D45–D47). page_kind = the D46 ownership contract (compiled: machine-owned, regenerated; authored: human-owned, review-flagged). inputs_hash = the D45 mechanical staleness key (stale iff recomputed hash differs). parent_artifact_id = the compile DAG. Git holds content; PG holds control.';
115
+ CREATE INDEX ix_kartifacts_scope ON knowledge_artifacts (scope_id);
116
+ CREATE INDEX ix_kartifacts_parent ON knowledge_artifacts (parent_artifact_id) WHERE parent_artifact_id IS NOT NULL;
117
+ CREATE INDEX ix_kartifacts_stale ON knowledge_artifacts (deployment_id) WHERE status = 'stale';
118
+
119
+ -- ─────────────────────────────────────────────────────────────────────────
120
+ -- knowledge_plan_decisions — the planner's append-only STRUCTURE transcript (D45; the D33
121
+ -- ledger discipline applied to structure). Low-blast-radius decisions auto-apply; restructures
122
+ -- above the band queue as 'proposed' for the deployment's accountable reviewer — a human or a
123
+ -- designated reviewer agent (the D24 pattern; k_layers §7). Exception: convert_kind in the
124
+ -- authored→compiled direction NEVER auto-applies (author confirmation).
125
+ -- ─────────────────────────────────────────────────────────────────────────
126
+ CREATE TABLE knowledge_plan_decisions (
127
+ decision_id uuid PRIMARY KEY,
128
+ deployment_id uuid NOT NULL REFERENCES deployments,
129
+ scope_id uuid, -- composite FK below
130
+ action plan_action NOT NULL, -- create_page | split_page | merge_pages | move_page | retire_page | adjust_rule | convert_kind
131
+ payload jsonb NOT NULL, -- paths, rule diffs, rationale text
132
+ trigger plan_trigger NOT NULL, -- orphan_evidence | size_overflow | community_change | reflection | writer_suggestion | human
133
+ planner_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions (knowledge_planner)
134
+ status plan_decision_status NOT NULL DEFAULT 'proposed', -- proposed | applied | rejected
135
+ decided_at timestamptz NOT NULL DEFAULT now(),
136
+ FOREIGN KEY (deployment_id, scope_id) REFERENCES scopes (deployment_id, scope_id) ON DELETE CASCADE
137
+ );
138
+ COMMENT ON TABLE knowledge_plan_decisions IS
139
+ 'Append-only planner transcript (D45): every create/split/merge/move/retire/rule change with trigger + rationale. Reviewable, revertible structure — the opposite of emergent session behavior. Blast-radius-gated auto-apply (D24 pattern).';
140
+ CREATE INDEX ix_kplan_proposed ON knowledge_plan_decisions (deployment_id, decided_at) WHERE status = 'proposed';
141
+
142
+ -- ─────────────────────────────────────────────────────────────────────────
143
+ -- knowledge_subscriptions — the DISPATCH consumers (the K trigger surface, k_layers §5; the
144
+ -- E→K signal channel D42 deferred, now designed). Binds match criteria — an owned routing rule
145
+ -- (below) and/or page watches — to a workflow endpoint. Dispatch is DEBOUNCED per subscription
146
+ -- and delivered with the D12 worker discipline (Cloud Tasks, retries, DLQ, idempotent
147
+ -- consumers); the payload carries the delta (matched evidence + citation/validity changes),
148
+ -- never a bare ping. The memory system only notifies + serves context — it never runs the
149
+ -- subscriber's logic (subscribers are operating agents outside the system boundary).
150
+ -- ─────────────────────────────────────────────────────────────────────────
151
+ CREATE TABLE knowledge_subscriptions (
152
+ subscription_id uuid PRIMARY KEY,
153
+ deployment_id uuid NOT NULL REFERENCES deployments,
154
+ scope_id uuid, -- optional owning scope (composite FK below)
155
+ name text NOT NULL, -- e.g. 'planning-module-replan'
156
+ workflow_endpoint text NOT NULL, -- the agentic workflow invoked on dispatch (Cloud Tasks target)
157
+ debounce_seconds integer NOT NULL, -- per-subscription batch window (starting point, measure — k_layers §11 spike 8)
158
+ status subscription_status NOT NULL DEFAULT 'active', -- active | paused | retired
159
+ created_by text, -- registering agent/human
160
+ created_at timestamptz NOT NULL DEFAULT now(),
161
+ UNIQUE (deployment_id, name),
162
+ FOREIGN KEY (deployment_id, scope_id) REFERENCES scopes (deployment_id, scope_id) ON DELETE CASCADE
163
+ );
164
+ COMMENT ON TABLE knowledge_subscriptions IS
165
+ 'Dispatch consumers of the K trigger surface (k_layers §5). Match criteria = owned routing rules and/or page watches; consequence = debounced workflow invocation carrying the evidence delta. The E→K signal channel D42 deferred.';
166
+
167
+ -- ─────────────────────────────────────────────────────────────────────────
168
+ -- knowledge_page_rules — the ROUTING RULES (D45): the recorded answer to "what evidence
169
+ -- belongs to this OWNER". Mechanical: each rule_kind has ONE fixed SQL evaluation
170
+ -- (k_layers_design.md §5); an LLM chooses the rule, SQL evaluates it — no LLM on the routing
171
+ -- path (the D9 rule applied to routing). An owner may hold several rules (union). The owner is
172
+ -- EXACTLY ONE of a page or a subscription, and the rule's CONSEQUENCE derives from it:
173
+ -- compiled page → stale/recompile; authored page → an 'authored_review' flag (a watch rule,
174
+ -- D46); subscription → dispatch (k_layers §5). Page-owned rules require a plan decision;
175
+ -- subscription-owned rules are accounted by the subscription's created_by.
176
+ -- ─────────────────────────────────────────────────────────────────────────
177
+ CREATE TABLE knowledge_page_rules (
178
+ rule_id uuid PRIMARY KEY,
179
+ deployment_id uuid NOT NULL REFERENCES deployments,
180
+ artifact_id uuid, -- the page this rule feeds (XOR subscription_id; composite FK below)
181
+ subscription_id uuid REFERENCES knowledge_subscriptions (subscription_id) ON DELETE CASCADE,
182
+ rule_kind knowledge_rule_kind NOT NULL,
183
+ params jsonb NOT NULL, -- e.g. {"entity_id": …, "predicates": ["works_for"], "layers": ["relations","observations","claims"]}
184
+ status ontology_status NOT NULL DEFAULT 'active',
185
+ plan_decision_id uuid REFERENCES knowledge_plan_decisions (decision_id), -- who created it and why (page-owned rules)
186
+ created_at timestamptz NOT NULL DEFAULT now(),
187
+ CHECK (num_nonnulls(artifact_id, subscription_id) = 1), -- exactly one owner
188
+ CHECK ((artifact_id IS NOT NULL) = (plan_decision_id IS NOT NULL)), -- plan-decided iff page-owned
189
+ FOREIGN KEY (deployment_id, artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE
190
+ );
191
+ COMMENT ON TABLE knowledge_page_rules IS
192
+ 'D45 routing rules, owned by a page XOR a subscription (the trigger surface, k_layers §5). Closed kind set; params per kind; union across an owner''s rules. manual = the editorial escape hatch. Evidence matching NO rule in a scope = orphan → a planner trigger.';
193
+
194
+ -- ─────────────────────────────────────────────────────────────────────────
195
+ -- knowledge_rule_keys — the routing INVERTED INDEX (D45): rule match keys materialized so that
196
+ -- routing a batch of new evidence is one indexed lookup (the D4 block-first philosophy — exact
197
+ -- keys narrow; nothing expensive runs corpus-wide). Derived-membership rules (entity_subtree
198
+ -- via the part_of closure; community via the D11 writeback) get their keys RE-MATERIALIZED by
199
+ -- the driver when their inputs change — both arrive as ordinary evidence events.
200
+ -- ─────────────────────────────────────────────────────────────────────────
201
+ CREATE TABLE knowledge_rule_keys (
202
+ deployment_id uuid NOT NULL, -- LOGICAL FK → deployments (tenancy-leading lookup index below)
203
+ rule_id uuid NOT NULL REFERENCES knowledge_page_rules (rule_id) ON DELETE CASCADE,
204
+ key_kind rule_key_kind NOT NULL, -- entity | predicate | community | doc_source
205
+ key_value text NOT NULL, -- uuid-as-text for entity/community; predicate name; doc source
206
+ PRIMARY KEY (rule_id, key_kind, key_value)
207
+ );
208
+ CREATE INDEX ix_krule_keys_lookup ON knowledge_rule_keys (deployment_id, key_kind, key_value);
209
+ COMMENT ON TABLE knowledge_rule_keys IS
210
+ 'Inverted index over rule match keys (D45): new evidence → its E-plane labels (entities, predicate, community, doc source) → the rules (page- or subscription-owned) it affects, in one lookup. The key set of a derived-membership rule is re-materialized when its inputs change.';
211
+
212
+ -- ─────────────────────────────────────────────────────────────────────────
213
+ -- knowledge_page_watches — PAGE-LEVEL watch targets (k_layers §5): subscribe to another page's
214
+ -- recompiles instead of re-declaring its rules (the paired-workbench ergonomics — a gap
215
+ -- analysis watches the compiled to-be page it judges, and stays subscribed as the planner
216
+ -- adjusts that page's rules). Watcher is EXACTLY ONE of an authored page (consequence: an
217
+ -- 'authored_review' flag) or a subscription (consequence: dispatch). Same edge type as the
218
+ -- compile DAG's parent→child dependency, different consequence.
219
+ -- ─────────────────────────────────────────────────────────────────────────
220
+ CREATE TABLE knowledge_page_watches (
221
+ watch_id uuid PRIMARY KEY,
222
+ deployment_id uuid NOT NULL,
223
+ watcher_artifact_id uuid, -- an authored page … (XOR subscription_id)
224
+ subscription_id uuid REFERENCES knowledge_subscriptions (subscription_id) ON DELETE CASCADE,
225
+ watched_artifact_id uuid NOT NULL, -- the page whose recompiles are watched
226
+ CHECK (num_nonnulls(watcher_artifact_id, subscription_id) = 1),
227
+ FOREIGN KEY (deployment_id, watcher_artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE,
228
+ FOREIGN KEY (deployment_id, watched_artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE
229
+ );
230
+ CREATE UNIQUE INDEX ux_kwatch ON knowledge_page_watches (watcher_artifact_id, subscription_id, watched_artifact_id) NULLS NOT DISTINCT;
231
+ CREATE INDEX ix_kwatch_watched ON knowledge_page_watches (watched_artifact_id);
232
+ COMMENT ON TABLE knowledge_page_watches IS
233
+ 'Page-level watches (k_layers §5): a watcher (authored page XOR subscription) subscribes to a watched page''s recompiles. Consequence derives from the watcher: flag or dispatch. Synced from authored frontmatter (watch: page:<path>) or registered with a subscription.';
234
+
235
+ -- ─────────────────────────────────────────────────────────────────────────
236
+ -- knowledge_dispatches — the append-only DISPATCH transcript (k_layers §5). The driver
237
+ -- coalesces a subscription's matches over its debounce window into ONE row whose payload
238
+ -- carries the delta (matched evidence IDs, citation/validity changes, affected page refs);
239
+ -- delivery is at-least-once (Cloud Tasks, D12 retries/DLQ) — subscriber workflows must be
240
+ -- idempotent per dispatch_id.
241
+ -- ─────────────────────────────────────────────────────────────────────────
242
+ CREATE TABLE knowledge_dispatches (
243
+ dispatch_id uuid PRIMARY KEY,
244
+ deployment_id uuid NOT NULL REFERENCES deployments,
245
+ subscription_id uuid NOT NULL REFERENCES knowledge_subscriptions (subscription_id) ON DELETE CASCADE,
246
+ payload jsonb NOT NULL, -- {matched_evidence_ids, deltas, page_refs} — the delta, never a bare ping
247
+ status refresh_status NOT NULL DEFAULT 'pending', -- pending | running | done | failed
248
+ enqueued_at timestamptz NOT NULL DEFAULT now(),
249
+ delivered_at timestamptz
250
+ );
251
+ CREATE INDEX ix_kdispatch_pending ON knowledge_dispatches (deployment_id, status) WHERE status = 'pending';
252
+ COMMENT ON TABLE knowledge_dispatches IS
253
+ 'Append-only dispatch transcript (k_layers §5): one debounce-coalesced row per subscription window, delta-carrying payload, at-least-once delivery with idempotent consumers (keyed by dispatch_id). Makes the E→K trigger surface auditable like every other non-deterministic boundary (D33 discipline).';
254
+
255
+ -- ─────────────────────────────────────────────────────────────────────────
256
+ -- knowledge_compilations — the append-only COMPILE transcript (D45; D33 for content).
257
+ -- uncited_count is the K-plane analogue of the Selection-drop ledger: rule-matched evidence the
258
+ -- writer chose not to cite is counted, so "why isn''t fact X on this page?" has an answer.
259
+ -- git_commit is two-phase: the row is written before the push, the sha stamped after; startup
260
+ -- reconciles repo HEAD against the newest committed rows.
261
+ -- ─────────────────────────────────────────────────────────────────────────
262
+ CREATE TABLE knowledge_compilations (
263
+ compilation_id uuid PRIMARY KEY,
264
+ deployment_id uuid NOT NULL REFERENCES deployments,
265
+ artifact_id uuid NOT NULL, -- composite FK below
266
+ inputs_hash text NOT NULL, -- the candidate snapshot this compile consumed (D45 idempotency)
267
+ candidate_count int NOT NULL, -- rule-matched evidence offered to the writer
268
+ cited_count int NOT NULL, -- evidence the writer used (→ knowledge_artifact_evidence)
269
+ uncited_count int NOT NULL, -- offered but not used (auditable coverage gap)
270
+ evidence_added int NOT NULL DEFAULT 0, -- citation-set delta vs the previous compile
271
+ evidence_removed int NOT NULL DEFAULT 0,
272
+ evidence_invalidated int NOT NULL DEFAULT 0,
273
+ writer_version text NOT NULL, -- LOGICAL FK → pipeline_component_versions (knowledge_writer)
274
+ tokens integer, cost_usd numeric, -- cost metering (requirements: per-layer budgets)
275
+ session_transcript_uri text, -- archived writer-session transcript (GCS) — the residual read-audit log for stock-harness writers (k_layers §7); NULL when a session left no transcript
276
+ git_commit text,
277
+ compiled_at timestamptz NOT NULL DEFAULT now(),
278
+ FOREIGN KEY (deployment_id, artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE
279
+ );
280
+ CREATE INDEX ix_kcompilations_artifact ON knowledge_compilations (artifact_id, compiled_at DESC);
281
+ COMMENT ON TABLE knowledge_compilations IS
282
+ 'Append-only compile transcript per page (D45): inputs snapshot, candidate/cited/uncited counts, citation deltas, versions, cost, commit. Makes compiles idempotent (inputs_hash), auditable, and replayable-from-storage like every non-deterministic stage (D7/D33).';
283
+
284
+ -- ─────────────────────────────────────────────────────────────────────────
285
+ -- knowledge_artifact_evidence — the CITATIONS: page ⇄ evidence links (D45/D46; K3 requirement +
286
+ -- deletion cascade). A BINDING output contract, not self-reported provenance: on a compiled page
287
+ -- the driver REPLACES these rows from the writer's returned citations each compile; on an
288
+ -- authored page they are synced from the page's frontmatter (`cites:`). Evidence-change
289
+ -- staleness, authored review flags, and deletion reach are reverse lookups through this table.
290
+ -- A single link targets EXACTLY ONE of claim/relation/doc (the others NULL) — so a surrogate PK
291
+ -- + a num_nonnulls CHECK + a NULL-tolerant unique index, NOT an all-columns PK (PK columns
292
+ -- cannot be NULL).
293
+ -- ─────────────────────────────────────────────────────────────────────────
294
+ CREATE TABLE knowledge_artifact_evidence (
295
+ evidence_link_id uuid PRIMARY KEY,
296
+ deployment_id uuid NOT NULL REFERENCES deployments,
297
+ artifact_id uuid NOT NULL, -- composite FK below, ON DELETE CASCADE
298
+ claim_id uuid, -- LOGICAL FK → claims (partitioned)
299
+ relation_id uuid, -- composite FK below, ON DELETE CASCADE (real, relations is not partitioned)
300
+ doc_id uuid, -- LOGICAL FK → documents
301
+ role knowledge_evidence_role NOT NULL, -- supports | contradicts | cites (K3 links supporting AND contradicting evidence)
302
+ CHECK (num_nonnulls(claim_id, relation_id, doc_id) = 1), -- exactly one target per link
303
+ FOREIGN KEY (deployment_id, artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE,
304
+ FOREIGN KEY (deployment_id, relation_id) REFERENCES relations (deployment_id, relation_id) ON DELETE CASCADE
305
+ );
306
+ COMMENT ON TABLE knowledge_artifact_evidence IS
307
+ 'Citations (D45/D46): the ONE claim/relation/document each link rests on, role supports|contradicts|cites. Binding writer output on compiled pages (replaced per compile); frontmatter-synced on authored pages. Drives exact incremental refresh (D12), authored review flags (D46), and the deletion cascade. Exactly-one-target enforced by CHECK; surrogate PK because the targets are nullable alternatives.';
308
+ -- NULL-tolerant dedup (one link per (artifact, target, role)); NULLS NOT DISTINCT treats the two
309
+ -- NULL targets as equal so the populated one is the discriminator:
310
+ CREATE UNIQUE INDEX ux_kae_link ON knowledge_artifact_evidence (artifact_id, role, claim_id, relation_id, doc_id) NULLS NOT DISTINCT;
311
+ CREATE INDEX ix_kae_claim ON knowledge_artifact_evidence (claim_id) WHERE claim_id IS NOT NULL;
312
+ CREATE INDEX ix_kae_relation ON knowledge_artifact_evidence (relation_id) WHERE relation_id IS NOT NULL;
313
+ CREATE INDEX ix_kae_doc ON knowledge_artifact_evidence (doc_id) WHERE doc_id IS NOT NULL;
314
+
315
+ -- ─────────────────────────────────────────────────────────────────────────
316
+ -- knowledge_refresh_queue — the debounced trigger queue (D12) the D45 driver consumes at cycle
317
+ -- start. Evidence-change events carry the changed IDs; the driver ROUTES them to pages via
318
+ -- knowledge_rule_keys + the citation reverse lookup — artifact_id is therefore NULL on evidence
319
+ -- batches (routing is mechanical, no longer "decide which" by an LLM at processing time) and
320
+ -- set only on targeted triggers (authored_review, tombstone, manual). not_before is the plain
321
+ -- debounce delay — the hot-file rationale is gone (D45: the root index is just the last DAG
322
+ -- target, compiled once per cycle). This is domain-trigger aggregation, not D61 task delivery:
323
+ -- once the driver materializes a K job, its unlaned processing_state row and that row's
324
+ -- not_before are authoritative under D67.
325
+ -- ─────────────────────────────────────────────────────────────────────────
326
+ CREATE TABLE knowledge_refresh_queue (
327
+ refresh_id uuid PRIMARY KEY,
328
+ deployment_id uuid NOT NULL REFERENCES deployments,
329
+ artifact_id uuid, -- composite FK below (nullable); NULL on evidence batches — routing via rule keys (D45)
330
+ scope_id uuid, -- composite FK below
331
+ trigger knowledge_trigger NOT NULL, -- evidence_changed | community_changed | debounce_timer | manual | tombstone | authored_review
332
+ payload jsonb, -- e.g. {changed_relation_ids:[…]} | {changed_claim_ids:[…]} | {community_id:…} | {deleted_doc_id:…}
333
+ not_before timestamptz, -- debounce delay — don't process before this
334
+ status refresh_status NOT NULL DEFAULT 'pending', -- pending | running | done | failed
335
+ enqueued_at timestamptz NOT NULL DEFAULT now(),
336
+ processed_at timestamptz,
337
+ FOREIGN KEY (deployment_id, artifact_id) REFERENCES knowledge_artifacts (deployment_id, artifact_id) ON DELETE CASCADE,
338
+ FOREIGN KEY (deployment_id, scope_id) REFERENCES scopes (deployment_id, scope_id) ON DELETE CASCADE
339
+ );
340
+ COMMENT ON TABLE knowledge_refresh_queue IS
341
+ 'Debounced domain-trigger queue for the K compile driver (D12/D45), not D61 delivery state. Evidence batches route mechanically; authored_review surfaces D46 flags; this not_before coalesces triggers, while a materialized K job is delivered only from its unlaned processing_state row (D67).';
342
+ CREATE INDEX ix_krefresh_runnable ON knowledge_refresh_queue (deployment_id, status, not_before) WHERE status = 'pending';
343
+ -- ─────────────────────────────────────────────────────────────────────────
344
+ -- retrieval_recipes — frozen query plans as registry data (D50). One row per recipe version;
345
+ -- surfaces (API/CLI/MCP) render from status='active' rows. The CHECK is the mechanical half
346
+ -- of the grain linter (D41/D49); the registration linter validates the chain itself.
347
+ -- ─────────────────────────────────────────────────────────────────────────
348
+ CREATE TABLE retrieval_recipes (
349
+ recipe_id uuid PRIMARY KEY,
350
+ deployment_id uuid NOT NULL REFERENCES deployments,
351
+ name text NOT NULL, -- e.g. 'relation_hybrid_rrf', 'claims_as_of', 'identity_as_of'
352
+ description text NOT NULL, -- rendered into the MCP tool description (D50)
353
+ parameters jsonb NOT NULL, -- typed parameter schema (JSON-Schema form)
354
+ chain jsonb NOT NULL, -- the typed primitive composition: ordered ops + fixed settings (channel sets, RRF constants, rerank weights)
355
+ output_grain recipe_output_grain NOT NULL, -- fact | evidence | compiled | composite (the D49 envelope grain)
356
+ answer_intent recipe_answer_intent NOT NULL, -- current_facts | assertion_history | orientation | audit | change_feed
357
+ version integer NOT NULL DEFAULT 1, -- recall@k measured per (name, version) — regressions attributable (D22)
358
+ status ontology_status NOT NULL DEFAULT 'active',
359
+ created_at timestamptz NOT NULL DEFAULT now(),
360
+ UNIQUE (deployment_id, name, version),
361
+ CHECK (answer_intent <> 'current_facts' OR output_grain = 'fact') -- the D41 bar, mechanical
362
+ );
363
+ COMMENT ON TABLE retrieval_recipes IS
364
+ 'D50: recipes as registry rows. MCP tools render from here; the eval harness measures per (name, version); the CHECK enforces the D41 grain bar mechanically (current_facts ⇒ fact grain), with chain-level validation in the registration linter. Adding a query pattern = inserting a row.';
365
+ """
366
+ _TABLES = (
367
+ "projection_snapshots",
368
+ "communities",
369
+ "entity_graph_metrics",
370
+ "knowledge_artifacts",
371
+ "knowledge_plan_decisions",
372
+ "knowledge_subscriptions",
373
+ "knowledge_page_rules",
374
+ "knowledge_rule_keys",
375
+ "knowledge_page_watches",
376
+ "knowledge_dispatches",
377
+ "knowledge_compilations",
378
+ "knowledge_artifact_evidence",
379
+ "knowledge_refresh_queue",
380
+ "retrieval_recipes",
381
+ )
382
+
383
+
384
+ def upgrade() -> None:
385
+ """Apply create projection, knowledge, and retrieval structures."""
386
+ apply_ddl(sql=_DDL)
387
+
388
+
389
+ def downgrade() -> None:
390
+ """Revert create projection, knowledge, and retrieval structures."""
391
+ drop_tables(table_names=reversed(_TABLES))
@@ -0,0 +1,158 @@
1
+ """Register RANGE families with pg_partman and create projection views."""
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from alembic import op
6
+
7
+ from rememberstack.spine.migrations._helpers import apply_ddl
8
+
9
+ revision: str = "p0_02_0006"
10
+ down_revision: str | None = "p0_02_0005"
11
+ branch_labels: str | Sequence[str] | None = None
12
+ depends_on: str | Sequence[str] | None = None
13
+
14
+ _RANGE_PARENTS = (
15
+ ("mentions", "created_at"),
16
+ ("resolution_decisions", "decided_at"),
17
+ ("chunks", "created_at"),
18
+ ("chunk_claims", "created_at"),
19
+ ("claims", "ingested_at"),
20
+ ("claim_extraction_decisions", "decided_at"),
21
+ ("testimony_currency_events", "occurred_at"),
22
+ )
23
+ _VIEWS = (
24
+ "v_graph_survivor",
25
+ "v_graph_entities",
26
+ "v_graph_documents",
27
+ "v_graph_relates",
28
+ "v_graph_mentioned_in",
29
+ "v_graph_crossref",
30
+ "v_graph_is_document",
31
+ )
32
+ _VIEW_DDL = r"""-- Resolve every entity id to its final merge SURVIVOR. A merge is a REDIRECT, not a rewrite
33
+ -- (entities.merged_into; entity_id never reused) and relations are NOT re-pointed in PG — so endpoints
34
+ -- MUST be redirected here, or the rebuild silently drops every edge touching a merged entity. Cycle-safe
35
+ -- (merged_into acyclicity is not schema-enforced); the rebuild's validation gate (below) aborts the
36
+ -- snapshot if any retained endpoint fails to resolve to exactly one emitted survivor.
37
+ CREATE VIEW v_graph_survivor AS
38
+ WITH RECURSIVE chain(entity_id, cur, depth) AS (
39
+ SELECT entity_id, entity_id, 0 FROM entities
40
+ UNION ALL
41
+ SELECT c.entity_id, e.merged_into, c.depth + 1
42
+ FROM chain c JOIN entities e ON e.entity_id = c.cur
43
+ WHERE e.merged_into IS NOT NULL AND c.depth < 64 -- cycle / runaway guard
44
+ )
45
+ SELECT entity_id,
46
+ (SELECT cur FROM chain x WHERE x.entity_id = chain.entity_id ORDER BY depth DESC LIMIT 1) AS survivor
47
+ FROM chain GROUP BY entity_id; -- survivor = the terminal (merged_into IS NULL) node of each chain
48
+
49
+ -- Nodes: survivors only; cast timestamps. Graph-derived metrics (pagerank/graph_degree) are NOT loaded —
50
+ -- they are computed POST-load (D11); reprojecting a stored value is circular. entity_id stays native UUID
51
+ -- (PK verified in LadybugDB source/tests; STRING fallback = entity_id::text, applied uniformly to the PK
52
+ -- AND every endpoint).
53
+ CREATE VIEW v_graph_entities AS
54
+ SELECT entity_id AS id, type, canonical_name AS name, normalized_name,
55
+ profile_summary AS summary, (created_at AT TIME ZONE 'UTC') AS created_at
56
+ FROM entities WHERE status = 'active'; -- merged/retired entities are not nodes
57
+
58
+ CREATE VIEW v_graph_documents AS
59
+ SELECT d.doc_id AS id, d.title, d.source_uri,
60
+ (dv.published_at AT TIME ZONE 'UTC')::date AS published_at -- the CURRENT version's date (D55); NULL when unset
61
+ FROM documents d
62
+ LEFT JOIN document_versions dv
63
+ ON dv.deployment_id = d.deployment_id AND dv.version_id = d.current_version_id
64
+ WHERE d.deleted_at IS NULL; -- lineages project; a lineage mid-ingest (no current version yet) projects with NULL date (F2)
65
+
66
+ -- Edges: endpoints are the FIRST TWO columns (FROM, TO), survivor-redirected and guarded so both
67
+ -- endpoints exist as emitted nodes (else COPY-REL throws). Keep EVERY invalidated edge by default for
68
+ -- transaction-time as-of (D69): there is no invalidation-age filter and a closed valid-time fact is
69
+ -- unaffected. Endpoint joins are the retention boundary. Parallel edges with distinct relation_id are
70
+ -- PRESERVED (no blind DISTINCT — same-(s,p,o) collapse is E3's job, D43).
71
+ CREATE VIEW v_graph_relates AS
72
+ SELECT s1.survivor AS "from", s2.survivor AS "to",
73
+ r.relation_id, r.predicate, r.fact_label AS fact,
74
+ r.evidence_count::bigint AS evidence_count, r.contradict_count::bigint AS contradict_count,
75
+ r.confidence::float8 AS confidence, r.contradiction_group,
76
+ (r.valid_from AT TIME ZONE 'UTC') AS valid_from, (r.valid_until AT TIME ZONE 'UTC') AS valid_until,
77
+ (r.ingested_at AT TIME ZONE 'UTC') AS ingested_at, (r.invalidated_at AT TIME ZONE 'UTC') AS invalidated_at
78
+ FROM relations r
79
+ JOIN v_graph_survivor s1 ON s1.entity_id = r.subject_entity_id
80
+ JOIN v_graph_survivor s2 ON s2.entity_id = r.object_entity_id
81
+ JOIN entities e1 ON e1.entity_id = s1.survivor AND e1.status = 'active' -- endpoint emitted as a node
82
+ JOIN entities e2 ON e2.entity_id = s2.survivor AND e2.status = 'active';
83
+ -- relations.status (GENERATED) is DROPPED — liveness is derived in Cypher (invalidated_at IS NULL), D6.
84
+
85
+ CREATE VIEW v_graph_mentioned_in AS -- aggregate: no (entity,doc) base table
86
+ SELECT s.survivor AS "from", m.doc_id AS "to",
87
+ COUNT(*)::bigint AS mention_count, (MIN(m.created_at) AT TIME ZONE 'UTC') AS first_seen
88
+ FROM mentions m
89
+ JOIN resolution_decisions rd ON rd.mention_id = m.mention_id AND rd.superseded_by IS NULL -- live verdict
90
+ JOIN v_graph_survivor s ON s.entity_id = rd.entity_id
91
+ JOIN entities e ON e.entity_id = s.survivor AND e.status = 'active'
92
+ WHERE EXISTS (SELECT 1 FROM documents d WHERE d.doc_id = m.doc_id AND d.deleted_at IS NULL)
93
+ GROUP BY s.survivor, m.doc_id;
94
+
95
+ CREATE VIEW v_graph_crossref AS
96
+ SELECT from_doc_id AS "from", to_doc_id AS "to", kind::text AS kind, context
97
+ FROM document_crossrefs WHERE to_doc_id IS NOT NULL; -- nullable = cited-but-not-ingested → no edge
98
+
99
+ CREATE VIEW v_graph_is_document AS -- bridge: Document-typed Entity ↔ its E0 doc
100
+ SELECT s.survivor AS "from", d.doc_id AS "to"
101
+ FROM documents d
102
+ JOIN v_graph_survivor s ON s.entity_id = d.document_entity_id
103
+ JOIN entities e ON e.entity_id = s.survivor AND e.status = 'active'
104
+ WHERE d.document_entity_id IS NOT NULL AND d.deleted_at IS NULL;
105
+ """
106
+
107
+
108
+ def upgrade() -> None:
109
+ """Register all monthly parents and create final no-filter projection views."""
110
+ for parent, control in _RANGE_PARENTS:
111
+ op.execute(
112
+ "SELECT public.create_parent("
113
+ f"p_parent_table := 'public.{parent}', "
114
+ f"p_control := '{control}', "
115
+ "p_interval := '1 month', "
116
+ "p_type := 'range', "
117
+ "p_premake := 4, "
118
+ "p_default_table := true, "
119
+ "p_automatic_maintenance := 'on', "
120
+ "p_jobmon := false)"
121
+ )
122
+ apply_ddl(sql=_VIEW_DDL)
123
+
124
+
125
+ def downgrade() -> None:
126
+ """Drop views and unregister only the seven UGM pg_partman parents."""
127
+ for view_name in reversed(_VIEWS):
128
+ op.execute(f"DROP VIEW IF EXISTS {view_name}")
129
+ op.execute(
130
+ """
131
+ DO $$
132
+ DECLARE
133
+ configured_parent text;
134
+ configured_template text;
135
+ BEGIN
136
+ FOR configured_parent, configured_template IN
137
+ SELECT parent_table, template_table
138
+ FROM public.part_config
139
+ WHERE parent_table = ANY (ARRAY[
140
+ 'public.mentions',
141
+ 'public.resolution_decisions',
142
+ 'public.chunks',
143
+ 'public.chunk_claims',
144
+ 'public.claims',
145
+ 'public.claim_extraction_decisions',
146
+ 'public.testimony_currency_events'
147
+ ])
148
+ LOOP
149
+ DELETE FROM public.part_config
150
+ WHERE parent_table = configured_parent;
151
+ IF configured_template IS NOT NULL THEN
152
+ EXECUTE 'DROP TABLE IF EXISTS ' || configured_template || ' CASCADE';
153
+ END IF;
154
+ END LOOP;
155
+ END
156
+ $$;
157
+ """
158
+ )
@@ -0,0 +1,26 @@
1
+ """Add the `invalidated` adjudication outcome (WP-2.6 review verdicts).
2
+
3
+ A review's invalidate_fact verdict sets a fact's invalidated_at; recording
4
+ that action as `noop` would make the append-only adjudication ledger lie on
5
+ replay (a rebuild would retain the fact). The enum gains the truthful value.
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ from alembic import op
11
+
12
+ revision: str = "p2_06_0007"
13
+ down_revision: str | None = "p0_02_0006"
14
+ branch_labels: str | Sequence[str] | None = None
15
+ depends_on: str | Sequence[str] | None = None
16
+
17
+
18
+ def upgrade() -> None:
19
+ """Append the enum value (additive; existing rows untouched)."""
20
+ op.execute("ALTER TYPE adjudication_outcome ADD VALUE IF NOT EXISTS 'invalidated'")
21
+
22
+
23
+ def downgrade() -> None:
24
+ """PostgreSQL cannot remove an enum value in place; the value is
25
+ additive and unused rows are impossible to strand, so downgrade is a
26
+ deliberate no-op (documented, not silent)."""
@@ -0,0 +1,58 @@
1
+ """Add the `document_version` processing target (Phase 3, D55/D12).
2
+
3
+ With multi-version lineages, the E-chain's idempotency key must name the
4
+ VERSION being processed: keying convert/structure/chunk work on the lineage
5
+ would make a second version's work collide with the first version's
6
+ completed row and never run. The enum gains the precise target.
7
+
8
+ Two lifecycle audit columns land alongside it:
9
+
10
+ - ``documents.deleted_sync_cycle_id`` — a source-observed deletion is
11
+ stamped with the cycle that observed it, so reconciliation's
12
+ finalization barrier can place the deletion inside its cycle.
13
+ - ``connector_sync_cycles.failed_items`` — a poll pass that lost items to
14
+ per-item errors says so on its own row; reconciliation must never treat
15
+ a lossy cycle's observation set as complete.
16
+
17
+ And one constraint falls: ``UNIQUE (deployment_id, doc_id, content_hash)``
18
+ on ``document_versions`` encoded a one-version-per-content rule that D55
19
+ does not have — a version is an OBSERVATION event, and content reverted
20
+ A→B→A legitimately recurs as a third version of the same content object
21
+ (content_objects still dedups the bytes themselves).
22
+ """
23
+
24
+ from collections.abc import Sequence
25
+
26
+ from alembic import op
27
+
28
+ revision: str = "p3_01_0008"
29
+ down_revision: str | None = "p2_06_0007"
30
+ branch_labels: str | Sequence[str] | None = None
31
+ depends_on: str | Sequence[str] | None = None
32
+
33
+
34
+ def upgrade() -> None:
35
+ """Append the enum value and the two audit columns (all additive)."""
36
+ op.execute(
37
+ "ALTER TYPE processing_target ADD VALUE IF NOT EXISTS 'document_version'"
38
+ )
39
+ op.execute("ALTER TABLE documents ADD COLUMN deleted_sync_cycle_id uuid")
40
+ op.execute(
41
+ "ALTER TABLE connector_sync_cycles"
42
+ " ADD COLUMN failed_items integer NOT NULL DEFAULT 0"
43
+ )
44
+ op.execute(
45
+ "ALTER TABLE document_versions"
46
+ " DROP CONSTRAINT document_versions_deployment_id_doc_id_content_hash_key"
47
+ )
48
+
49
+
50
+ def downgrade() -> None:
51
+ """Restore the constraint, drop the columns; the enum value stays."""
52
+ op.execute(
53
+ "ALTER TABLE document_versions"
54
+ " ADD CONSTRAINT document_versions_deployment_id_doc_id_content_hash_key"
55
+ " UNIQUE (deployment_id, doc_id, content_hash)"
56
+ )
57
+ op.execute("ALTER TABLE connector_sync_cycles DROP COLUMN failed_items")
58
+ op.execute("ALTER TABLE documents DROP COLUMN deleted_sync_cycle_id")