nl2data-semantic-catalog-postgres 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 (34) hide show
  1. nl2data_semantic_catalog_postgres/__init__.py +18 -0
  2. nl2data_semantic_catalog_postgres/client.py +139 -0
  3. nl2data_semantic_catalog_postgres/config.py +136 -0
  4. nl2data_semantic_catalog_postgres/envelope.py +327 -0
  5. nl2data_semantic_catalog_postgres/errors.py +188 -0
  6. nl2data_semantic_catalog_postgres/fake_postgres/__init__.py +53 -0
  7. nl2data_semantic_catalog_postgres/fake_postgres/driver.py +209 -0
  8. nl2data_semantic_catalog_postgres/fake_postgres/handlers_audit.py +243 -0
  9. nl2data_semantic_catalog_postgres/fake_postgres/handlers_drafts.py +84 -0
  10. nl2data_semantic_catalog_postgres/fake_postgres/handlers_maintenance.py +263 -0
  11. nl2data_semantic_catalog_postgres/fake_postgres/handlers_publications.py +249 -0
  12. nl2data_semantic_catalog_postgres/fake_postgres/handlers_schema.py +23 -0
  13. nl2data_semantic_catalog_postgres/fake_postgres/handlers_snapshots.py +197 -0
  14. nl2data_semantic_catalog_postgres/fake_postgres/handlers_versions.py +330 -0
  15. nl2data_semantic_catalog_postgres/fake_postgres/keys.py +99 -0
  16. nl2data_semantic_catalog_postgres/fake_postgres/pool.py +152 -0
  17. nl2data_semantic_catalog_postgres/fake_postgres/registry.py +144 -0
  18. nl2data_semantic_catalog_postgres/maintenance.py +235 -0
  19. nl2data_semantic_catalog_postgres/py.typed +0 -0
  20. nl2data_semantic_catalog_postgres/repositories/__init__.py +26 -0
  21. nl2data_semantic_catalog_postgres/repositories/activation.py +849 -0
  22. nl2data_semantic_catalog_postgres/repositories/audit_evidence.py +221 -0
  23. nl2data_semantic_catalog_postgres/repositories/drafts.py +156 -0
  24. nl2data_semantic_catalog_postgres/repositories/evidence.py +415 -0
  25. nl2data_semantic_catalog_postgres/repositories/publications.py +486 -0
  26. nl2data_semantic_catalog_postgres/repositories/snapshots.py +372 -0
  27. nl2data_semantic_catalog_postgres/schema.py +337 -0
  28. nl2data_semantic_catalog_postgres/sql.py +520 -0
  29. nl2data_semantic_catalog_postgres/store.py +716 -0
  30. nl2data_semantic_catalog_postgres/unit_of_work.py +589 -0
  31. nl2data_semantic_catalog_postgres-0.1.0.dist-info/METADATA +76 -0
  32. nl2data_semantic_catalog_postgres-0.1.0.dist-info/RECORD +34 -0
  33. nl2data_semantic_catalog_postgres-0.1.0.dist-info/WHEEL +5 -0
  34. nl2data_semantic_catalog_postgres-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,337 @@
