lorebase-store 0.2.3__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 (163) hide show
  1. lorebase_store/__init__.py +1 -0
  2. lorebase_store/_migrations/env.py +106 -0
  3. lorebase_store/_migrations/script.py.mako +27 -0
  4. lorebase_store/_migrations/versions/0001_initial_schema.py +1071 -0
  5. lorebase_store/_migrations/versions/0002_merge_attempt_details.py +74 -0
  6. lorebase_store/_migrations/versions/0003_webhooks.py +127 -0
  7. lorebase_store/_migrations/versions/0004_defer_revision_references.py +38 -0
  8. lorebase_store/_migrations/versions/0005_security_definer_reads.py +396 -0
  9. lorebase_store/_migrations/versions/0006_api_key_usage.py +48 -0
  10. lorebase_store/_migrations/versions/0007_document_extraction.py +154 -0
  11. lorebase_store/_migrations/versions/0008_definer_argument_checks.py +178 -0
  12. lorebase_store/_migrations/versions/0009_api_key_networks.py +33 -0
  13. lorebase_store/_migrations/versions/0010_overlay_branches.py +582 -0
  14. lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py +229 -0
  15. lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py +185 -0
  16. lorebase_store/_migrations/versions/0013_web_sessions.py +72 -0
  17. lorebase_store/_migrations/versions/0014_embedding_jobs.py +70 -0
  18. lorebase_store/_migrations/versions/0015_append_only_audit_log.py +83 -0
  19. lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py +55 -0
  20. lorebase_store/_migrations/versions/0017_background_branch_copies.py +77 -0
  21. lorebase_store/_migrations/versions/0018_search_lexicon.py +477 -0
  22. lorebase_store/_migrations/versions/0019_vault_bindings.py +56 -0
  23. lorebase_store/_migrations/versions/0020_outbox_consumers.py +150 -0
  24. lorebase_store/_migrations/versions/0021_schema_suggestions.py +114 -0
  25. lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py +133 -0
  26. lorebase_store/_migrations/versions/0023_agent_trace.py +96 -0
  27. lorebase_store/_migrations/versions/0024_embedding_template_keys.py +205 -0
  28. lorebase_store/_migrations/versions/0025_snapshot_refs.py +35 -0
  29. lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py +112 -0
  30. lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py +145 -0
  31. lorebase_store/_migrations/versions/0028_operation_deferred_events.py +71 -0
  32. lorebase_store/_migrations/versions/0029_agent_trace_sessions.py +108 -0
  33. lorebase_store/_migrations/versions/0030_path_fanout.py +31 -0
  34. lorebase_store/_migrations/versions/0031_branch_builds.py +91 -0
  35. lorebase_store/_migrations/versions/0032_lineage_revisions.py +35 -0
  36. lorebase_store/_migrations/versions/0033_vault_moved_tokens.py +30 -0
  37. lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py +73 -0
  38. lorebase_store/_migrations/versions/0035_search_truncation.py +104 -0
  39. lorebase_store/_migrations/versions/0036_lineage_maps.py +325 -0
  40. lorebase_store/_migrations/versions/0037_bm25_search.py +346 -0
  41. lorebase_store/auth/__init__.py +52 -0
  42. lorebase_store/auth/api_keys.py +161 -0
  43. lorebase_store/auth/audit.py +382 -0
  44. lorebase_store/auth/checkpoints.py +185 -0
  45. lorebase_store/auth/grants.py +161 -0
  46. lorebase_store/auth/principals.py +158 -0
  47. lorebase_store/auth/scope.py +70 -0
  48. lorebase_store/auth/sessions.py +153 -0
  49. lorebase_store/auth/unit.py +53 -0
  50. lorebase_store/auth/usage.py +70 -0
  51. lorebase_store/blobs.py +73 -0
  52. lorebase_store/bm25.py +346 -0
  53. lorebase_store/bulk.py +147 -0
  54. lorebase_store/bundle.py +1830 -0
  55. lorebase_store/changefeed.py +261 -0
  56. lorebase_store/commit/__init__.py +42 -0
  57. lorebase_store/commit/changes.py +67 -0
  58. lorebase_store/commit/errors.py +60 -0
  59. lorebase_store/commit/migrations.py +335 -0
  60. lorebase_store/commit/pipeline.py +754 -0
  61. lorebase_store/commit/schema.py +123 -0
  62. lorebase_store/db.py +58 -0
  63. lorebase_store/definer.py +50 -0
  64. lorebase_store/digests.py +99 -0
  65. lorebase_store/embeddings.py +444 -0
  66. lorebase_store/endpoint_modes.py +59 -0
  67. lorebase_store/extraction/__init__.py +61 -0
  68. lorebase_store/extraction/items.py +187 -0
  69. lorebase_store/extraction/materialize.py +364 -0
  70. lorebase_store/extraction/proposal.py +65 -0
  71. lorebase_store/extraction/review.py +168 -0
  72. lorebase_store/gc.py +970 -0
  73. lorebase_store/indexes.py +1033 -0
  74. lorebase_store/jobs.py +651 -0
  75. lorebase_store/keyring.py +72 -0
  76. lorebase_store/layout.py +335 -0
  77. lorebase_store/materialize/__init__.py +103 -0
  78. lorebase_store/materialize/apply.py +436 -0
  79. lorebase_store/materialize/background.py +1184 -0
  80. lorebase_store/materialize/branch.py +268 -0
  81. lorebase_store/materialize/build.py +536 -0
  82. lorebase_store/materialize/derived.py +540 -0
  83. lorebase_store/materialize/lineage_backfill.py +502 -0
  84. lorebase_store/materialize/links.py +266 -0
  85. lorebase_store/materialize/overlay.py +292 -0
  86. lorebase_store/materialize/paths.py +315 -0
  87. lorebase_store/materialize/rebuild.py +326 -0
  88. lorebase_store/materialize/rows.py +153 -0
  89. lorebase_store/materialize/sensitivity.py +66 -0
  90. lorebase_store/materialize/snapshot.py +205 -0
  91. lorebase_store/materialize/stale.py +444 -0
  92. lorebase_store/materialize/verify.py +321 -0
  93. lorebase_store/merging/__init__.py +39 -0
  94. lorebase_store/merging/attempts.py +334 -0
  95. lorebase_store/merging/lineage.py +123 -0
  96. lorebase_store/merging/service.py +652 -0
  97. lorebase_store/migrations.py +133 -0
  98. lorebase_store/models/__init__.py +136 -0
  99. lorebase_store/models/base.py +64 -0
  100. lorebase_store/models/enums.py +132 -0
  101. lorebase_store/models/governance.py +268 -0
  102. lorebase_store/models/ingestion.py +288 -0
  103. lorebase_store/models/materialized.py +277 -0
  104. lorebase_store/models/proposals.py +195 -0
  105. lorebase_store/models/queues.py +87 -0
  106. lorebase_store/models/tracing.py +116 -0
  107. lorebase_store/models/versioning.py +222 -0
  108. lorebase_store/models/webhooks.py +96 -0
  109. lorebase_store/nodestore.py +384 -0
  110. lorebase_store/objectstore.py +162 -0
  111. lorebase_store/outbox_events.py +166 -0
  112. lorebase_store/profiling/__init__.py +58 -0
  113. lorebase_store/profiling/delimited.py +109 -0
  114. lorebase_store/profiling/postgres.py +211 -0
  115. lorebase_store/profiling/record_files.py +127 -0
  116. lorebase_store/profiling/records.py +92 -0
  117. lorebase_store/profiling/sql_databases.py +298 -0
  118. lorebase_store/ratelimit.py +129 -0
  119. lorebase_store/reads.py +1491 -0
  120. lorebase_store/replicas.py +434 -0
  121. lorebase_store/repositories/__init__.py +152 -0
  122. lorebase_store/repositories/changesets.py +59 -0
  123. lorebase_store/repositories/deferred_events.py +142 -0
  124. lorebase_store/repositories/documents.py +305 -0
  125. lorebase_store/repositories/domains.py +97 -0
  126. lorebase_store/repositories/events.py +205 -0
  127. lorebase_store/repositories/index_view.py +489 -0
  128. lorebase_store/repositories/invocations.py +68 -0
  129. lorebase_store/repositories/materializations.py +276 -0
  130. lorebase_store/repositories/materialized.py +1129 -0
  131. lorebase_store/repositories/merges.py +417 -0
  132. lorebase_store/repositories/proposal_items.py +199 -0
  133. lorebase_store/repositories/proposals.py +184 -0
  134. lorebase_store/repositories/provenance.py +77 -0
  135. lorebase_store/repositories/refs.py +452 -0
  136. lorebase_store/repositories/revisions.py +273 -0
  137. lorebase_store/repositories/scope.py +33 -0
  138. lorebase_store/repositories/source_runs.py +123 -0
  139. lorebase_store/repositories/suggestions.py +638 -0
  140. lorebase_store/runtime.py +87 -0
  141. lorebase_store/schema_cache.py +50 -0
  142. lorebase_store/settings.py +879 -0
  143. lorebase_store/storage.py +1478 -0
  144. lorebase_store/telemetry.py +142 -0
  145. lorebase_store/testing.py +108 -0
  146. lorebase_store/tracing.py +505 -0
  147. lorebase_store/traverse.py +578 -0
  148. lorebase_store/vault/__init__.py +50 -0
  149. lorebase_store/vault/keys.py +216 -0
  150. lorebase_store/vault/quotes.py +133 -0
  151. lorebase_store/vault/rotation.py +209 -0
  152. lorebase_store/vault/settings.py +152 -0
  153. lorebase_store/vault/store.py +987 -0
  154. lorebase_store/vault/transfer.py +558 -0
  155. lorebase_store/webhooks/__init__.py +24 -0
  156. lorebase_store/webhooks/delivery.py +637 -0
  157. lorebase_store/webhooks/keys.py +126 -0
  158. lorebase_store/webhooks/repository.py +142 -0
  159. lorebase_store/webhooks/settings.py +138 -0
  160. lorebase_store/webhooks/signing.py +50 -0
  161. lorebase_store-0.2.3.dist-info/METADATA +13 -0
  162. lorebase_store-0.2.3.dist-info/RECORD +163 -0
  163. lorebase_store-0.2.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,1071 @@
