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,520 @@
1
+ """Categorized SQL template registry for the durable semantic catalog.
2
+
3
+ Every statement the catalog issues lives here, keyed by a stable name and
4
+ grouped by repository domain (schema, snapshots, proposal sets, drafts,
5
+ publications, publication lifecycle records, activation/history, events,
6
+ and maintenance). The ``{schema}`` placeholder is replaced with the
7
+ quoted deployment namespace at unit-of-work construction; tests may match
8
+ against these templates directly.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+
15
+ #: Bounded tenant-scope fingerprint accepted by public catalog methods.
16
+ FINGERPRINT_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$")
17
+
18
+ #: Safe migration bootstrap: the catalog metadata table itself is not
19
+ #: versioned and is created before any migration runs.
20
+ BOOTSTRAP_DDL = """
21
+ CREATE TABLE IF NOT EXISTS {schema}.catalog_schema_metadata (
22
+ key TEXT PRIMARY KEY,
23
+ value TEXT NOT NULL,
24
+ updated_at TIMESTAMPTZ NOT NULL
25
+ )
26
+ """
27
+
28
+ # -- schema metadata ------------------------------------------------------
29
+ _SCHEMA_SQL: dict[str, str] = {
30
+ "read_schema_version": (
31
+ "SELECT value FROM {schema}.catalog_schema_metadata "
32
+ "WHERE key = 'schema_version'"
33
+ ),
34
+ "write_schema_version": (
35
+ "INSERT INTO {schema}.catalog_schema_metadata "
36
+ "(key, value, updated_at) VALUES ('schema_version', %s, NOW()) "
37
+ "ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, "
38
+ "updated_at = NOW()"
39
+ ),
40
+ }
41
+
42
+ # -- snapshots ------------------------------------------------------------
43
+ _SNAPSHOT_SQL: dict[str, str] = {
44
+ "upsert_snapshot": (
45
+ "INSERT INTO {schema}.metadata_snapshots ("
46
+ "scope_namespace, snapshot_fingerprint, source_id, state, "
47
+ "schema_version, envelope, discovered_at, retained_until, created_at"
48
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) "
49
+ "ON CONFLICT (scope_namespace, snapshot_fingerprint) DO UPDATE SET "
50
+ "source_id = EXCLUDED.source_id, schema_version = EXCLUDED.schema_version, "
51
+ "envelope = EXCLUDED.envelope, discovered_at = EXCLUDED.discovered_at, "
52
+ "retained_until = EXCLUDED.retained_until"
53
+ ),
54
+ "read_snapshot_envelope": (
55
+ "SELECT envelope, schema_version, discovered_at "
56
+ "FROM {schema}.metadata_snapshots "
57
+ "WHERE scope_namespace = %s AND snapshot_fingerprint = %s"
58
+ ),
59
+ "lock_snapshot_row": (
60
+ "SELECT source_id, state, retained_until, discovered_at, envelope, "
61
+ "schema_version FROM {schema}.metadata_snapshots "
62
+ "WHERE scope_namespace = %s AND snapshot_fingerprint = %s FOR UPDATE"
63
+ ),
64
+ "set_snapshot_state": (
65
+ "UPDATE {schema}.metadata_snapshots "
66
+ "SET state = %s, activated_at = %s "
67
+ "WHERE scope_namespace = %s AND snapshot_fingerprint = %s"
68
+ ),
69
+ "snapshot_exists": (
70
+ "SELECT 1 FROM {schema}.metadata_snapshots "
71
+ "WHERE scope_namespace = %s AND snapshot_fingerprint = %s"
72
+ ),
73
+ "upsert_snapshot_pointer": (
74
+ "INSERT INTO {schema}.snapshot_pointers ("
75
+ "scope_namespace, source_id, snapshot_fingerprint, schema_version, "
76
+ "activated_at"
77
+ ") VALUES (%s, %s, %s, %s, %s) "
78
+ "ON CONFLICT (scope_namespace, source_id) DO UPDATE SET "
79
+ "snapshot_fingerprint = EXCLUDED.snapshot_fingerprint, "
80
+ "schema_version = EXCLUDED.schema_version, "
81
+ "activated_at = EXCLUDED.activated_at"
82
+ ),
83
+ "read_snapshot_pointer": (
84
+ "SELECT snapshot_fingerprint, schema_version "
85
+ "FROM {schema}.snapshot_pointers "
86
+ "WHERE scope_namespace = %s AND source_id = %s"
87
+ ),
88
+ "list_snapshot_pointers": (
89
+ "SELECT scope_namespace, source_id, snapshot_fingerprint "
90
+ "FROM {schema}.snapshot_pointers"
91
+ ),
92
+ }
93
+
94
+ # -- proposal sets ----------------------------------------------------------
95
+ _PROPOSAL_SET_SQL: dict[str, str] = {
96
+ "upsert_proposal_set": (
97
+ "INSERT INTO {schema}.proposal_sets ("
98
+ "scope_namespace, snapshot_fingerprint, schema_version, envelope, saved_at"
99
+ ") VALUES (%s, %s, %s, %s, %s) "
100
+ "ON CONFLICT (scope_namespace, snapshot_fingerprint) DO UPDATE SET "
101
+ "schema_version = EXCLUDED.schema_version, envelope = EXCLUDED.envelope, "
102
+ "saved_at = EXCLUDED.saved_at"
103
+ ),
104
+ "read_proposal_set": (
105
+ "SELECT envelope, schema_version FROM {schema}.proposal_sets "
106
+ "WHERE scope_namespace = %s AND snapshot_fingerprint = %s"
107
+ ),
108
+ }
109
+
110
+ # -- assembly drafts --------------------------------------------------------
111
+ _DRAFT_SQL: dict[str, str] = {
112
+ "insert_assembly_draft": (
113
+ "INSERT INTO {schema}.assembly_drafts ("
114
+ "scope_namespace, draft_id, bundle_id, source_id, draft_revision, "
115
+ "state, schema_version, envelope, updated_at"
116
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) "
117
+ "ON CONFLICT (scope_namespace, draft_id) DO NOTHING"
118
+ ),
119
+ "read_assembly_draft": (
120
+ "SELECT envelope, schema_version, draft_revision "
121
+ "FROM {schema}.assembly_drafts "
122
+ "WHERE scope_namespace = %s AND draft_id = %s"
123
+ ),
124
+ "lock_assembly_draft": (
125
+ "SELECT envelope, schema_version, draft_revision "
126
+ "FROM {schema}.assembly_drafts "
127
+ "WHERE scope_namespace = %s AND draft_id = %s FOR UPDATE"
128
+ ),
129
+ "replace_assembly_draft": (
130
+ "UPDATE {schema}.assembly_drafts SET bundle_id = %s, source_id = %s, "
131
+ "draft_revision = %s, state = %s, schema_version = %s, envelope = %s, "
132
+ "updated_at = %s WHERE scope_namespace = %s AND draft_id = %s "
133
+ "AND draft_revision = %s"
134
+ ),
135
+ }
136
+
137
+ # -- bundle publications ------------------------------------------------------
138
+ _PUBLICATION_SQL: dict[str, str] = {
139
+ "insert_publication": (
140
+ "INSERT INTO {schema}.bundle_publications ("
141
+ "scope_namespace, bundle_id, model_version, bundle_fingerprint, "
142
+ "schema_version, envelope, published_at"
143
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s) "
144
+ "ON CONFLICT (scope_namespace, bundle_id, model_version) DO NOTHING"
145
+ ),
146
+ "read_publication": (
147
+ "SELECT envelope, schema_version, published_at "
148
+ "FROM {schema}.bundle_publications "
149
+ "WHERE scope_namespace = %s AND bundle_id = %s AND model_version = %s"
150
+ ),
151
+ "read_publication_fingerprint": (
152
+ "SELECT bundle_fingerprint FROM {schema}.bundle_publications "
153
+ "WHERE scope_namespace = %s AND bundle_id = %s AND model_version = %s"
154
+ ),
155
+ "read_publication_by_fingerprint": (
156
+ "SELECT envelope, schema_version, published_at, model_version "
157
+ "FROM {schema}.bundle_publications WHERE scope_namespace = %s "
158
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
159
+ ),
160
+ "lock_publication_series": (
161
+ "SELECT pg_advisory_xact_lock(hashtextextended(%s || ':' || %s, 0))"
162
+ ),
163
+ "list_publications": (
164
+ "SELECT envelope, model_version, schema_version FROM {schema}.bundle_publications "
165
+ "WHERE scope_namespace = %s AND bundle_id = %s "
166
+ "ORDER BY published_at, model_version"
167
+ ),
168
+ }
169
+
170
+ # -- publication lifecycle records (manifests, evidence, audits) --------------
171
+ _PUBLICATION_LIFECYCLE_SQL: dict[str, str] = {
172
+ "insert_accepted_manifest": (
173
+ "INSERT INTO {schema}.accepted_assertion_manifests ("
174
+ "scope_namespace, bundle_id, bundle_fingerprint, schema_version, "
175
+ "envelope, created_at) VALUES (%s, %s, %s, %s, %s, %s)"
176
+ ),
177
+ "read_accepted_manifest": (
178
+ "SELECT envelope, schema_version FROM "
179
+ "{schema}.accepted_assertion_manifests WHERE scope_namespace = %s "
180
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
181
+ ),
182
+ "insert_verification_evidence": (
183
+ "INSERT INTO {schema}.verification_suite_evidence (scope_namespace, "
184
+ "bundle_id, bundle_fingerprint, evidence_fingerprint, schema_version, "
185
+ "envelope, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s)"
186
+ ),
187
+ "read_verification_evidence": (
188
+ "SELECT evidence_fingerprint, envelope, schema_version FROM "
189
+ "{schema}.verification_suite_evidence WHERE scope_namespace = %s "
190
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
191
+ ),
192
+ "insert_publish_audit": (
193
+ "INSERT INTO {schema}.publish_audits (scope_namespace, bundle_id, "
194
+ "bundle_fingerprint, audit_id, idempotency_key, schema_version, "
195
+ "envelope, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
196
+ ),
197
+ "read_publish_audit": (
198
+ "SELECT envelope, schema_version FROM {schema}.publish_audits "
199
+ "WHERE scope_namespace = %s AND bundle_id = %s "
200
+ "AND bundle_fingerprint = %s"
201
+ ),
202
+ "read_publish_by_idempotency_key": (
203
+ "SELECT bundle_id, bundle_fingerprint FROM {schema}.publish_audits "
204
+ "WHERE scope_namespace = %s AND idempotency_key = %s"
205
+ ),
206
+ }
207
+
208
+ # -- activation, versions, supersession, and history ---------------------------
209
+ _ACTIVATION_SQL: dict[str, str] = {
210
+ "read_latest_version": (
211
+ "SELECT bundle_fingerprint, lifecycle_state FROM "
212
+ "{schema}.published_versions WHERE scope_namespace = %s "
213
+ "AND bundle_id = %s ORDER BY published_at DESC, model_version DESC "
214
+ "LIMIT 1 FOR UPDATE"
215
+ ),
216
+ "insert_published_version": (
217
+ "INSERT INTO {schema}.published_versions (scope_namespace, bundle_id, "
218
+ "bundle_fingerprint, model_version, lifecycle_state, "
219
+ "predecessor_fingerprint, successor_fingerprint, audit_id, published_at) "
220
+ "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
221
+ ),
222
+ "update_version_successor": (
223
+ "UPDATE {schema}.published_versions SET successor_fingerprint = %s, "
224
+ "lifecycle_state = CASE WHEN lifecycle_state = 'active' THEN "
225
+ "lifecycle_state ELSE 'superseded' END WHERE scope_namespace = %s "
226
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
227
+ ),
228
+ "insert_supersession_edge": (
229
+ "INSERT INTO {schema}.supersession_edges (scope_namespace, bundle_id, "
230
+ "predecessor_fingerprint, successor_fingerprint, created_at) "
231
+ "VALUES (%s, %s, %s, %s, %s)"
232
+ ),
233
+ "read_published_version": (
234
+ "SELECT model_version, lifecycle_state, predecessor_fingerprint, "
235
+ "successor_fingerprint, audit_id, published_at FROM "
236
+ "{schema}.published_versions WHERE scope_namespace = %s "
237
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
238
+ ),
239
+ "list_published_versions": (
240
+ "SELECT bundle_fingerprint, model_version, lifecycle_state, "
241
+ "predecessor_fingerprint, successor_fingerprint, audit_id, published_at "
242
+ "FROM {schema}.published_versions WHERE scope_namespace = %s "
243
+ "AND bundle_id = %s ORDER BY published_at, model_version"
244
+ ),
245
+ "set_published_version_state": (
246
+ "UPDATE {schema}.published_versions SET lifecycle_state = %s "
247
+ "WHERE scope_namespace = %s AND bundle_id = %s "
248
+ "AND bundle_fingerprint = %s"
249
+ ),
250
+ "upsert_bundle_pointer": (
251
+ "INSERT INTO {schema}.bundle_pointers ("
252
+ "scope_namespace, bundle_id, model_version, bundle_fingerprint, "
253
+ "schema_version, activated_at, activation_sequence"
254
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s) "
255
+ "ON CONFLICT (scope_namespace, bundle_id) DO UPDATE SET "
256
+ "model_version = EXCLUDED.model_version, "
257
+ "bundle_fingerprint = EXCLUDED.bundle_fingerprint, "
258
+ "schema_version = EXCLUDED.schema_version, "
259
+ "activated_at = EXCLUDED.activated_at, "
260
+ "activation_sequence = EXCLUDED.activation_sequence"
261
+ ),
262
+ "read_bundle_pointer": (
263
+ "SELECT model_version, bundle_fingerprint, schema_version, "
264
+ "activation_sequence FROM {schema}.bundle_pointers "
265
+ "WHERE scope_namespace = %s AND bundle_id = %s"
266
+ ),
267
+ "lock_bundle_pointer": (
268
+ "SELECT model_version, bundle_fingerprint, schema_version, "
269
+ "activation_sequence, activated_at FROM {schema}.bundle_pointers "
270
+ "WHERE scope_namespace = %s AND bundle_id = %s FOR UPDATE"
271
+ ),
272
+ "next_history_position": (
273
+ "SELECT COALESCE(MAX(position), 0) + 1 AS next_position "
274
+ "FROM {schema}.bundle_history "
275
+ "WHERE scope_namespace = %s AND bundle_id = %s"
276
+ ),
277
+ "insert_history": (
278
+ "INSERT INTO {schema}.bundle_history ("
279
+ "scope_namespace, bundle_id, position, model_version, "
280
+ "bundle_fingerprint, schema_version, activated_at, deactivated_at"
281
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
282
+ ),
283
+ "read_history_top": (
284
+ "SELECT position, model_version, bundle_fingerprint, schema_version, "
285
+ "activated_at FROM {schema}.bundle_history "
286
+ "WHERE scope_namespace = %s AND bundle_id = %s "
287
+ "ORDER BY position DESC LIMIT 1"
288
+ ),
289
+ "delete_history_top": (
290
+ "DELETE FROM {schema}.bundle_history "
291
+ "WHERE scope_namespace = %s AND bundle_id = %s AND position = %s"
292
+ ),
293
+ "trim_history": (
294
+ "DELETE FROM {schema}.bundle_history "
295
+ "WHERE scope_namespace = %s AND bundle_id = %s AND position < %s"
296
+ ),
297
+ "list_bundle_pointers": (
298
+ "SELECT scope_namespace, bundle_id, model_version, bundle_fingerprint "
299
+ "FROM {schema}.bundle_pointers"
300
+ ),
301
+ "list_orphan_active_versions": (
302
+ "SELECT v.scope_namespace, v.bundle_id, v.model_version "
303
+ "FROM {schema}.published_versions v "
304
+ "WHERE v.lifecycle_state = 'active' "
305
+ "AND NOT EXISTS (SELECT 1 FROM {schema}.bundle_pointers p "
306
+ "WHERE p.scope_namespace = v.scope_namespace "
307
+ "AND p.bundle_id = v.bundle_id) "
308
+ "ORDER BY v.scope_namespace, v.bundle_id"
309
+ ),
310
+ }
311
+
312
+ # -- lifecycle events ---------------------------------------------------------
313
+ _EVENT_SQL: dict[str, str] = {
314
+ "insert_event": (
315
+ "INSERT INTO {schema}.lifecycle_events ("
316
+ "scope_namespace, event_id, kind, member_id, schema_version, payload, "
317
+ "occurred_at"
318
+ ") VALUES (%s, %s, %s, %s, %s, %s, %s) "
319
+ "ON CONFLICT (scope_namespace, event_id) DO NOTHING"
320
+ ),
321
+ }
322
+
323
+ # -- maintenance --------------------------------------------------------------
324
+ _MAINTENANCE_SQL: dict[str, str] = {
325
+ "delete_expired_snapshots": (
326
+ "WITH referenced_catalog_fingerprints AS ("
327
+ "SELECT DISTINCT ref ->> 'catalog_fingerprint' AS fingerprint "
328
+ "FROM {schema}.bundle_publications bp "
329
+ "JOIN {schema}.bundle_pointers ptr "
330
+ "ON ptr.scope_namespace = bp.scope_namespace "
331
+ "AND ptr.bundle_id = bp.bundle_id "
332
+ "AND ptr.model_version = bp.model_version "
333
+ "CROSS JOIN LATERAL jsonb_array_elements(COALESCE("
334
+ "bp.envelope::jsonb -> 'payload' -> 'sources', '[]'::jsonb)) ref "
335
+ "WHERE bp.envelope::jsonb ->> 'kind' = 'bundle' "
336
+ "AND ref ->> 'catalog_fingerprint' IS NOT NULL "
337
+ "UNION "
338
+ "SELECT DISTINCT bp.envelope::jsonb -> 'payload' -> 'descriptor' "
339
+ "->> 'catalog_fingerprint' AS fingerprint "
340
+ "FROM {schema}.bundle_publications bp "
341
+ "JOIN {schema}.bundle_pointers ptr "
342
+ "ON ptr.scope_namespace = bp.scope_namespace "
343
+ "AND ptr.bundle_id = bp.bundle_id "
344
+ "AND ptr.model_version = bp.model_version "
345
+ "WHERE bp.envelope::jsonb ->> 'kind' = 'bundle' "
346
+ "AND bp.envelope::jsonb -> 'payload' -> 'descriptor' "
347
+ "->> 'catalog_fingerprint' IS NOT NULL "
348
+ "UNION "
349
+ "SELECT DISTINCT ref ->> 'catalog_fingerprint' AS fingerprint "
350
+ "FROM {schema}.bundle_publications bp "
351
+ "JOIN {schema}.bundle_pointers ptr "
352
+ "ON ptr.scope_namespace = bp.scope_namespace "
353
+ "AND ptr.bundle_id = bp.bundle_id "
354
+ "AND ptr.model_version = bp.model_version "
355
+ "CROSS JOIN LATERAL jsonb_array_elements(COALESCE("
356
+ "bp.envelope::jsonb -> 'payload' -> 'compatibility' "
357
+ "-> 'compatible_catalog_fingerprints', '[]'::jsonb)) ref "
358
+ "WHERE bp.envelope::jsonb ->> 'kind' = 'bundle'"
359
+ ") "
360
+ "DELETE FROM {schema}.metadata_snapshots "
361
+ "WHERE (scope_namespace, snapshot_fingerprint) IN ("
362
+ "SELECT scope_namespace, snapshot_fingerprint "
363
+ "FROM {schema}.metadata_snapshots "
364
+ "WHERE retained_until < %s "
365
+ "AND snapshot_fingerprint NOT IN ("
366
+ "SELECT snapshot_fingerprint FROM {schema}.snapshot_pointers) "
367
+ "AND snapshot_fingerprint NOT IN ("
368
+ "SELECT fingerprint FROM referenced_catalog_fingerprints) "
369
+ "AND envelope::jsonb -> 'payload' -> 'source' "
370
+ "->> 'catalog_fingerprint' NOT IN ("
371
+ "SELECT fingerprint FROM referenced_catalog_fingerprints) "
372
+ "ORDER BY retained_until, snapshot_fingerprint LIMIT %s)"
373
+ ),
374
+ "delete_expired_publications": (
375
+ "DELETE FROM {schema}.bundle_publications "
376
+ "WHERE (scope_namespace, bundle_id, model_version) IN ("
377
+ "SELECT scope_namespace, bundle_id, model_version "
378
+ "FROM {schema}.bundle_publications "
379
+ "WHERE published_at < %s "
380
+ "AND (scope_namespace, bundle_id, model_version) NOT IN ("
381
+ "SELECT scope_namespace, bundle_id, model_version "
382
+ "FROM {schema}.bundle_pointers) "
383
+ "AND (scope_namespace, bundle_id, model_version) NOT IN ("
384
+ "SELECT scope_namespace, bundle_id, model_version "
385
+ "FROM {schema}.bundle_history) "
386
+ "AND (scope_namespace, bundle_id, model_version) NOT IN ("
387
+ "SELECT DISTINCT bp.scope_namespace, dep ->> 'bundle_id', "
388
+ "dep ->> 'version' "
389
+ "FROM {schema}.bundle_publications bp, "
390
+ "jsonb_array_elements(COALESCE("
391
+ "bp.envelope::jsonb -> 'payload' -> 'dependencies', '[]'::jsonb)) dep "
392
+ "WHERE bp.envelope::jsonb ->> 'kind' = 'bundle') "
393
+ "ORDER BY published_at, bundle_id, model_version LIMIT %s)"
394
+ ),
395
+ "delete_expired_events": (
396
+ "DELETE FROM {schema}.lifecycle_events "
397
+ "WHERE (scope_namespace, event_id) IN ("
398
+ "SELECT scope_namespace, event_id FROM {schema}.lifecycle_events "
399
+ "WHERE occurred_at < %s "
400
+ "ORDER BY occurred_at, event_id LIMIT %s)"
401
+ ),
402
+ # Audit-evidence retention preserves explainability: entries whose
403
+ # bundle fingerprint belongs to a non-retired published version and
404
+ # entries referenced as predecessors by such protected entries are
405
+ # never removed, whatever their age.
406
+ "delete_expired_audit_entries": (
407
+ "DELETE FROM {schema}.assembly_audit_evidence "
408
+ "WHERE (scope_namespace, event_id) IN ("
409
+ "SELECT a.scope_namespace, a.event_id "
410
+ "FROM {schema}.assembly_audit_evidence a "
411
+ "WHERE a.occurred_at < %s "
412
+ "AND NOT EXISTS ("
413
+ "SELECT 1 FROM {schema}.published_versions pv "
414
+ "WHERE pv.scope_namespace = a.scope_namespace "
415
+ "AND pv.bundle_fingerprint = a.bundle_fingerprint "
416
+ "AND pv.lifecycle_state <> 'retired') "
417
+ "AND NOT EXISTS ("
418
+ "SELECT 1 FROM {schema}.assembly_audit_evidence p "
419
+ "WHERE p.scope_namespace = a.scope_namespace "
420
+ "AND a.event_id IN (SELECT jsonb_array_elements_text("
421
+ "p.envelope::jsonb -> 'payload' -> 'predecessor_event_ids')) "
422
+ "AND EXISTS ("
423
+ "SELECT 1 FROM {schema}.published_versions pv2 "
424
+ "WHERE pv2.scope_namespace = p.scope_namespace "
425
+ "AND pv2.bundle_fingerprint = p.bundle_fingerprint "
426
+ "AND pv2.lifecycle_state <> 'retired')) "
427
+ "ORDER BY a.occurred_at, a.event_id LIMIT %s)"
428
+ ),
429
+ }
430
+
431
+ # -- assembly audit evidence -------------------------------------------------
432
+ _AUDIT_EVIDENCE_SQL: dict[str, str] = {
433
+ "insert_publication_audit_evidence": (
434
+ "INSERT INTO {schema}.publication_audit_evidence (scope_namespace, "
435
+ "bundle_id, bundle_fingerprint, evidence_fingerprint, schema_version, "
436
+ "envelope, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s)"
437
+ ),
438
+ "read_publication_audit_evidence": (
439
+ "SELECT evidence_fingerprint, envelope, schema_version FROM "
440
+ "{schema}.publication_audit_evidence WHERE scope_namespace = %s "
441
+ "AND bundle_id = %s AND bundle_fingerprint = %s"
442
+ ),
443
+ "insert_audit_entry": (
444
+ "INSERT INTO {schema}.assembly_audit_evidence (scope_namespace, "
445
+ "event_id, event_kind, subject_kind, subject_reference, draft_id, "
446
+ "draft_revision, assertion_id, bundle_fingerprint, "
447
+ "lifecycle_reference, entry_fingerprint, schema_version, envelope, "
448
+ "occurred_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, "
449
+ "%s, %s, %s) ON CONFLICT (scope_namespace, event_id) DO NOTHING"
450
+ ),
451
+ "read_audit_entry": (
452
+ "SELECT entry_fingerprint, envelope, schema_version, occurred_at FROM "
453
+ "{schema}.assembly_audit_evidence WHERE scope_namespace = %s "
454
+ "AND event_id = %s"
455
+ ),
456
+ # The publication-kind trail entry is the durable witness that a
457
+ # publication audit-evidence binding existed: a binding row that is
458
+ # lost while its publish audit survives is corruption, not legacy.
459
+ "read_publication_audit_entry": (
460
+ "SELECT event_id FROM {schema}.assembly_audit_evidence "
461
+ "WHERE scope_namespace = %s AND lifecycle_reference = %s "
462
+ "AND event_kind = 'publication' LIMIT 1"
463
+ ),
464
+ "read_latest_publication_entry": (
465
+ "SELECT event_id FROM {schema}.assembly_audit_evidence "
466
+ "WHERE scope_namespace = %s AND bundle_fingerprint = %s "
467
+ "AND event_kind = 'publication' "
468
+ "ORDER BY occurred_at DESC, event_id DESC LIMIT 1"
469
+ ),
470
+ # Optional equality filters keep the explicit ``IS NULL`` pattern so an
471
+ # unbound filter can never compare a column against itself (a
472
+ # ``col = COALESCE(x, col)`` predicate silently drops rows whose column
473
+ # is NULL). The revision bounds and the keyset cursor use the same
474
+ # pattern. Ordering is the deterministic (occurred_at, event_id)
475
+ # lifecycle order shared with the core trail.
476
+ "count_audit_evidence": (
477
+ "SELECT COUNT(*) AS total FROM {schema}.assembly_audit_evidence "
478
+ "WHERE scope_namespace = %s "
479
+ "AND (%s::text IS NULL OR draft_id = %s) "
480
+ "AND (%s::text IS NULL OR assertion_id = %s) "
481
+ "AND (%s::text IS NULL OR bundle_fingerprint = %s) "
482
+ "AND (%s::text IS NULL OR lifecycle_reference = %s) "
483
+ "AND (%s::int IS NULL OR draft_revision >= %s::int) "
484
+ "AND (%s::int IS NULL OR draft_revision <= %s::int) "
485
+ "AND (%s::text IS NULL OR envelope::jsonb -> 'payload' "
486
+ "-> 'predecessor_event_ids' ? %s::text) "
487
+ "AND (%s::timestamptz IS NULL OR (occurred_at, event_id) > "
488
+ "(%s::timestamptz, %s))"
489
+ ),
490
+ "list_audit_evidence": (
491
+ "SELECT event_id, entry_fingerprint, envelope, schema_version, "
492
+ "occurred_at FROM "
493
+ "{schema}.assembly_audit_evidence WHERE scope_namespace = %s "
494
+ "AND (%s::text IS NULL OR draft_id = %s) "
495
+ "AND (%s::text IS NULL OR assertion_id = %s) "
496
+ "AND (%s::text IS NULL OR bundle_fingerprint = %s) "
497
+ "AND (%s::text IS NULL OR lifecycle_reference = %s) "
498
+ "AND (%s::int IS NULL OR draft_revision >= %s::int) "
499
+ "AND (%s::int IS NULL OR draft_revision <= %s::int) "
500
+ "AND (%s::text IS NULL OR envelope::jsonb -> 'payload' "
501
+ "-> 'predecessor_event_ids' ? %s::text) "
502
+ "AND (%s::timestamptz IS NULL OR (occurred_at, event_id) > "
503
+ "(%s::timestamptz, %s)) "
504
+ "ORDER BY occurred_at, event_id LIMIT %s"
505
+ ),
506
+ }
507
+
508
+ #: Every statement the catalog issues, keyed by a stable name.
509
+ SQL_TEMPLATES: dict[str, str] = {
510
+ **_SCHEMA_SQL,
511
+ **_SNAPSHOT_SQL,
512
+ **_PROPOSAL_SET_SQL,
513
+ **_DRAFT_SQL,
514
+ **_PUBLICATION_SQL,
515
+ **_PUBLICATION_LIFECYCLE_SQL,
516
+ **_ACTIVATION_SQL,
517
+ **_EVENT_SQL,
518
+ **_AUDIT_EVIDENCE_SQL,
519
+ **_MAINTENANCE_SQL,
520
+ }