1
+ """Versioned PostgreSQL schema for the durable semantic catalog.
2
+
3
+ The catalog owns its own schema namespace (the configured ``namespace``),
4
+ separate from the workflow state store: workflow tables coordinate execution,
5
+ catalog tables hold versioned control-plane content. Records are scoped by
6
+ opaque tenant scope fingerprints exactly like the workflow store - raw tenant
7
+ identifiers are never keys.
8
+
9
+ Migrations are additive and versioned: version ``1`` creates the snapshot,
10
+ proposal-set, Bundle publication, pointer, history, event, and schema-metadata
11
+ surface. Version ``2`` adds durable assembly drafts and immutable publication
12
+ lifecycle records. Version ``3`` adds Verification Suite evidence. Version ``4``
13
+ adds publication audit evidence bindings and the bounded assembly
14
+ audit-evidence trail. A runtime never applies a migration newer than the schema
15
+ version it supports, and a database reporting a newer schema than the runtime
16
+ supports is rejected without modification.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ #: The newest schema version this runtime understands.
22
+ SUPPORTED_SCHEMA_VERSION = 4
23
+
24
+ #: Migration steps keyed by the schema version they produce. Each entry is
25
+ #: applied inside one transaction and only when the persisted version is
26
+ #: exactly ``version - 1``, so a partial deployment can never be guessed at.
27
+ #: Templates use the ``{schema}`` placeholder (the configured namespace).
28
+ MIGRATIONS: dict[int, tuple[str, ...]] = {
29
+ 1: (
30
+ # Catalog-owned metadata; named distinctly from the workflow store's
31
+ # ``schema_metadata`` so one deployment may share a namespace safely.
32
+ """
33
+ CREATE TABLE IF NOT EXISTS {schema}.catalog_schema_metadata (
34
+ key TEXT PRIMARY KEY,
35
+ value TEXT NOT NULL,
36
+ updated_at TIMESTAMPTZ NOT NULL
37
+ )
38
+ """,
39
+ """
40
+ CREATE TABLE IF NOT EXISTS {schema}.metadata_snapshots (
41
+ scope_namespace TEXT NOT NULL DEFAULT '',
42
+ snapshot_fingerprint TEXT NOT NULL,
43
+ source_id TEXT NOT NULL,
44
+ state TEXT NOT NULL,
45
+ schema_version INTEGER NOT NULL,
46
+ envelope TEXT NOT NULL,
47
+ discovered_at TIMESTAMPTZ NOT NULL,
48
+ retained_until TIMESTAMPTZ,
49
+ activated_at TIMESTAMPTZ,
50
+ created_at TIMESTAMPTZ NOT NULL,
51
+ PRIMARY KEY (scope_namespace, snapshot_fingerprint)
52
+ )
53
+ """,
54
+ """
55
+ CREATE INDEX IF NOT EXISTS idx_snapshots_source
56
+ ON {schema}.metadata_snapshots (scope_namespace, source_id, state)
57
+ """,
58
+ """
59
+ CREATE INDEX IF NOT EXISTS idx_snapshots_retention
60
+ ON {schema}.metadata_snapshots (scope_namespace, retained_until)
61
+ """,
62
+ """
63
+ CREATE TABLE IF NOT EXISTS {schema}.snapshot_pointers (
64
+ scope_namespace TEXT NOT NULL DEFAULT '',
65
+ source_id TEXT NOT NULL,
66
+ snapshot_fingerprint TEXT NOT NULL,
67
+ schema_version INTEGER NOT NULL,
68
+ activated_at TIMESTAMPTZ NOT NULL,
69
+ PRIMARY KEY (scope_namespace, source_id),
70
+ FOREIGN KEY (scope_namespace, snapshot_fingerprint)
71
+ REFERENCES {schema}.metadata_snapshots
72
+ (scope_namespace, snapshot_fingerprint)
73
+ ON DELETE RESTRICT
74
+ )
75
+ """,
76
+ """
77
+ CREATE TABLE IF NOT EXISTS {schema}.proposal_sets (
78
+ scope_namespace TEXT NOT NULL DEFAULT '',
79
+ snapshot_fingerprint TEXT NOT NULL,
80
+ schema_version INTEGER NOT NULL,
81
+ envelope TEXT NOT NULL,
82
+ saved_at TIMESTAMPTZ NOT NULL,
83
+ PRIMARY KEY (scope_namespace, snapshot_fingerprint),
84
+ FOREIGN KEY (scope_namespace, snapshot_fingerprint)
85
+ REFERENCES {schema}.metadata_snapshots
86
+ (scope_namespace, snapshot_fingerprint)
87
+ ON DELETE CASCADE
88
+ )
89
+ """,
90
+ """
91
+ CREATE TABLE IF NOT EXISTS {schema}.bundle_publications (
92
+ scope_namespace TEXT NOT NULL DEFAULT '',
93
+ bundle_id TEXT NOT NULL,
94
+ model_version TEXT NOT NULL,
95
+ bundle_fingerprint TEXT NOT NULL,
96
+ schema_version INTEGER NOT NULL,
97
+ envelope TEXT NOT NULL,
98
+ published_at TIMESTAMPTZ NOT NULL,
99
+ PRIMARY KEY (scope_namespace, bundle_id, model_version),
100
+ UNIQUE (scope_namespace, bundle_id, bundle_fingerprint)
101
+ )
102
+ """,
103
+ """
104
+ CREATE INDEX IF NOT EXISTS idx_publications_published
105
+ ON {schema}.bundle_publications (scope_namespace, published_at)
106
+ """,
107
+ """
108
+ CREATE TABLE IF NOT EXISTS {schema}.bundle_pointers (
109
+ scope_namespace TEXT NOT NULL DEFAULT '',
110
+ bundle_id TEXT NOT NULL,
111
+ model_version TEXT NOT NULL,
112
+ bundle_fingerprint TEXT NOT NULL,
113
+ schema_version INTEGER NOT NULL,
114
+ activated_at TIMESTAMPTZ NOT NULL,
115
+ activation_sequence BIGINT NOT NULL,
116
+ PRIMARY KEY (scope_namespace, bundle_id),
117
+ FOREIGN KEY (scope_namespace, bundle_id, model_version)
118
+ REFERENCES {schema}.bundle_publications
119
+ (scope_namespace, bundle_id, model_version)
120
+ ON DELETE RESTRICT
121
+ )
122
+ """,
123
+ """
124
+ CREATE TABLE IF NOT EXISTS {schema}.bundle_history (
125
+ scope_namespace TEXT NOT NULL DEFAULT '',
126
+ bundle_id TEXT NOT NULL,
127
+ position BIGINT NOT NULL,
128
+ model_version TEXT NOT NULL,
129
+ bundle_fingerprint TEXT NOT NULL,
130
+ schema_version INTEGER NOT NULL,
131
+ activated_at TIMESTAMPTZ NOT NULL,
132
+ deactivated_at TIMESTAMPTZ NOT NULL,
133
+ PRIMARY KEY (scope_namespace, bundle_id, position),
134
+ FOREIGN KEY (scope_namespace, bundle_id, model_version)
135
+ REFERENCES {schema}.bundle_publications
136
+ (scope_namespace, bundle_id, model_version)
137
+ ON DELETE RESTRICT
138
+ )
139
+ """,
140
+ """
141
+ CREATE INDEX IF NOT EXISTS idx_bundle_history_position
142
+ ON {schema}.bundle_history (scope_namespace, bundle_id, position)
143
+ """,
144
+ """
145
+ CREATE TABLE IF NOT EXISTS {schema}.lifecycle_events (
146
+ scope_namespace TEXT NOT NULL DEFAULT '',
147
+ event_id TEXT NOT NULL,
148
+ kind TEXT NOT NULL,
149
+ member_id TEXT,
150
+ schema_version INTEGER NOT NULL,
151
+ payload TEXT NOT NULL,
152
+ occurred_at TIMESTAMPTZ NOT NULL,
153
+ PRIMARY KEY (scope_namespace, event_id)
154
+ )
155
+ """,
156
+ """
157
+ CREATE INDEX IF NOT EXISTS idx_events_occurred
158
+ ON {schema}.lifecycle_events (scope_namespace, occurred_at)
159
+ """,
160
+ ),
161
+ 2: (
162
+ """
163
+ CREATE TABLE IF NOT EXISTS {schema}.assembly_drafts (
164
+ scope_namespace TEXT NOT NULL DEFAULT '',
165
+ draft_id TEXT NOT NULL,
166
+ bundle_id TEXT NOT NULL,
167
+ source_id TEXT NOT NULL,
168
+ draft_revision BIGINT NOT NULL,
169
+ state TEXT NOT NULL,
170
+ schema_version INTEGER NOT NULL,
171
+ envelope TEXT NOT NULL,
172
+ updated_at TIMESTAMPTZ NOT NULL,
173
+ PRIMARY KEY (scope_namespace, draft_id)
174
+ )
175
+ """,
176
+ """
177
+ CREATE INDEX IF NOT EXISTS idx_assembly_drafts_bundle
178
+ ON {schema}.assembly_drafts (scope_namespace, bundle_id, updated_at)
179
+ """,
180
+ """
181
+ CREATE TABLE IF NOT EXISTS {schema}.accepted_assertion_manifests (
182
+ scope_namespace TEXT NOT NULL DEFAULT '',
183
+ bundle_id TEXT NOT NULL,
184
+ bundle_fingerprint TEXT NOT NULL,
185
+ schema_version INTEGER NOT NULL,
186
+ envelope TEXT NOT NULL,
187
+ created_at TIMESTAMPTZ NOT NULL,
188
+ PRIMARY KEY (scope_namespace, bundle_id, bundle_fingerprint)
189
+ )
190
+ """,
191
+ """
192
+ CREATE TABLE IF NOT EXISTS {schema}.publish_audits (
193
+ scope_namespace TEXT NOT NULL DEFAULT '',
194
+ bundle_id TEXT NOT NULL,
195
+ bundle_fingerprint TEXT NOT NULL,
196
+ audit_id TEXT NOT NULL,
197
+ idempotency_key TEXT,
198
+ schema_version INTEGER NOT NULL,
199
+ envelope TEXT NOT NULL,
200
+ created_at TIMESTAMPTZ NOT NULL,
201
+ PRIMARY KEY (scope_namespace, bundle_id, bundle_fingerprint),
202
+ UNIQUE (scope_namespace, audit_id),
203
+ UNIQUE (scope_namespace, idempotency_key)
204
+ )
205
+ """,
206
+ """
207
+ CREATE TABLE IF NOT EXISTS {schema}.published_versions (
208
+ scope_namespace TEXT NOT NULL DEFAULT '',
209
+ bundle_id TEXT NOT NULL,
210
+ bundle_fingerprint TEXT NOT NULL,
211
+ model_version TEXT NOT NULL,
212
+ lifecycle_state TEXT NOT NULL,
213
+ predecessor_fingerprint TEXT,
214
+ successor_fingerprint TEXT,
215
+ audit_id TEXT,
216
+ published_at TIMESTAMPTZ NOT NULL,
217
+ PRIMARY KEY (scope_namespace, bundle_id, bundle_fingerprint),
218
+ UNIQUE (scope_namespace, bundle_id, model_version)
219
+ )
220
+ """,
221
+ """
222
+ CREATE INDEX IF NOT EXISTS idx_published_versions_order
223
+ ON {schema}.published_versions
224
+ (scope_namespace, bundle_id, published_at, model_version)
225
+ """,
226
+ """
227
+ CREATE TABLE IF NOT EXISTS {schema}.supersession_edges (
228
+ scope_namespace TEXT NOT NULL DEFAULT '',
229
+ bundle_id TEXT NOT NULL,
230
+ predecessor_fingerprint TEXT NOT NULL,
231
+ successor_fingerprint TEXT NOT NULL,
232
+ created_at TIMESTAMPTZ NOT NULL,
233
+ PRIMARY KEY (scope_namespace, bundle_id, successor_fingerprint),
234
+ UNIQUE (scope_namespace, bundle_id, predecessor_fingerprint)
235
+ )
236
+ """,
237
+ ),
238
+ 3: (
239
+ """
240
+ CREATE TABLE IF NOT EXISTS {schema}.verification_suite_evidence (
241
+ scope_namespace TEXT NOT NULL DEFAULT '',
242
+ bundle_id TEXT NOT NULL,
243
+ bundle_fingerprint TEXT NOT NULL,
244
+ evidence_fingerprint TEXT NOT NULL,
245
+ schema_version INTEGER NOT NULL,
246
+ envelope TEXT NOT NULL,
247
+ created_at TIMESTAMPTZ NOT NULL,
248
+ PRIMARY KEY (scope_namespace, bundle_id, bundle_fingerprint),
249
+ UNIQUE (scope_namespace, evidence_fingerprint),
250
+ FOREIGN KEY (scope_namespace, bundle_id, bundle_fingerprint)
251
+ REFERENCES {schema}.bundle_publications
252
+ (scope_namespace, bundle_id, bundle_fingerprint)
253
+ ON DELETE CASCADE
254
+ )
255
+ """,
256
+ ),
257
+ 4: (
258
+ # The publication audit-evidence binding persists atomically with
259
+ # its publication and dies with it (CASCADE), exactly like the
260
+ # verification evidence it cross-links to.
261
+ """
262
+ CREATE TABLE IF NOT EXISTS {schema}.publication_audit_evidence (
263
+ scope_namespace TEXT NOT NULL DEFAULT '',
264
+ bundle_id TEXT NOT NULL,
265
+ bundle_fingerprint TEXT NOT NULL,
266
+ evidence_fingerprint TEXT NOT NULL,
267
+ schema_version INTEGER NOT NULL,
268
+ envelope TEXT NOT NULL,
269
+ created_at TIMESTAMPTZ NOT NULL,
270
+ PRIMARY KEY (scope_namespace, bundle_id, bundle_fingerprint),
271
+ UNIQUE (scope_namespace, evidence_fingerprint),
272
+ FOREIGN KEY (scope_namespace, bundle_id, bundle_fingerprint)
273
+ REFERENCES {schema}.bundle_publications
274
+ (scope_namespace, bundle_id, bundle_fingerprint)
275
+ ON DELETE CASCADE
276
+ )
277
+ """,
278
+ # One bounded envelope per audit-evidence entry, scoped by the
279
+ # opaque tenant namespace; the entry fingerprint column is the
280
+ # independent tamper witness recomputed on every read.
281
+ """
282
+ CREATE TABLE IF NOT EXISTS {schema}.assembly_audit_evidence (
283
+ scope_namespace TEXT NOT NULL DEFAULT '',
284
+ event_id TEXT NOT NULL,
285
+ event_kind TEXT NOT NULL,
286
+ subject_kind TEXT NOT NULL,
287
+ subject_reference TEXT NOT NULL,
288
+ draft_id TEXT,
289
+ draft_revision BIGINT,
290
+ assertion_id TEXT,
291
+ bundle_fingerprint TEXT,
292
+ lifecycle_reference TEXT,
293
+ entry_fingerprint TEXT NOT NULL,
294
+ schema_version INTEGER NOT NULL,
295
+ envelope TEXT NOT NULL,
296
+ occurred_at TIMESTAMPTZ NOT NULL,
297
+ PRIMARY KEY (scope_namespace, event_id)
298
+ )
299
+ """,
300
+ """
301
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_subject
302
+ ON {schema}.assembly_audit_evidence
303
+ (scope_namespace, subject_kind, subject_reference, occurred_at, event_id)
304
+ """,
305
+ """
306
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_draft
307
+ ON {schema}.assembly_audit_evidence
308
+ (scope_namespace, draft_id, occurred_at, event_id)
309
+ """,
310
+ """
311
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_assertion
312
+ ON {schema}.assembly_audit_evidence
313
+ (scope_namespace, assertion_id)
314
+ """,
315
+ """
316
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_bundle
317
+ ON {schema}.assembly_audit_evidence
318
+ (scope_namespace, bundle_fingerprint)
319
+ """,
320
+ # Predecessor links live inside the canonical envelope; the GIN
321
+ # index keeps predecessor-scoped lookups bounded without denormalizing
322
+ # the immutable payload. Only the jsonb expression is indexed: plain
323
+ # text columns have no default GIN operator class, and the namespace
324
+ # equality is served by the scope-leading btree indexes via BitmapAnd.
325
+ """
326
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_predecessors
327
+ ON {schema}.assembly_audit_evidence USING gin (
328
+ (envelope::jsonb -> 'payload' -> 'predecessor_event_ids')
329
+ )
330
+ """,
331
+ """
332
+ CREATE INDEX IF NOT EXISTS idx_audit_evidence_retention
333
+ ON {schema}.assembly_audit_evidence (scope_namespace, occurred_at)
334
+ """,
335
+ ),
336
+ }
337
+