1
+ """Initial schema (design 7).
2
+
3
+ Besides the tables, this revision installs what model metadata cannot express:
4
+
5
+ - extensions `pg_trgm` and `vector` (created if missing; never dropped on downgrade because
6
+ an administrator may have installed them for other users);
7
+ - native enum types, created once and shared by several tables;
8
+ - `store.layout.hash_partitions` hash partitions on `domain_id`;
9
+ - lz4 TOAST compression of `tree_nodes.body`;
10
+ - the HNSW index of `embeddings` for `store.embeddings.dims`;
11
+ - row-level security on `domain_id` keyed to the transaction setting `lorebase.domain_ids`
12
+ (comma-separated Domain ids), bypassed when `lorebase.bypass_rls` is `on`.
13
+
14
+ Settings are read when the revision runs; this file never changes after release.
15
+
16
+ Revision ID: 0001
17
+ Revises:
18
+ Create Date: 2026-10-20 18:41:41.275934
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from collections.abc import Sequence
24
+
25
+ import sqlalchemy as sa
26
+ from alembic import op
27
+ from pgvector.sqlalchemy import VECTOR
28
+ from sqlalchemy.dialects import postgresql
29
+
30
+ revision: str = "0001"
31
+ down_revision: str | Sequence[str] | None = None
32
+ branch_labels: str | Sequence[str] | None = None
33
+ depends_on: str | Sequence[str] | None = None
34
+
35
+ EXTENSIONS = ("pg_trgm", "vector")
36
+
37
+ ENUMS = {
38
+ "ref_kind": ("branch", "tag"),
39
+ "materialization": ("full", "overlay", "none"),
40
+ "proposal_origin": ("manual", "source_sync", "extraction", "operation"),
41
+ "proposal_status": ("open", "merged", "closed"),
42
+ "review_verdict": ("approve", "request_changes", "comment"),
43
+ "item_decision": ("pending", "accepted", "rejected"),
44
+ "merge_attempt_status": ("conflicted", "stale", "resolved", "merged", "abandoned"),
45
+ "principal_type": ("user", "service", "api_key"),
46
+ "grant_scope": ("domain", "kind", "operation", "branch"),
47
+ "invocation_status": ("pending", "committed", "proposed", "rejected", "dry_run", "failed"),
48
+ "job_status": ("queued", "running", "succeeded", "failed", "cancelled"),
49
+ "run_status": ("queued", "running", "succeeded", "failed", "cancelled"),
50
+ }
51
+
52
+ HASH_PARTITIONED = ("objects", "tree_nodes", "mat_records", "mat_links")
53
+
54
+ LZ4_COLUMNS = (("tree_nodes", "body"),)
55
+
56
+ DOMAIN_SCOPED = (
57
+ "objects",
58
+ "tree_nodes",
59
+ "revisions",
60
+ "revision_parents",
61
+ "changesets",
62
+ "provenance",
63
+ "refs",
64
+ "ref_log",
65
+ "mat_schema",
66
+ "mat_records",
67
+ "mat_links",
68
+ "mat_history",
69
+ "embeddings",
70
+ "proposals",
71
+ "proposal_reviews",
72
+ "proposal_items",
73
+ "merge_attempts",
74
+ "conflicts",
75
+ "conflict_resolutions",
76
+ "grants",
77
+ "operation_invocations",
78
+ "audit_log",
79
+ "outbox",
80
+ "jobs",
81
+ "source_runs",
82
+ "documents",
83
+ "document_chunks",
84
+ "extraction_jobs",
85
+ "vault",
86
+ )
87
+
88
+ DOMAIN_VISIBLE = (
89
+ "current_setting('lorebase.bypass_rls', true) = 'on' "
90
+ "OR domain_id = ANY (string_to_array(current_setting('lorebase.domain_ids', true), ','))"
91
+ )
92
+
93
+
94
+ def _settings() -> tuple[int, int]:
95
+ """(hash partitions, embedding dimension) from the `store` settings."""
96
+ config = op.get_context().config
97
+ if config is None or "store_settings" not in config.attributes:
98
+ raise RuntimeError("run this migration through the Lorebase Alembic environment")
99
+ store = config.attributes["store_settings"]
100
+ return store.layout.hash_partitions, store.embeddings.dims
101
+
102
+
103
+ def _create_partitions(partitions: int) -> None:
104
+ for table in HASH_PARTITIONED:
105
+ for remainder in range(partitions):
106
+ op.execute(
107
+ f"CREATE TABLE {table}_p{remainder} PARTITION OF {table} "
108
+ f"FOR VALUES WITH (MODULUS {partitions}, REMAINDER {remainder})"
109
+ )
110
+
111
+
112
+ def _with_partitions(table: str, partitions: int) -> list[str]:
113
+ """`table` and, when it is hash-partitioned, each of its partitions."""
114
+ if table not in HASH_PARTITIONED:
115
+ return [table]
116
+ return [table, *(f"{table}_p{remainder}" for remainder in range(partitions))]
117
+
118
+
119
+ def _compress(partitions: int) -> None:
120
+ # Set on every partition: ALTER on a partitioned parent does not reach existing partitions.
121
+ for table, column in LZ4_COLUMNS:
122
+ for target in _with_partitions(table, partitions):
123
+ op.execute(f"ALTER TABLE {target} ALTER COLUMN {column} SET COMPRESSION lz4")
124
+
125
+
126
+ def _enable_row_security(partitions: int) -> None:
127
+ # Partitions get their own policy: querying a partition directly skips the parent's.
128
+ for table in DOMAIN_SCOPED:
129
+ for target in _with_partitions(table, partitions):
130
+ op.execute(f"ALTER TABLE {target} ENABLE ROW LEVEL SECURITY")
131
+ op.execute(f"ALTER TABLE {target} FORCE ROW LEVEL SECURITY")
132
+ op.execute(
133
+ f"CREATE POLICY domain_isolation ON {target} "
134
+ f"USING ({DOMAIN_VISIBLE}) WITH CHECK ({DOMAIN_VISIBLE})"
135
+ )
136
+
137
+
138
+ def upgrade() -> None:
139
+ partitions, dims = _settings()
140
+ for extension in EXTENSIONS:
141
+ op.execute(f"CREATE EXTENSION IF NOT EXISTS {extension}")
142
+ bind = op.get_bind()
143
+ for name, values in ENUMS.items():
144
+ postgresql.ENUM(*values, name=name).create(bind, checkfirst=False)
145
+
146
+ op.create_table(
147
+ "audit_log",
148
+ sa.Column("seq", sa.BigInteger(), sa.Identity(always=True), nullable=False),
149
+ sa.Column("domain_id", sa.Text(), nullable=True),
150
+ sa.Column(
151
+ "at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
152
+ ),
153
+ sa.Column("actor", sa.Text(), nullable=False),
154
+ sa.Column("action", sa.Text(), nullable=False),
155
+ sa.Column("target", sa.Text(), nullable=True),
156
+ sa.Column(
157
+ "details",
158
+ postgresql.JSONB(astext_type=sa.Text()),
159
+ server_default=sa.text("'{}'::jsonb"),
160
+ nullable=False,
161
+ ),
162
+ sa.Column("prev_hash", sa.LargeBinary(), nullable=True),
163
+ sa.Column("entry_hash", sa.LargeBinary(), nullable=False),
164
+ sa.CheckConstraint(
165
+ "octet_length(entry_hash) = 32", name=op.f("ck_audit_log_entry_hash_h32")
166
+ ),
167
+ sa.PrimaryKeyConstraint("seq", name=op.f("pk_audit_log")),
168
+ )
169
+ op.create_index(
170
+ op.f("ix_audit_log_domain_id_seq"), "audit_log", ["domain_id", "seq"], unique=False
171
+ )
172
+ op.create_index(op.f("ix_audit_log_entry_hash"), "audit_log", ["entry_hash"], unique=True)
173
+ op.create_table(
174
+ "domains",
175
+ sa.Column("id", sa.Text(), nullable=False),
176
+ sa.Column("display_name", sa.Text(), nullable=False),
177
+ sa.Column("description", sa.Text(), nullable=True),
178
+ sa.Column("default_branch", sa.Text(), server_default=sa.text("'main'"), nullable=False),
179
+ sa.Column("leaf_size", sa.Integer(), nullable=False),
180
+ sa.Column(
181
+ "settings",
182
+ postgresql.JSONB(astext_type=sa.Text()),
183
+ server_default=sa.text("'{}'::jsonb"),
184
+ nullable=False,
185
+ ),
186
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
187
+ sa.Column("created_by", sa.Text(), nullable=False),
188
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_domains")),
189
+ )
190
+ op.create_table(
191
+ "extraction_cache",
192
+ sa.Column("cache_key", sa.LargeBinary(), nullable=False),
193
+ sa.Column("model", sa.Text(), nullable=False),
194
+ sa.Column("output", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
195
+ sa.Column("usage", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
196
+ sa.Column(
197
+ "created_at",
198
+ sa.DateTime(timezone=True),
199
+ server_default=sa.text("now()"),
200
+ nullable=False,
201
+ ),
202
+ sa.CheckConstraint(
203
+ "octet_length(cache_key) = 32", name=op.f("ck_extraction_cache_cache_key_h32")
204
+ ),
205
+ sa.PrimaryKeyConstraint("cache_key", name=op.f("pk_extraction_cache")),
206
+ )
207
+ op.create_table(
208
+ "groups",
209
+ sa.Column("id", sa.Text(), nullable=False),
210
+ sa.Column("display_name", sa.Text(), nullable=False),
211
+ sa.Column("external_group", sa.Text(), nullable=True),
212
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_groups")),
213
+ )
214
+ op.create_index(
215
+ op.f("ix_groups_external_group"),
216
+ "groups",
217
+ ["external_group"],
218
+ unique=True,
219
+ postgresql_where=sa.text("external_group IS NOT NULL"),
220
+ )
221
+ op.create_table(
222
+ "jobs",
223
+ sa.Column("id", sa.BigInteger(), sa.Identity(always=True), nullable=False),
224
+ sa.Column("type", sa.Text(), nullable=False),
225
+ sa.Column("domain_id", sa.Text(), nullable=True),
226
+ sa.Column(
227
+ "payload",
228
+ postgresql.JSONB(astext_type=sa.Text()),
229
+ server_default=sa.text("'{}'::jsonb"),
230
+ nullable=False,
231
+ ),
232
+ sa.Column(
233
+ "status",
234
+ postgresql.ENUM(name="job_status", create_type=False),
235
+ server_default=sa.text("'queued'"),
236
+ nullable=False,
237
+ ),
238
+ sa.Column("priority", sa.Integer(), server_default=sa.text("0"), nullable=False),
239
+ sa.Column(
240
+ "run_after", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False
241
+ ),
242
+ sa.Column("attempts", sa.Integer(), server_default=sa.text("0"), nullable=False),
243
+ sa.Column("locked_by", sa.Text(), nullable=True),
244
+ sa.Column("locked_at", sa.DateTime(timezone=True), nullable=True),
245
+ sa.Column("result", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
246
+ sa.Column(
247
+ "created_at",
248
+ sa.DateTime(timezone=True),
249
+ server_default=sa.text("now()"),
250
+ nullable=False,
251
+ ),
252
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_jobs")),
253
+ )
254
+ op.create_index(
255
+ "ix_jobs_claimable",
256
+ "jobs",
257
+ [sa.literal_column("priority DESC"), "run_after"],
258
+ unique=False,
259
+ postgresql_where=sa.text("status = 'queued'"),
260
+ )
261
+ op.create_index(op.f("ix_jobs_domain_id_type"), "jobs", ["domain_id", "type"], unique=False)
262
+ op.create_table(
263
+ "outbox",
264
+ sa.Column("id", sa.BigInteger(), sa.Identity(always=True), nullable=False),
265
+ sa.Column("domain_id", sa.Text(), nullable=False),
266
+ sa.Column("event_type", sa.Text(), nullable=False),
267
+ sa.Column("payload", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
268
+ sa.Column(
269
+ "created_at",
270
+ sa.DateTime(timezone=True),
271
+ server_default=sa.text("now()"),
272
+ nullable=False,
273
+ ),
274
+ sa.Column("delivered_at", sa.DateTime(timezone=True), nullable=True),
275
+ sa.Column("attempts", sa.Integer(), server_default=sa.text("0"), nullable=False),
276
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_outbox")),
277
+ )
278
+ op.create_index(
279
+ "ix_outbox_undelivered",
280
+ "outbox",
281
+ ["id"],
282
+ unique=False,
283
+ postgresql_where=sa.text("delivered_at IS NULL"),
284
+ )
285
+ op.create_table(
286
+ "principals",
287
+ sa.Column("id", sa.Text(), nullable=False),
288
+ sa.Column(
289
+ "type", postgresql.ENUM(name="principal_type", create_type=False), nullable=False
290
+ ),
291
+ sa.Column("display_name", sa.Text(), nullable=False),
292
+ sa.Column("external_subject", sa.Text(), nullable=True),
293
+ sa.Column(
294
+ "created_at",
295
+ sa.DateTime(timezone=True),
296
+ server_default=sa.text("now()"),
297
+ nullable=False,
298
+ ),
299
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_principals")),
300
+ )
301
+ op.create_index(
302
+ op.f("ix_principals_external_subject"),
303
+ "principals",
304
+ ["external_subject"],
305
+ unique=True,
306
+ postgresql_where=sa.text("external_subject IS NOT NULL"),
307
+ )
308
+ op.create_table(
309
+ "api_keys",
310
+ sa.Column("id", sa.Text(), nullable=False),
311
+ sa.Column("principal_id", sa.Text(), nullable=False),
312
+ sa.Column("prefix", sa.Text(), nullable=False),
313
+ sa.Column("hash", sa.LargeBinary(), nullable=False),
314
+ sa.Column(
315
+ "scopes",
316
+ postgresql.JSONB(astext_type=sa.Text()),
317
+ server_default=sa.text("'[]'::jsonb"),
318
+ nullable=False,
319
+ ),
320
+ sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True),
321
+ sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True),
322
+ sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
323
+ sa.Column(
324
+ "created_at",
325
+ sa.DateTime(timezone=True),
326
+ server_default=sa.text("now()"),
327
+ nullable=False,
328
+ ),
329
+ sa.CheckConstraint("octet_length(hash) = 32", name=op.f("ck_api_keys_hash_h32")),
330
+ sa.ForeignKeyConstraint(
331
+ ["principal_id"],
332
+ ["principals.id"],
333
+ name=op.f("fk_api_keys_principal_id_principals"),
334
+ ondelete="CASCADE",
335
+ ),
336
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_api_keys")),
337
+ sa.UniqueConstraint("prefix", name=op.f("uq_api_keys_prefix")),
338
+ )
339
+ op.create_index(op.f("ix_api_keys_principal_id"), "api_keys", ["principal_id"], unique=False)
340
+ op.create_table(
341
+ "documents",
342
+ sa.Column("domain_id", sa.Text(), nullable=False),
343
+ sa.Column("id", sa.Text(), nullable=False),
344
+ sa.Column("filename", sa.Text(), nullable=False),
345
+ sa.Column("media_type", sa.Text(), nullable=False),
346
+ sa.Column("sha256", sa.LargeBinary(), nullable=False),
347
+ sa.Column("size", sa.BigInteger(), nullable=False),
348
+ sa.Column("blob_ref", sa.Text(), nullable=False),
349
+ sa.Column("text_hash", sa.LargeBinary(), nullable=True),
350
+ sa.Column(
351
+ "metadata",
352
+ postgresql.JSONB(astext_type=sa.Text()),
353
+ server_default=sa.text("'{}'::jsonb"),
354
+ nullable=False,
355
+ ),
356
+ sa.Column(
357
+ "created_at",
358
+ sa.DateTime(timezone=True),
359
+ server_default=sa.text("now()"),
360
+ nullable=False,
361
+ ),
362
+ sa.Column("created_by", sa.Text(), nullable=False),
363
+ sa.CheckConstraint("octet_length(sha256) = 32", name=op.f("ck_documents_sha256_h32")),
364
+ sa.ForeignKeyConstraint(
365
+ ["domain_id"],
366
+ ["domains.id"],
367
+ name=op.f("fk_documents_domain_id_domains"),
368
+ ondelete="CASCADE",
369
+ ),
370
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_documents")),
371
+ )
372
+ op.create_index(
373
+ op.f("ix_documents_domain_id_sha256"), "documents", ["domain_id", "sha256"], unique=False
374
+ )
375
+ op.create_table(
376
+ "embeddings",
377
+ sa.Column("domain_id", sa.Text(), nullable=False),
378
+ sa.Column("object_hash", sa.LargeBinary(), nullable=False),
379
+ sa.Column("model", sa.Text(), nullable=False),
380
+ sa.Column("dims", sa.Integer(), nullable=False),
381
+ sa.Column("embedding", VECTOR(), nullable=False),
382
+ sa.CheckConstraint("vector_dims(embedding) = dims", name=op.f("ck_embeddings_dims_match")),
383
+ sa.ForeignKeyConstraint(
384
+ ["domain_id"],
385
+ ["domains.id"],
386
+ name=op.f("fk_embeddings_domain_id_domains"),
387
+ ondelete="CASCADE",
388
+ ),
389
+ sa.PrimaryKeyConstraint("domain_id", "object_hash", "model", name=op.f("pk_embeddings")),
390
+ )
391
+ op.create_table(
392
+ "extraction_jobs",
393
+ sa.Column("domain_id", sa.Text(), nullable=False),
394
+ sa.Column("id", sa.Text(), nullable=False),
395
+ sa.Column("documents", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
396
+ sa.Column("schema_hash", sa.LargeBinary(), nullable=False),
397
+ sa.Column("model", sa.Text(), nullable=False),
398
+ sa.Column("prompt_version", sa.Text(), nullable=False),
399
+ sa.Column("status", postgresql.ENUM(name="run_status", create_type=False), nullable=False),
400
+ sa.Column(
401
+ "budget",
402
+ postgresql.JSONB(astext_type=sa.Text()),
403
+ server_default=sa.text("'{}'::jsonb"),
404
+ nullable=False,
405
+ ),
406
+ sa.Column(
407
+ "usage",
408
+ postgresql.JSONB(astext_type=sa.Text()),
409
+ server_default=sa.text("'{}'::jsonb"),
410
+ nullable=False,
411
+ ),
412
+ sa.Column("proposal_id", sa.Text(), nullable=True),
413
+ sa.Column(
414
+ "created_at",
415
+ sa.DateTime(timezone=True),
416
+ server_default=sa.text("now()"),
417
+ nullable=False,
418
+ ),
419
+ sa.ForeignKeyConstraint(
420
+ ["domain_id"],
421
+ ["domains.id"],
422
+ name=op.f("fk_extraction_jobs_domain_id_domains"),
423
+ ondelete="CASCADE",
424
+ ),
425
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_extraction_jobs")),
426
+ )
427
+ op.create_table(
428
+ "grants",
429
+ sa.Column("id", sa.Text(), nullable=False),
430
+ sa.Column("domain_id", sa.Text(), nullable=False),
431
+ sa.Column("subject", sa.Text(), nullable=False),
432
+ sa.Column("role", sa.Text(), nullable=False),
433
+ sa.Column(
434
+ "scope_type", postgresql.ENUM(name="grant_scope", create_type=False), nullable=False
435
+ ),
436
+ sa.Column("scope_ref", sa.Text(), nullable=True),
437
+ sa.Column("created_by", sa.Text(), nullable=False),
438
+ sa.Column(
439
+ "created_at",
440
+ sa.DateTime(timezone=True),
441
+ server_default=sa.text("now()"),
442
+ nullable=False,
443
+ ),
444
+ sa.ForeignKeyConstraint(
445
+ ["domain_id"],
446
+ ["domains.id"],
447
+ name=op.f("fk_grants_domain_id_domains"),
448
+ ondelete="CASCADE",
449
+ ),
450
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_grants")),
451
+ )
452
+ op.create_index(
453
+ op.f("ix_grants_domain_id_subject"), "grants", ["domain_id", "subject"], unique=False
454
+ )
455
+ op.create_index(op.f("ix_grants_subject"), "grants", ["subject"], unique=False)
456
+ op.create_table(
457
+ "group_members",
458
+ sa.Column("group_id", sa.Text(), nullable=False),
459
+ sa.Column("principal_id", sa.Text(), nullable=False),
460
+ sa.ForeignKeyConstraint(
461
+ ["group_id"],
462
+ ["groups.id"],
463
+ name=op.f("fk_group_members_group_id_groups"),
464
+ ondelete="CASCADE",
465
+ ),
466
+ sa.ForeignKeyConstraint(
467
+ ["principal_id"],
468
+ ["principals.id"],
469
+ name=op.f("fk_group_members_principal_id_principals"),
470
+ ondelete="CASCADE",
471
+ ),
472
+ sa.PrimaryKeyConstraint("group_id", "principal_id", name=op.f("pk_group_members")),
473
+ )
474
+ op.create_index(
475
+ op.f("ix_group_members_principal_id"), "group_members", ["principal_id"], unique=False
476
+ )
477
+ op.create_table(
478
+ "objects",
479
+ sa.Column("domain_id", sa.Text(), nullable=False),
480
+ sa.Column("hash", sa.LargeBinary(), nullable=False),
481
+ sa.Column("object_type", sa.SmallInteger(), nullable=False),
482
+ sa.Column("body", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
483
+ sa.Column("size", sa.Integer(), nullable=False),
484
+ sa.CheckConstraint("octet_length(hash) = 32", name=op.f("ck_objects_hash_h32")),
485
+ sa.ForeignKeyConstraint(
486
+ ["domain_id"],
487
+ ["domains.id"],
488
+ name=op.f("fk_objects_domain_id_domains"),
489
+ ondelete="CASCADE",
490
+ ),
491
+ sa.PrimaryKeyConstraint("domain_id", "hash", name=op.f("pk_objects")),
492
+ postgresql_partition_by="HASH (domain_id)",
493
+ )
494
+ op.create_table(
495
+ "operation_invocations",
496
+ sa.Column("domain_id", sa.Text(), nullable=False),
497
+ sa.Column("id", sa.Text(), nullable=False),
498
+ sa.Column("operation_id", sa.Text(), nullable=False),
499
+ sa.Column("principal", sa.Text(), nullable=False),
500
+ sa.Column("params", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
501
+ sa.Column(
502
+ "status", postgresql.ENUM(name="invocation_status", create_type=False), nullable=False
503
+ ),
504
+ sa.Column("revision_id", sa.LargeBinary(), nullable=True),
505
+ sa.Column("proposal_id", sa.Text(), nullable=True),
506
+ sa.Column("idempotency_key", sa.Text(), nullable=True),
507
+ sa.Column(
508
+ "created_at",
509
+ sa.DateTime(timezone=True),
510
+ server_default=sa.text("now()"),
511
+ nullable=False,
512
+ ),
513
+ sa.Column("duration_ms", sa.Integer(), nullable=True),
514
+ sa.ForeignKeyConstraint(
515
+ ["domain_id"],
516
+ ["domains.id"],
517
+ name=op.f("fk_operation_invocations_domain_id_domains"),
518
+ ondelete="CASCADE",
519
+ ),
520
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_operation_invocations")),
521
+ )
522
+ op.create_index(
523
+ "uq_operation_invocations_idempotency",
524
+ "operation_invocations",
525
+ ["domain_id", "principal", "idempotency_key"],
526
+ unique=True,
527
+ postgresql_where=sa.text("idempotency_key IS NOT NULL"),
528
+ )
529
+ op.create_table(
530
+ "proposals",
531
+ sa.Column("domain_id", sa.Text(), nullable=False),
532
+ sa.Column("id", sa.Text(), nullable=False),
533
+ sa.Column("title", sa.Text(), nullable=False),
534
+ sa.Column("description", sa.Text(), nullable=True),
535
+ sa.Column("source_ref", sa.Text(), nullable=False),
536
+ sa.Column("target_ref", sa.Text(), nullable=False),
537
+ sa.Column(
538
+ "origin", postgresql.ENUM(name="proposal_origin", create_type=False), nullable=False
539
+ ),
540
+ sa.Column(
541
+ "status", postgresql.ENUM(name="proposal_status", create_type=False), nullable=False
542
+ ),
543
+ sa.Column("author", sa.Text(), nullable=False),
544
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
545
+ sa.Column("merged_revision", sa.LargeBinary(), nullable=True),
546
+ sa.Column("checks", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
547
+ sa.Column("policies", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
548
+ sa.ForeignKeyConstraint(
549
+ ["domain_id"],
550
+ ["domains.id"],
551
+ name=op.f("fk_proposals_domain_id_domains"),
552
+ ondelete="CASCADE",
553
+ ),
554
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_proposals")),
555
+ )
556
+ op.create_index(
557
+ op.f("ix_proposals_domain_id_target_ref_status"),
558
+ "proposals",
559
+ ["domain_id", "target_ref", "status"],
560
+ unique=False,
561
+ )
562
+ op.create_table(
563
+ "provenance",
564
+ sa.Column("domain_id", sa.Text(), nullable=False),
565
+ sa.Column("id", sa.Text(), nullable=False),
566
+ sa.Column("origin_type", sa.Text(), nullable=False),
567
+ sa.Column("origin_ref", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
568
+ sa.Column("actor", sa.Text(), nullable=False),
569
+ sa.Column("at", sa.DateTime(timezone=True), nullable=False),
570
+ sa.ForeignKeyConstraint(
571
+ ["domain_id"],
572
+ ["domains.id"],
573
+ name=op.f("fk_provenance_domain_id_domains"),
574
+ ondelete="CASCADE",
575
+ ),
576
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_provenance")),
577
+ )
578
+ op.create_table(
579
+ "ref_log",
580
+ sa.Column("domain_id", sa.Text(), nullable=False),
581
+ sa.Column("ref_name", sa.Text(), nullable=False),
582
+ sa.Column("seq", sa.BigInteger(), nullable=False),
583
+ sa.Column("old_head", sa.LargeBinary(), nullable=True),
584
+ sa.Column("new_head", sa.LargeBinary(), nullable=False),
585
+ sa.Column("reason", sa.Text(), nullable=False),
586
+ sa.Column("actor", sa.Text(), nullable=False),
587
+ sa.Column("at", sa.DateTime(timezone=True), nullable=False),
588
+ sa.ForeignKeyConstraint(
589
+ ["domain_id"],
590
+ ["domains.id"],
591
+ name=op.f("fk_ref_log_domain_id_domains"),
592
+ ondelete="CASCADE",
593
+ ),
594
+ sa.PrimaryKeyConstraint("domain_id", "ref_name", "seq", name=op.f("pk_ref_log")),
595
+ )
596
+ op.create_table(
597
+ "revisions",
598
+ sa.Column("domain_id", sa.Text(), nullable=False),
599
+ sa.Column("id", sa.LargeBinary(), nullable=False),
600
+ sa.Column("root", sa.LargeBinary(), nullable=False),
601
+ sa.Column("generation", sa.BigInteger(), nullable=False),
602
+ sa.Column("author", sa.Text(), nullable=False),
603
+ sa.Column("on_behalf_of", sa.Text(), nullable=True),
604
+ sa.Column("message", sa.Text(), nullable=False),
605
+ sa.Column("origin", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
606
+ sa.Column("stats", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
607
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
608
+ sa.CheckConstraint("octet_length(id) = 32", name=op.f("ck_revisions_id_h32")),
609
+ sa.CheckConstraint("octet_length(root) = 32", name=op.f("ck_revisions_root_h32")),
610
+ sa.ForeignKeyConstraint(
611
+ ["domain_id"],
612
+ ["domains.id"],
613
+ name=op.f("fk_revisions_domain_id_domains"),
614
+ ondelete="CASCADE",
615
+ ),
616
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_revisions")),
617
+ )
618
+ op.create_table(
619
+ "source_runs",
620
+ sa.Column("domain_id", sa.Text(), nullable=False),
621
+ sa.Column("id", sa.Text(), nullable=False),
622
+ sa.Column("source_id", sa.Text(), nullable=False),
623
+ sa.Column("status", postgresql.ENUM(name="run_status", create_type=False), nullable=False),
624
+ sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
625
+ sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
626
+ sa.Column(
627
+ "stats",
628
+ postgresql.JSONB(astext_type=sa.Text()),
629
+ server_default=sa.text("'{}'::jsonb"),
630
+ nullable=False,
631
+ ),
632
+ sa.Column("revision_id", sa.LargeBinary(), nullable=True),
633
+ sa.Column("proposal_id", sa.Text(), nullable=True),
634
+ sa.Column("error", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
635
+ sa.ForeignKeyConstraint(
636
+ ["domain_id"],
637
+ ["domains.id"],
638
+ name=op.f("fk_source_runs_domain_id_domains"),
639
+ ondelete="CASCADE",
640
+ ),
641
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_source_runs")),
642
+ )
643
+ op.create_index(
644
+ op.f("ix_source_runs_domain_id_source_id_started_at"),
645
+ "source_runs",
646
+ ["domain_id", "source_id", "started_at"],
647
+ unique=False,
648
+ )
649
+ op.create_table(
650
+ "tree_nodes",
651
+ sa.Column("domain_id", sa.Text(), nullable=False),
652
+ sa.Column("hash", sa.LargeBinary(), nullable=False),
653
+ sa.Column("body", sa.LargeBinary(), nullable=False),
654
+ sa.CheckConstraint("octet_length(hash) = 32", name=op.f("ck_tree_nodes_hash_h32")),
655
+ sa.ForeignKeyConstraint(
656
+ ["domain_id"],
657
+ ["domains.id"],
658
+ name=op.f("fk_tree_nodes_domain_id_domains"),
659
+ ondelete="CASCADE",
660
+ ),
661
+ sa.PrimaryKeyConstraint("domain_id", "hash", name=op.f("pk_tree_nodes")),
662
+ postgresql_partition_by="HASH (domain_id)",
663
+ )
664
+ op.create_table(
665
+ "vault",
666
+ sa.Column("domain_id", sa.Text(), nullable=False),
667
+ sa.Column("id", sa.Text(), nullable=False),
668
+ sa.Column("value_ciphertext", sa.LargeBinary(), nullable=True),
669
+ sa.Column("key_id", sa.Text(), nullable=True),
670
+ sa.Column(
671
+ "created_at",
672
+ sa.DateTime(timezone=True),
673
+ server_default=sa.text("now()"),
674
+ nullable=False,
675
+ ),
676
+ sa.Column("erased_at", sa.DateTime(timezone=True), nullable=True),
677
+ sa.ForeignKeyConstraint(
678
+ ["domain_id"],
679
+ ["domains.id"],
680
+ name=op.f("fk_vault_domain_id_domains"),
681
+ ondelete="CASCADE",
682
+ ),
683
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_vault")),
684
+ )
685
+ op.create_table(
686
+ "changesets",
687
+ sa.Column("domain_id", sa.Text(), nullable=False),
688
+ sa.Column("revision_id", sa.LargeBinary(), nullable=False),
689
+ sa.Column("partition", sa.Text(), nullable=False),
690
+ sa.Column("element_key", sa.Text(), nullable=False),
691
+ sa.Column("old_hash", sa.LargeBinary(), nullable=True),
692
+ sa.Column("new_hash", sa.LargeBinary(), nullable=True),
693
+ sa.Column("provenance", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
694
+ sa.ForeignKeyConstraint(
695
+ ["domain_id", "revision_id"],
696
+ ["revisions.domain_id", "revisions.id"],
697
+ name=op.f("fk_changesets_domain_id_revision_id_revisions"),
698
+ ondelete="CASCADE",
699
+ ),
700
+ sa.PrimaryKeyConstraint(
701
+ "domain_id", "revision_id", "partition", "element_key", name=op.f("pk_changesets")
702
+ ),
703
+ )
704
+ op.create_table(
705
+ "conflict_resolutions",
706
+ sa.Column("domain_id", sa.Text(), nullable=False),
707
+ sa.Column("proposal_id", sa.Text(), nullable=False),
708
+ sa.Column("fingerprint", sa.LargeBinary(), nullable=False),
709
+ sa.Column("resolution", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
710
+ sa.Column("resolved_by", sa.Text(), nullable=False),
711
+ sa.Column("at", sa.DateTime(timezone=True), nullable=False),
712
+ sa.ForeignKeyConstraint(
713
+ ["domain_id", "proposal_id"],
714
+ ["proposals.domain_id", "proposals.id"],
715
+ name=op.f("fk_conflict_resolutions_domain_id_proposal_id_proposals"),
716
+ ondelete="CASCADE",
717
+ ),
718
+ sa.PrimaryKeyConstraint(
719
+ "domain_id", "proposal_id", "fingerprint", name=op.f("pk_conflict_resolutions")
720
+ ),
721
+ )
722
+ op.create_table(
723
+ "document_chunks",
724
+ sa.Column("domain_id", sa.Text(), nullable=False),
725
+ sa.Column("document_id", sa.Text(), nullable=False),
726
+ sa.Column("seq", sa.Integer(), nullable=False),
727
+ sa.Column("start_offset", sa.Integer(), nullable=False),
728
+ sa.Column("end_offset", sa.Integer(), nullable=False),
729
+ sa.Column("text", sa.Text(), nullable=False),
730
+ sa.Column("chunk_hash", sa.LargeBinary(), nullable=False),
731
+ sa.ForeignKeyConstraint(
732
+ ["domain_id", "document_id"],
733
+ ["documents.domain_id", "documents.id"],
734
+ name=op.f("fk_document_chunks_domain_id_document_id_documents"),
735
+ ondelete="CASCADE",
736
+ ),
737
+ sa.PrimaryKeyConstraint("domain_id", "document_id", "seq", name=op.f("pk_document_chunks")),
738
+ )
739
+ op.create_table(
740
+ "merge_attempts",
741
+ sa.Column("domain_id", sa.Text(), nullable=False),
742
+ sa.Column("id", sa.Text(), nullable=False),
743
+ sa.Column("proposal_id", sa.Text(), nullable=False),
744
+ sa.Column("base", sa.LargeBinary(), nullable=False),
745
+ sa.Column("ours", sa.LargeBinary(), nullable=False),
746
+ sa.Column("theirs", sa.LargeBinary(), nullable=False),
747
+ sa.Column(
748
+ "status",
749
+ postgresql.ENUM(name="merge_attempt_status", create_type=False),
750
+ nullable=False,
751
+ ),
752
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
753
+ sa.ForeignKeyConstraint(
754
+ ["domain_id", "proposal_id"],
755
+ ["proposals.domain_id", "proposals.id"],
756
+ name=op.f("fk_merge_attempts_domain_id_proposal_id_proposals"),
757
+ ondelete="CASCADE",
758
+ ),
759
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_merge_attempts")),
760
+ )
761
+ op.create_index(
762
+ op.f("ix_merge_attempts_domain_id_proposal_id"),
763
+ "merge_attempts",
764
+ ["domain_id", "proposal_id"],
765
+ unique=False,
766
+ )
767
+ op.create_table(
768
+ "proposal_items",
769
+ sa.Column("domain_id", sa.Text(), nullable=False),
770
+ sa.Column("proposal_id", sa.Text(), nullable=False),
771
+ sa.Column("item_id", sa.Text(), nullable=False),
772
+ sa.Column("partition", sa.Text(), nullable=False),
773
+ sa.Column("element_key", sa.Text(), nullable=False),
774
+ sa.Column("attribute_id", sa.Text(), nullable=True),
775
+ sa.Column("evidence", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
776
+ sa.Column("confidence", sa.REAL(), nullable=True),
777
+ sa.Column(
778
+ "decision", postgresql.ENUM(name="item_decision", create_type=False), nullable=False
779
+ ),
780
+ sa.Column("decided_by", sa.Text(), nullable=True),
781
+ sa.Column("decided_at", sa.DateTime(timezone=True), nullable=True),
782
+ sa.ForeignKeyConstraint(
783
+ ["domain_id", "proposal_id"],
784
+ ["proposals.domain_id", "proposals.id"],
785
+ name=op.f("fk_proposal_items_domain_id_proposal_id_proposals"),
786
+ ondelete="CASCADE",
787
+ ),
788
+ sa.PrimaryKeyConstraint(
789
+ "domain_id", "proposal_id", "item_id", name=op.f("pk_proposal_items")
790
+ ),
791
+ )
792
+ op.create_table(
793
+ "proposal_reviews",
794
+ sa.Column("domain_id", sa.Text(), nullable=False),
795
+ sa.Column("id", sa.Text(), nullable=False),
796
+ sa.Column("proposal_id", sa.Text(), nullable=False),
797
+ sa.Column("reviewer", sa.Text(), nullable=False),
798
+ sa.Column(
799
+ "verdict", postgresql.ENUM(name="review_verdict", create_type=False), nullable=False
800
+ ),
801
+ sa.Column("comment", sa.Text(), nullable=True),
802
+ sa.Column("at", sa.DateTime(timezone=True), nullable=False),
803
+ sa.ForeignKeyConstraint(
804
+ ["domain_id", "proposal_id"],
805
+ ["proposals.domain_id", "proposals.id"],
806
+ name=op.f("fk_proposal_reviews_domain_id_proposal_id_proposals"),
807
+ ondelete="CASCADE",
808
+ ),
809
+ sa.PrimaryKeyConstraint("domain_id", "id", name=op.f("pk_proposal_reviews")),
810
+ )
811
+ op.create_index(
812
+ op.f("ix_proposal_reviews_domain_id_proposal_id"),
813
+ "proposal_reviews",
814
+ ["domain_id", "proposal_id"],
815
+ unique=False,
816
+ )
817
+ op.create_table(
818
+ "refs",
819
+ sa.Column("domain_id", sa.Text(), nullable=False),
820
+ sa.Column("name", sa.Text(), nullable=False),
821
+ sa.Column("kind", postgresql.ENUM(name="ref_kind", create_type=False), nullable=False),
822
+ sa.Column("head", sa.LargeBinary(), nullable=False),
823
+ sa.Column("protected", sa.Boolean(), server_default=sa.text("false"), nullable=False),
824
+ sa.Column(
825
+ "materialization",
826
+ postgresql.ENUM(name="materialization", create_type=False),
827
+ nullable=False,
828
+ ),
829
+ sa.Column("version", sa.BigInteger(), nullable=False),
830
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
831
+ sa.Column("created_by", sa.Text(), nullable=False),
832
+ sa.ForeignKeyConstraint(
833
+ ["domain_id", "head"],
834
+ ["revisions.domain_id", "revisions.id"],
835
+ name=op.f("fk_refs_domain_id_head_revisions"),
836
+ ),
837
+ sa.ForeignKeyConstraint(
838
+ ["domain_id"],
839
+ ["domains.id"],
840
+ name=op.f("fk_refs_domain_id_domains"),
841
+ ondelete="CASCADE",
842
+ ),
843
+ sa.PrimaryKeyConstraint("domain_id", "name", name=op.f("pk_refs")),
844
+ )
845
+ op.create_table(
846
+ "revision_parents",
847
+ sa.Column("domain_id", sa.Text(), nullable=False),
848
+ sa.Column("revision_id", sa.LargeBinary(), nullable=False),
849
+ sa.Column("position", sa.SmallInteger(), nullable=False),
850
+ sa.Column("parent_id", sa.LargeBinary(), nullable=False),
851
+ sa.ForeignKeyConstraint(
852
+ ["domain_id", "parent_id"],
853
+ ["revisions.domain_id", "revisions.id"],
854
+ name=op.f("fk_revision_parents_domain_id_parent_id_revisions"),
855
+ ),
856
+ sa.ForeignKeyConstraint(
857
+ ["domain_id", "revision_id"],
858
+ ["revisions.domain_id", "revisions.id"],
859
+ name=op.f("fk_revision_parents_domain_id_revision_id_revisions"),
860
+ ondelete="CASCADE",
861
+ ),
862
+ sa.PrimaryKeyConstraint(
863
+ "domain_id", "revision_id", "position", name=op.f("pk_revision_parents")
864
+ ),
865
+ )
866
+ op.create_index(
867
+ op.f("ix_revision_parents_domain_id_parent_id"),
868
+ "revision_parents",
869
+ ["domain_id", "parent_id"],
870
+ unique=False,
871
+ )
872
+ op.create_table(
873
+ "conflicts",
874
+ sa.Column("domain_id", sa.Text(), nullable=False),
875
+ sa.Column("attempt_id", sa.Text(), nullable=False),
876
+ sa.Column("id", sa.Text(), nullable=False),
877
+ sa.Column("code", sa.Text(), nullable=False),
878
+ sa.Column("fingerprint", sa.LargeBinary(), nullable=False),
879
+ sa.Column("partition", sa.Text(), nullable=False),
880
+ sa.Column("element_key", sa.Text(), nullable=True),
881
+ sa.Column("attribute_id", sa.Text(), nullable=True),
882
+ sa.Column("payload", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
883
+ sa.Column("member_count", sa.Integer(), nullable=False),
884
+ sa.Column("resolution", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
885
+ sa.Column("resolved_by", sa.Text(), nullable=True),
886
+ sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
887
+ sa.ForeignKeyConstraint(
888
+ ["domain_id", "attempt_id"],
889
+ ["merge_attempts.domain_id", "merge_attempts.id"],
890
+ name=op.f("fk_conflicts_domain_id_attempt_id_merge_attempts"),
891
+ ondelete="CASCADE",
892
+ ),
893
+ sa.PrimaryKeyConstraint("domain_id", "attempt_id", "id", name=op.f("pk_conflicts")),
894
+ sa.UniqueConstraint(
895
+ "domain_id",
896
+ "attempt_id",
897
+ "fingerprint",
898
+ name=op.f("uq_conflicts_domain_id_attempt_id_fingerprint"),
899
+ ),
900
+ )
901
+ op.create_table(
902
+ "mat_history",
903
+ sa.Column("domain_id", sa.Text(), nullable=False),
904
+ sa.Column("branch", sa.Text(), nullable=False),
905
+ sa.Column("partition", sa.Text(), nullable=False),
906
+ sa.Column("element_key", sa.Text(), nullable=False),
907
+ sa.Column("revision_id", sa.LargeBinary(), nullable=False),
908
+ sa.Column("at", sa.DateTime(timezone=True), nullable=False),
909
+ sa.Column("old_hash", sa.LargeBinary(), nullable=True),
910
+ sa.Column("new_hash", sa.LargeBinary(), nullable=True),
911
+ sa.Column("provenance", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
912
+ sa.ForeignKeyConstraint(
913
+ ["domain_id", "branch"],
914
+ ["refs.domain_id", "refs.name"],
915
+ name=op.f("fk_mat_history_domain_id_branch_refs"),
916
+ ondelete="CASCADE",
917
+ ),
918
+ sa.PrimaryKeyConstraint(
919
+ "domain_id",
920
+ "branch",
921
+ "partition",
922
+ "element_key",
923
+ "revision_id",
924
+ name=op.f("pk_mat_history"),
925
+ ),
926
+ )
927
+ op.create_table(
928
+ "mat_links",
929
+ sa.Column("domain_id", sa.Text(), nullable=False),
930
+ sa.Column("branch", sa.Text(), nullable=False),
931
+ sa.Column("relation_id", sa.Text(), nullable=False),
932
+ sa.Column("from_key", sa.Text(), nullable=False),
933
+ sa.Column("to_key", sa.Text(), nullable=False),
934
+ sa.Column(
935
+ "values",
936
+ postgresql.JSONB(astext_type=sa.Text()),
937
+ server_default=sa.text("'{}'::jsonb"),
938
+ nullable=False,
939
+ ),
940
+ sa.Column("object_hash", sa.LargeBinary(), nullable=False),
941
+ sa.Column("lineage", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
942
+ sa.ForeignKeyConstraint(
943
+ ["domain_id", "branch"],
944
+ ["refs.domain_id", "refs.name"],
945
+ name=op.f("fk_mat_links_domain_id_branch_refs"),
946
+ ondelete="CASCADE",
947
+ ),
948
+ sa.PrimaryKeyConstraint(
949
+ "domain_id", "branch", "relation_id", "from_key", "to_key", name=op.f("pk_mat_links")
950
+ ),
951
+ postgresql_partition_by="HASH (domain_id)",
952
+ )
953
+ op.create_index(
954
+ "ix_mat_links_reverse",
955
+ "mat_links",
956
+ ["domain_id", "branch", "relation_id", "to_key"],
957
+ unique=False,
958
+ )
959
+ op.create_table(
960
+ "mat_records",
961
+ sa.Column("domain_id", sa.Text(), nullable=False),
962
+ sa.Column("branch", sa.Text(), nullable=False),
963
+ sa.Column("kind_id", sa.Text(), nullable=False),
964
+ sa.Column("key", sa.Text(), nullable=False),
965
+ sa.Column("values", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
966
+ sa.Column(
967
+ "derived",
968
+ postgresql.JSONB(astext_type=sa.Text()),
969
+ server_default=sa.text("'{}'::jsonb"),
970
+ nullable=False,
971
+ ),
972
+ sa.Column("title", sa.Text(), nullable=True),
973
+ sa.Column("search", postgresql.TSVECTOR(), nullable=True),
974
+ sa.Column("object_hash", sa.LargeBinary(), nullable=False),
975
+ sa.Column("lineage", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
976
+ sa.Column("updated_revision", sa.LargeBinary(), nullable=False),
977
+ sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
978
+ sa.ForeignKeyConstraint(
979
+ ["domain_id", "branch"],
980
+ ["refs.domain_id", "refs.name"],
981
+ name=op.f("fk_mat_records_domain_id_branch_refs"),
982
+ ondelete="CASCADE",
983
+ ),
984
+ sa.PrimaryKeyConstraint(
985
+ "domain_id", "branch", "kind_id", "key", name=op.f("pk_mat_records")
986
+ ),
987
+ postgresql_partition_by="HASH (domain_id)",
988
+ )
989
+ op.create_index(
990
+ "ix_mat_records_search", "mat_records", ["search"], unique=False, postgresql_using="gin"
991
+ )
992
+ op.create_index(
993
+ "ix_mat_records_title",
994
+ "mat_records",
995
+ ["title"],
996
+ unique=False,
997
+ postgresql_using="gin",
998
+ postgresql_ops={"title": "gin_trgm_ops"},
999
+ )
1000
+ op.create_index(
1001
+ "ix_mat_records_values",
1002
+ "mat_records",
1003
+ ["values"],
1004
+ unique=False,
1005
+ postgresql_using="gin",
1006
+ postgresql_ops={"values": "jsonb_path_ops"},
1007
+ )
1008
+ op.create_table(
1009
+ "mat_schema",
1010
+ sa.Column("domain_id", sa.Text(), nullable=False),
1011
+ sa.Column("branch", sa.Text(), nullable=False),
1012
+ sa.Column("schema_hash", sa.LargeBinary(), nullable=False),
1013
+ sa.Column("compiled", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
1014
+ sa.ForeignKeyConstraint(
1015
+ ["domain_id", "branch"],
1016
+ ["refs.domain_id", "refs.name"],
1017
+ name=op.f("fk_mat_schema_domain_id_branch_refs"),
1018
+ ondelete="CASCADE",
1019
+ ),
1020
+ sa.PrimaryKeyConstraint("domain_id", "branch", name=op.f("pk_mat_schema")),
1021
+ )
1022
+
1023
+ _create_partitions(partitions)
1024
+ _compress(partitions)
1025
+ op.execute(
1026
+ f"CREATE INDEX embeddings_hnsw_{dims} ON embeddings "
1027
+ f"USING hnsw ((embedding::vector({dims})) vector_cosine_ops) WHERE dims = {dims}"
1028
+ )
1029
+ _enable_row_security(partitions)
1030
+
1031
+
1032
+ def downgrade() -> None:
1033
+ # Indexes, partitions and policies go with their tables.
1034
+ op.drop_table("mat_schema")
1035
+ op.drop_table("mat_records")
1036
+ op.drop_table("mat_links")
1037
+ op.drop_table("mat_history")
1038
+ op.drop_table("conflicts")
1039
+ op.drop_table("revision_parents")
1040
+ op.drop_table("refs")
1041
+ op.drop_table("proposal_reviews")
1042
+ op.drop_table("proposal_items")
1043
+ op.drop_table("merge_attempts")
1044
+ op.drop_table("document_chunks")
1045
+ op.drop_table("conflict_resolutions")
1046
+ op.drop_table("changesets")
1047
+ op.drop_table("vault")
1048
+ op.drop_table("tree_nodes")
1049
+ op.drop_table("source_runs")
1050
+ op.drop_table("revisions")
1051
+ op.drop_table("ref_log")
1052
+ op.drop_table("provenance")
1053
+ op.drop_table("proposals")
1054
+ op.drop_table("operation_invocations")
1055
+ op.drop_table("objects")
1056
+ op.drop_table("group_members")
1057
+ op.drop_table("grants")
1058
+ op.drop_table("extraction_jobs")
1059
+ op.drop_table("embeddings")
1060
+ op.drop_table("documents")
1061
+ op.drop_table("api_keys")
1062
+ op.drop_table("principals")
1063
+ op.drop_table("outbox")
1064
+ op.drop_table("jobs")
1065
+ op.drop_table("groups")
1066
+ op.drop_table("extraction_cache")
1067
+ op.drop_table("domains")
1068
+ op.drop_table("audit_log")
1069
+ bind = op.get_bind()
1070
+ for name in reversed(ENUMS):
1071
+ postgresql.ENUM(name=name).drop(bind, checkfirst=False)