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.
- nl2data_semantic_catalog_postgres/__init__.py +18 -0
- nl2data_semantic_catalog_postgres/client.py +139 -0
- nl2data_semantic_catalog_postgres/config.py +136 -0
- nl2data_semantic_catalog_postgres/envelope.py +327 -0
- nl2data_semantic_catalog_postgres/errors.py +188 -0
- nl2data_semantic_catalog_postgres/fake_postgres/__init__.py +53 -0
- nl2data_semantic_catalog_postgres/fake_postgres/driver.py +209 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_audit.py +243 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_drafts.py +84 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_maintenance.py +263 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_publications.py +249 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_schema.py +23 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_snapshots.py +197 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_versions.py +330 -0
- nl2data_semantic_catalog_postgres/fake_postgres/keys.py +99 -0
- nl2data_semantic_catalog_postgres/fake_postgres/pool.py +152 -0
- nl2data_semantic_catalog_postgres/fake_postgres/registry.py +144 -0
- nl2data_semantic_catalog_postgres/maintenance.py +235 -0
- nl2data_semantic_catalog_postgres/py.typed +0 -0
- nl2data_semantic_catalog_postgres/repositories/__init__.py +26 -0
- nl2data_semantic_catalog_postgres/repositories/activation.py +849 -0
- nl2data_semantic_catalog_postgres/repositories/audit_evidence.py +221 -0
- nl2data_semantic_catalog_postgres/repositories/drafts.py +156 -0
- nl2data_semantic_catalog_postgres/repositories/evidence.py +415 -0
- nl2data_semantic_catalog_postgres/repositories/publications.py +486 -0
- nl2data_semantic_catalog_postgres/repositories/snapshots.py +372 -0
- nl2data_semantic_catalog_postgres/schema.py +337 -0
- nl2data_semantic_catalog_postgres/sql.py +520 -0
- nl2data_semantic_catalog_postgres/store.py +716 -0
- nl2data_semantic_catalog_postgres/unit_of_work.py +589 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/METADATA +76 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/RECORD +34 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/WHEEL +5 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""Verification evidence, publish audit, and accepted-manifest repository.
|
|
2
|
+
|
|
3
|
+
Every read revalidates the persisted envelope and cross-checks the
|
|
4
|
+
persisted metadata against the requested publication identity, so tampered
|
|
5
|
+
or truncated records fail closed. Historical evidence validation uses
|
|
6
|
+
only immutable publication-time records (manifest, evidence, audit, frozen
|
|
7
|
+
release binding); the mutable draft store is never read here.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from nl2data_core.assembly.audit_evidence import PublicationAuditEvidence
|
|
15
|
+
from nl2data_core.assembly.manifest import AcceptedAssertionManifest
|
|
16
|
+
from nl2data_core.bundles.publication import PublishAuditRecord
|
|
17
|
+
from nl2data_core.canonical import strict_sha256_fingerprint
|
|
18
|
+
from nl2data_core.control_plane.publication.contracts import (
|
|
19
|
+
FrozenReleaseBinding,
|
|
20
|
+
PublicationIntegrityError,
|
|
21
|
+
PublicationRecordSet,
|
|
22
|
+
validate_publication_integrity,
|
|
23
|
+
)
|
|
24
|
+
from nl2data_core.verification.models import VerificationSuiteEvidence
|
|
25
|
+
|
|
26
|
+
from ..envelope import ENVELOPE_SCHEMA_VERSION, ArtifactKind
|
|
27
|
+
from ..errors import SemanticCatalogError, SemanticCatalogErrorCode
|
|
28
|
+
from ..unit_of_work import CatalogUnitOfWork, _namespace
|
|
29
|
+
|
|
30
|
+
# Read paths translate centralized integrity codes into the established
|
|
31
|
+
# persisted-record cause types so callers see stable error details.
|
|
32
|
+
_CAUSE_TYPES = {
|
|
33
|
+
"manifest_mismatch": "ManifestMetadataMismatch",
|
|
34
|
+
"audit_mismatch": "AuditMetadataMismatch",
|
|
35
|
+
"verification_audit_mismatch": "VerificationAuditMismatch",
|
|
36
|
+
"verification_manifest_mismatch": "VerificationBindingMismatch",
|
|
37
|
+
"verification_binding_mismatch": "VerificationBindingMismatch",
|
|
38
|
+
"verification_evidence_mismatch": "VerificationEvidenceMetadataMismatch",
|
|
39
|
+
"verification_binding_audit_mismatch": "VerificationAuditMismatch",
|
|
40
|
+
"publication_audit_evidence_mismatch": "PublicationAuditEvidenceMismatch",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _integrity_rejection(error: PublicationIntegrityError) -> SemanticCatalogError:
|
|
45
|
+
if error.code == "verification_binding_mismatch" and (
|
|
46
|
+
error.classification == "legacy_unverified"
|
|
47
|
+
):
|
|
48
|
+
details: dict[str, str] = {
|
|
49
|
+
"classification": "legacy_unverified",
|
|
50
|
+
"cause_type": "LegacyVerificationEvidenceMissingFrozenBinding",
|
|
51
|
+
}
|
|
52
|
+
else:
|
|
53
|
+
details = {
|
|
54
|
+
"cause_type": _CAUSE_TYPES.get(error.code, "VerificationAuditMismatch")
|
|
55
|
+
}
|
|
56
|
+
return SemanticCatalogError(
|
|
57
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED, error.message, details=details
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EvidenceRepository:
|
|
62
|
+
"""Immutable publication-time lifecycle records for one Bundle."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, uow: CatalogUnitOfWork) -> None:
|
|
65
|
+
self._uow = uow
|
|
66
|
+
|
|
67
|
+
def read_publish_audit(
|
|
68
|
+
self,
|
|
69
|
+
conn: Any,
|
|
70
|
+
namespace: str,
|
|
71
|
+
bundle_id: str,
|
|
72
|
+
fingerprint: str,
|
|
73
|
+
) -> PublishAuditRecord | None:
|
|
74
|
+
"""Load one audit row inside an existing transaction."""
|
|
75
|
+
row = self._uow.execute(
|
|
76
|
+
conn,
|
|
77
|
+
"read_publish_audit",
|
|
78
|
+
(namespace, bundle_id, fingerprint),
|
|
79
|
+
).fetchone()
|
|
80
|
+
if row is None:
|
|
81
|
+
return None
|
|
82
|
+
envelope = self._uow.decode(
|
|
83
|
+
row["envelope"],
|
|
84
|
+
ArtifactKind.PUBLISH_AUDIT,
|
|
85
|
+
row_schema_version=row["schema_version"],
|
|
86
|
+
)
|
|
87
|
+
audit = self._uow.audit_from_envelope(envelope)
|
|
88
|
+
if audit.bundle_id != bundle_id or audit.bundle_fingerprint != fingerprint:
|
|
89
|
+
raise SemanticCatalogError(
|
|
90
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
91
|
+
"persisted audit metadata does not match its publication",
|
|
92
|
+
details={"cause_type": "AuditMetadataMismatch"},
|
|
93
|
+
)
|
|
94
|
+
return audit
|
|
95
|
+
|
|
96
|
+
def read_release_binding(
|
|
97
|
+
self,
|
|
98
|
+
conn: Any,
|
|
99
|
+
namespace: str,
|
|
100
|
+
bundle_id: str,
|
|
101
|
+
fingerprint: str,
|
|
102
|
+
) -> FrozenReleaseBinding | None:
|
|
103
|
+
"""Load the frozen release binding persisted with one evidence row."""
|
|
104
|
+
row = self._uow.execute(
|
|
105
|
+
conn,
|
|
106
|
+
"read_verification_evidence",
|
|
107
|
+
(namespace, bundle_id, fingerprint),
|
|
108
|
+
).fetchone()
|
|
109
|
+
if row is None:
|
|
110
|
+
return None
|
|
111
|
+
envelope = self._uow.decode(
|
|
112
|
+
row["envelope"],
|
|
113
|
+
ArtifactKind.VERIFICATION_SUITE_EVIDENCE,
|
|
114
|
+
row_schema_version=row["schema_version"],
|
|
115
|
+
)
|
|
116
|
+
return self._uow.release_binding_from_envelope(envelope)
|
|
117
|
+
|
|
118
|
+
def insert_publication_audit_evidence(
|
|
119
|
+
self,
|
|
120
|
+
conn: Any,
|
|
121
|
+
namespace: str,
|
|
122
|
+
bundle_id: str,
|
|
123
|
+
fingerprint: str,
|
|
124
|
+
binding: PublicationAuditEvidence,
|
|
125
|
+
*,
|
|
126
|
+
now: Any,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Persist the publication audit-evidence binding inside a transaction."""
|
|
129
|
+
payload = self._uow.publication_audit_evidence_payload(binding)
|
|
130
|
+
self._uow.execute(
|
|
131
|
+
conn,
|
|
132
|
+
"insert_publication_audit_evidence",
|
|
133
|
+
(
|
|
134
|
+
namespace,
|
|
135
|
+
bundle_id,
|
|
136
|
+
fingerprint,
|
|
137
|
+
binding.fingerprint,
|
|
138
|
+
ENVELOPE_SCHEMA_VERSION,
|
|
139
|
+
self._uow.encode(
|
|
140
|
+
ArtifactKind.PUBLICATION_AUDIT_EVIDENCE,
|
|
141
|
+
payload,
|
|
142
|
+
strict_sha256_fingerprint(payload),
|
|
143
|
+
),
|
|
144
|
+
now,
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def read_publication_audit_evidence(
|
|
149
|
+
self,
|
|
150
|
+
conn: Any,
|
|
151
|
+
namespace: str,
|
|
152
|
+
bundle_id: str,
|
|
153
|
+
fingerprint: str,
|
|
154
|
+
) -> PublicationAuditEvidence | None:
|
|
155
|
+
"""Load one publication audit-evidence binding inside a transaction."""
|
|
156
|
+
row = self._uow.execute(
|
|
157
|
+
conn,
|
|
158
|
+
"read_publication_audit_evidence",
|
|
159
|
+
(namespace, bundle_id, fingerprint),
|
|
160
|
+
).fetchone()
|
|
161
|
+
if row is None:
|
|
162
|
+
return None
|
|
163
|
+
envelope = self._uow.decode(
|
|
164
|
+
row["envelope"],
|
|
165
|
+
ArtifactKind.PUBLICATION_AUDIT_EVIDENCE,
|
|
166
|
+
row_schema_version=row["schema_version"],
|
|
167
|
+
)
|
|
168
|
+
binding = self._uow.publication_audit_evidence_from_envelope(envelope)
|
|
169
|
+
if (
|
|
170
|
+
binding.bundle_fingerprint != fingerprint
|
|
171
|
+
or binding.fingerprint != row["evidence_fingerprint"]
|
|
172
|
+
):
|
|
173
|
+
raise SemanticCatalogError(
|
|
174
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
175
|
+
"persisted publication audit evidence metadata does not "
|
|
176
|
+
"match publication",
|
|
177
|
+
details={"cause_type": "PublicationAuditEvidenceMismatch"},
|
|
178
|
+
)
|
|
179
|
+
return binding
|
|
180
|
+
|
|
181
|
+
def read_verification_evidence(
|
|
182
|
+
self,
|
|
183
|
+
conn: Any,
|
|
184
|
+
namespace: str,
|
|
185
|
+
bundle_id: str,
|
|
186
|
+
fingerprint: str,
|
|
187
|
+
) -> VerificationSuiteEvidence | None:
|
|
188
|
+
"""Load one evidence row inside an existing transaction."""
|
|
189
|
+
row = self._uow.execute(
|
|
190
|
+
conn,
|
|
191
|
+
"read_verification_evidence",
|
|
192
|
+
(namespace, bundle_id, fingerprint),
|
|
193
|
+
).fetchone()
|
|
194
|
+
if row is None:
|
|
195
|
+
return None
|
|
196
|
+
envelope = self._uow.decode(
|
|
197
|
+
row["envelope"],
|
|
198
|
+
ArtifactKind.VERIFICATION_SUITE_EVIDENCE,
|
|
199
|
+
row_schema_version=row["schema_version"],
|
|
200
|
+
)
|
|
201
|
+
evidence = self._uow.evidence_from_envelope(envelope)
|
|
202
|
+
if (
|
|
203
|
+
evidence.bundle_fingerprint != fingerprint
|
|
204
|
+
or evidence.fingerprint != row["evidence_fingerprint"]
|
|
205
|
+
):
|
|
206
|
+
raise SemanticCatalogError(
|
|
207
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
208
|
+
"persisted verification evidence metadata does not match publication",
|
|
209
|
+
details={"cause_type": "VerificationEvidenceMetadataMismatch"},
|
|
210
|
+
)
|
|
211
|
+
return evidence
|
|
212
|
+
|
|
213
|
+
def accepted_assertion_manifest(
|
|
214
|
+
self,
|
|
215
|
+
bundle_id: str,
|
|
216
|
+
fingerprint: str,
|
|
217
|
+
*,
|
|
218
|
+
tenant_scope_fingerprint: str | None = None,
|
|
219
|
+
) -> AcceptedAssertionManifest | None:
|
|
220
|
+
"""Load the immutable accepted-assertion manifest for a publication."""
|
|
221
|
+
namespace = _namespace(tenant_scope_fingerprint)
|
|
222
|
+
with self._uow.transaction() as conn:
|
|
223
|
+
row = self._uow.execute(
|
|
224
|
+
conn,
|
|
225
|
+
"read_accepted_manifest",
|
|
226
|
+
(namespace, bundle_id, fingerprint),
|
|
227
|
+
).fetchone()
|
|
228
|
+
if row is None:
|
|
229
|
+
return None
|
|
230
|
+
envelope = self._uow.decode(
|
|
231
|
+
row["envelope"],
|
|
232
|
+
ArtifactKind.ACCEPTED_ASSERTION_MANIFEST,
|
|
233
|
+
row_schema_version=row["schema_version"],
|
|
234
|
+
)
|
|
235
|
+
manifest = self._uow.manifest_from_envelope(envelope)
|
|
236
|
+
if manifest.bundle_id != bundle_id or manifest.bundle_fingerprint != fingerprint:
|
|
237
|
+
raise SemanticCatalogError(
|
|
238
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
239
|
+
"persisted manifest metadata does not match its publication",
|
|
240
|
+
details={"cause_type": "ManifestMetadataMismatch"},
|
|
241
|
+
)
|
|
242
|
+
return manifest
|
|
243
|
+
|
|
244
|
+
def publish_audit(
|
|
245
|
+
self,
|
|
246
|
+
bundle_id: str,
|
|
247
|
+
fingerprint: str,
|
|
248
|
+
*,
|
|
249
|
+
tenant_scope_fingerprint: str | None = None,
|
|
250
|
+
) -> PublishAuditRecord | None:
|
|
251
|
+
"""Load the immutable safe audit record for a publication."""
|
|
252
|
+
namespace = _namespace(tenant_scope_fingerprint)
|
|
253
|
+
with self._uow.transaction() as conn:
|
|
254
|
+
return self.read_publish_audit(conn, namespace, bundle_id, fingerprint)
|
|
255
|
+
|
|
256
|
+
def validated_publication_records(
|
|
257
|
+
self,
|
|
258
|
+
conn: Any,
|
|
259
|
+
namespace: str,
|
|
260
|
+
bundle_id: str,
|
|
261
|
+
fingerprint: str,
|
|
262
|
+
*,
|
|
263
|
+
audit_id: str | None = None,
|
|
264
|
+
) -> PublicationRecordSet:
|
|
265
|
+
"""Load and centrally validate one publication's lifecycle records.
|
|
266
|
+
|
|
267
|
+
This is the single read-side integrity entry point: every reuse,
|
|
268
|
+
record, activation, rollback, and reload path wraps its loaded
|
|
269
|
+
rows in a ``PublicationRecordSet`` and validates through the
|
|
270
|
+
shared rule set instead of per-entry-point checks. Legacy
|
|
271
|
+
compatibility publications (plain Bundle, manifest-only) yield a
|
|
272
|
+
record set with ``audit`` and ``verification_evidence`` unset.
|
|
273
|
+
``audit_id`` is the published-version row's independent witness
|
|
274
|
+
that a publish audit was written with the publication; a missing
|
|
275
|
+
row it claims existed is corruption and fails closed.
|
|
276
|
+
"""
|
|
277
|
+
evidence = self.read_verification_evidence(
|
|
278
|
+
conn, namespace, bundle_id, fingerprint
|
|
279
|
+
)
|
|
280
|
+
audit = self.read_publish_audit(conn, namespace, bundle_id, fingerprint)
|
|
281
|
+
audit_evidence = self.read_publication_audit_evidence(
|
|
282
|
+
conn, namespace, bundle_id, fingerprint
|
|
283
|
+
)
|
|
284
|
+
manifest_row = self._uow.execute(
|
|
285
|
+
conn,
|
|
286
|
+
"read_accepted_manifest",
|
|
287
|
+
(namespace, bundle_id, fingerprint),
|
|
288
|
+
).fetchone()
|
|
289
|
+
manifest = None
|
|
290
|
+
if manifest_row is not None:
|
|
291
|
+
manifest_envelope = self._uow.decode(
|
|
292
|
+
manifest_row["envelope"],
|
|
293
|
+
ArtifactKind.ACCEPTED_ASSERTION_MANIFEST,
|
|
294
|
+
row_schema_version=manifest_row["schema_version"],
|
|
295
|
+
)
|
|
296
|
+
manifest = self._uow.manifest_from_envelope(manifest_envelope)
|
|
297
|
+
binding = None
|
|
298
|
+
if evidence is not None:
|
|
299
|
+
binding = self.read_release_binding(
|
|
300
|
+
conn, namespace, bundle_id, fingerprint
|
|
301
|
+
)
|
|
302
|
+
if evidence is None and audit is None and audit_id is not None:
|
|
303
|
+
# The version row is an independent witness that a publish
|
|
304
|
+
# audit existed; a missing audit row is corruption.
|
|
305
|
+
raise SemanticCatalogError(
|
|
306
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
307
|
+
"published version has lost its publish audit",
|
|
308
|
+
details={"cause_type": "MissingPublishAudit"},
|
|
309
|
+
)
|
|
310
|
+
records = PublicationRecordSet(
|
|
311
|
+
bundle_id=bundle_id,
|
|
312
|
+
bundle_fingerprint=fingerprint,
|
|
313
|
+
accepted_assertion_manifest=manifest,
|
|
314
|
+
audit=audit,
|
|
315
|
+
verification_evidence=evidence,
|
|
316
|
+
frozen_release_binding=binding,
|
|
317
|
+
audit_evidence=audit_evidence,
|
|
318
|
+
)
|
|
319
|
+
try:
|
|
320
|
+
validate_publication_integrity(records)
|
|
321
|
+
except PublicationIntegrityError as error:
|
|
322
|
+
if (
|
|
323
|
+
error.code == "verification_audit_mismatch"
|
|
324
|
+
and audit is not None
|
|
325
|
+
and evidence is None
|
|
326
|
+
):
|
|
327
|
+
# The audit is an independent witness that the evidence
|
|
328
|
+
# row existed; a missing row is corruption.
|
|
329
|
+
raise SemanticCatalogError(
|
|
330
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
331
|
+
"verification evidence row is missing for its publish audit",
|
|
332
|
+
details={"cause_type": "MissingVerificationEvidence"},
|
|
333
|
+
) from error
|
|
334
|
+
if (
|
|
335
|
+
error.code == "verification_audit_mismatch"
|
|
336
|
+
and audit is None
|
|
337
|
+
and audit_id is not None
|
|
338
|
+
):
|
|
339
|
+
# The version row is an independent witness that a publish
|
|
340
|
+
# audit existed; a missing audit row is corruption.
|
|
341
|
+
raise SemanticCatalogError(
|
|
342
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
343
|
+
"published version has lost its publish audit",
|
|
344
|
+
details={"cause_type": "MissingPublishAudit"},
|
|
345
|
+
) from error
|
|
346
|
+
raise _integrity_rejection(error) from error
|
|
347
|
+
if (
|
|
348
|
+
audit is not None
|
|
349
|
+
and audit_evidence is None
|
|
350
|
+
and self._uow.execute(
|
|
351
|
+
conn,
|
|
352
|
+
"read_publication_audit_entry",
|
|
353
|
+
(namespace, audit.audit_id),
|
|
354
|
+
).fetchone()
|
|
355
|
+
is not None
|
|
356
|
+
):
|
|
357
|
+
# The publication-kind trail entry witnesses that a binding row
|
|
358
|
+
# was persisted atomically with the publish audit; a binding
|
|
359
|
+
# row that vanished while its witness survives is corruption,
|
|
360
|
+
# never a legacy shape.
|
|
361
|
+
raise SemanticCatalogError(
|
|
362
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
363
|
+
"publication audit evidence row is missing for its publish "
|
|
364
|
+
"audit",
|
|
365
|
+
details={"cause_type": "MissingPublicationAuditEvidence"},
|
|
366
|
+
)
|
|
367
|
+
return records
|
|
368
|
+
|
|
369
|
+
def validated_verification_evidence(
|
|
370
|
+
self,
|
|
371
|
+
conn: Any,
|
|
372
|
+
namespace: str,
|
|
373
|
+
bundle_id: str,
|
|
374
|
+
fingerprint: str,
|
|
375
|
+
) -> VerificationSuiteEvidence | None:
|
|
376
|
+
"""Load evidence with full immutable binding validation in one transaction.
|
|
377
|
+
|
|
378
|
+
Thin view over ``validated_publication_records``: evidence is valid
|
|
379
|
+
only when its frozen release binding, audit, and manifest
|
|
380
|
+
cross-links validate together. Tenant-scope checks against the
|
|
381
|
+
caller are applied by the transaction-owner entry points.
|
|
382
|
+
"""
|
|
383
|
+
records = self.validated_publication_records(
|
|
384
|
+
conn, namespace, bundle_id, fingerprint
|
|
385
|
+
)
|
|
386
|
+
return records.verification_evidence
|
|
387
|
+
|
|
388
|
+
def verification_evidence(
|
|
389
|
+
self,
|
|
390
|
+
bundle_id: str,
|
|
391
|
+
fingerprint: str,
|
|
392
|
+
*,
|
|
393
|
+
tenant_scope_fingerprint: str | None = None,
|
|
394
|
+
) -> VerificationSuiteEvidence | None:
|
|
395
|
+
"""Load immutable bounded verification evidence for a publication."""
|
|
396
|
+
namespace = _namespace(tenant_scope_fingerprint)
|
|
397
|
+
with self._uow.transaction() as conn:
|
|
398
|
+
evidence = self.validated_verification_evidence(
|
|
399
|
+
conn, namespace, bundle_id, fingerprint
|
|
400
|
+
)
|
|
401
|
+
if evidence is None:
|
|
402
|
+
return None
|
|
403
|
+
if (
|
|
404
|
+
tenant_scope_fingerprint is None
|
|
405
|
+
or evidence.tenant_scope_fingerprint != tenant_scope_fingerprint
|
|
406
|
+
):
|
|
407
|
+
raise SemanticCatalogError(
|
|
408
|
+
SemanticCatalogErrorCode.ENVELOPE_REJECTED,
|
|
409
|
+
"persisted verification evidence does not match publish audit",
|
|
410
|
+
details={"cause_type": "VerificationAuditMismatch"},
|
|
411
|
+
)
|
|
412
|
+
return evidence
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
__all__ = ["EvidenceRepository"]
|