cmgl 1.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.
- cmgl/__init__.py +269 -0
- cmgl/__main__.py +4 -0
- cmgl/absence.py +29 -0
- cmgl/adapters/__init__.py +3 -0
- cmgl/adapters/binding.py +212 -0
- cmgl/adapters/common.py +331 -0
- cmgl/adapters/graphiti.py +235 -0
- cmgl/adapters/langgraph.py +183 -0
- cmgl/adapters/langmem.py +395 -0
- cmgl/adapters/mem0.py +339 -0
- cmgl/admission.py +78 -0
- cmgl/audit.py +442 -0
- cmgl/authority.py +310 -0
- cmgl/backends/__init__.py +4 -0
- cmgl/backends/inmemory.py +216 -0
- cmgl/backends/protocol.py +34 -0
- cmgl/canonical.py +45 -0
- cmgl/challenge.py +28 -0
- cmgl/checker.py +64 -0
- cmgl/cli.py +119 -0
- cmgl/commands/__init__.py +1 -0
- cmgl/commands/_common.py +16 -0
- cmgl/commands/adapters.py +68 -0
- cmgl/commands/audit.py +42 -0
- cmgl/commands/authority.py +111 -0
- cmgl/commands/compression.py +29 -0
- cmgl/commands/conformance.py +44 -0
- cmgl/commands/ledger.py +80 -0
- cmgl/commands/lifecycle.py +98 -0
- cmgl/commands/memory.py +116 -0
- cmgl/commands/promotion.py +42 -0
- cmgl/commands/receipt.py +55 -0
- cmgl/commands/reference.py +27 -0
- cmgl/commands/retrieve.py +45 -0
- cmgl/commands/schema.py +27 -0
- cmgl/commands/telemetry.py +50 -0
- cmgl/commands/validate.py +56 -0
- cmgl/commands/workflow.py +29 -0
- cmgl/compression.py +250 -0
- cmgl/config.py +116 -0
- cmgl/conformance.py +119 -0
- cmgl/contracts/__init__.py +198 -0
- cmgl/contracts/adapters.py +57 -0
- cmgl/contracts/authority.py +104 -0
- cmgl/contracts/base.py +22 -0
- cmgl/contracts/compression.py +106 -0
- cmgl/contracts/conformance.py +52 -0
- cmgl/contracts/contamination.py +57 -0
- cmgl/contracts/enums.py +184 -0
- cmgl/contracts/ledger.py +89 -0
- cmgl/contracts/lifecycle.py +105 -0
- cmgl/contracts/memory.py +188 -0
- cmgl/contracts/obligations.py +49 -0
- cmgl/contracts/promotion.py +114 -0
- cmgl/contracts/receipts.py +61 -0
- cmgl/contracts/rules.py +21 -0
- cmgl/contracts/telemetry.py +203 -0
- cmgl/contracts/workflow.py +95 -0
- cmgl/current.py +20 -0
- cmgl/digest.py +18 -0
- cmgl/doctor.py +83 -0
- cmgl/evidence.py +134 -0
- cmgl/exceptions.py +29 -0
- cmgl/guarded.py +177 -0
- cmgl/layer.py +359 -0
- cmgl/ledger.py +375 -0
- cmgl/ledger_signing.py +77 -0
- cmgl/lifecycle.py +157 -0
- cmgl/live_smoke.py +212 -0
- cmgl/models.py +189 -0
- cmgl/obligations.py +306 -0
- cmgl/pipeline.py +345 -0
- cmgl/policy.py +343 -0
- cmgl/py.typed +1 -0
- cmgl/receipt_verifier.py +235 -0
- cmgl/rules.py +502 -0
- cmgl/schemas.py +195 -0
- cmgl/state.py +96 -0
- cmgl/stress.py +86 -0
- cmgl/telemetry.py +70 -0
- cmgl/telemetry_ingest.py +137 -0
- cmgl/telemetry_replay.py +299 -0
- cmgl/time.py +19 -0
- cmgl/transitions.py +118 -0
- cmgl/validation.py +160 -0
- cmgl/workflow.py +227 -0
- cmgl-1.1.0.dist-info/METADATA +407 -0
- cmgl-1.1.0.dist-info/RECORD +91 -0
- cmgl-1.1.0.dist-info/WHEEL +4 -0
- cmgl-1.1.0.dist-info/entry_points.txt +2 -0
- cmgl-1.1.0.dist-info/licenses/LICENSE +199 -0
cmgl/__init__.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from cmgl.admission import RetrievalFilterResult, candidate_from_event, filter_retrieval
|
|
2
|
+
from cmgl.authority import (
|
|
3
|
+
StrictAuthorityVerifier,
|
|
4
|
+
authorize_bundle,
|
|
5
|
+
authorize_request,
|
|
6
|
+
make_authority_bundle,
|
|
7
|
+
make_authority_evidence_bundle,
|
|
8
|
+
make_authority_receipt,
|
|
9
|
+
make_declared_scope,
|
|
10
|
+
make_protected_action_request,
|
|
11
|
+
)
|
|
12
|
+
from cmgl.compression import (
|
|
13
|
+
CompressionProbeSuite,
|
|
14
|
+
audit_compression_certificate,
|
|
15
|
+
compression_metrics,
|
|
16
|
+
make_compression_certificate,
|
|
17
|
+
)
|
|
18
|
+
from cmgl.config import (
|
|
19
|
+
AuthorityConfig,
|
|
20
|
+
CMGLConfig,
|
|
21
|
+
LedgerConfig,
|
|
22
|
+
PolicyConfig,
|
|
23
|
+
load_config,
|
|
24
|
+
)
|
|
25
|
+
from cmgl.conformance import audit_ledger_conformance
|
|
26
|
+
from cmgl.digest import sha256_digest
|
|
27
|
+
from cmgl.evidence import (
|
|
28
|
+
build_evidence_manifest,
|
|
29
|
+
build_input_set_manifest,
|
|
30
|
+
build_promotion_evidence_bundle,
|
|
31
|
+
build_replay_evidence,
|
|
32
|
+
versioned_ref_from_event,
|
|
33
|
+
)
|
|
34
|
+
from cmgl.guarded import GuardedMemoryBackend
|
|
35
|
+
from cmgl.layer import GovernanceLayer
|
|
36
|
+
from cmgl.ledger import (
|
|
37
|
+
AppendOnlyLedger,
|
|
38
|
+
LedgerRecord,
|
|
39
|
+
LedgerVerificationResult,
|
|
40
|
+
make_duplicate_policy_receipt,
|
|
41
|
+
make_schema_migration_record,
|
|
42
|
+
)
|
|
43
|
+
from cmgl.models import (
|
|
44
|
+
AbsenceNoticeType,
|
|
45
|
+
ActivePromotionReceipt,
|
|
46
|
+
AdapterOperationReceipt,
|
|
47
|
+
AdapterOperationStatus,
|
|
48
|
+
AdmissionDecision,
|
|
49
|
+
AuthorityBundle,
|
|
50
|
+
AuthorityEvidenceBundle,
|
|
51
|
+
AuthorityReceipt,
|
|
52
|
+
BackendName,
|
|
53
|
+
ChallengeStatus,
|
|
54
|
+
CompressionAuditReport,
|
|
55
|
+
CompressionBridgeProbe,
|
|
56
|
+
CompressionCertificate,
|
|
57
|
+
CompressionDeploymentProbe,
|
|
58
|
+
CompressionFailureClass,
|
|
59
|
+
CompressionGluingProbe,
|
|
60
|
+
ConformanceFinding,
|
|
61
|
+
ConformanceLevel,
|
|
62
|
+
ConformanceProfile,
|
|
63
|
+
ConformanceReport,
|
|
64
|
+
ConformanceSeverity,
|
|
65
|
+
ContaminationAuditReport,
|
|
66
|
+
ContaminationContext,
|
|
67
|
+
ContaminationLane,
|
|
68
|
+
ContaminationStateReplay,
|
|
69
|
+
CurrentMemoryView,
|
|
70
|
+
DeclaredScope,
|
|
71
|
+
DuplicatePolicyReceipt,
|
|
72
|
+
EvidenceBindingReport,
|
|
73
|
+
EvidenceManifest,
|
|
74
|
+
ExternalMemoryRef,
|
|
75
|
+
GovernanceProfile,
|
|
76
|
+
GovernanceReceiptBundle,
|
|
77
|
+
InputSetManifest,
|
|
78
|
+
LeaseReceipt,
|
|
79
|
+
LedgerAppendReceipt,
|
|
80
|
+
LedgerIntegrityReceipt,
|
|
81
|
+
LedgerLineStatus,
|
|
82
|
+
MemoryCandidate,
|
|
83
|
+
MemoryChallengeRecord,
|
|
84
|
+
MemoryEvent,
|
|
85
|
+
MemoryEventType,
|
|
86
|
+
MemoryGovernanceEvidenceContract,
|
|
87
|
+
MemoryRevision,
|
|
88
|
+
MemoryStateSnapshot,
|
|
89
|
+
MemoryStatus,
|
|
90
|
+
MemoryTelemetryEvent,
|
|
91
|
+
MetricResult,
|
|
92
|
+
MetricStatus,
|
|
93
|
+
ObligationGraph,
|
|
94
|
+
ObligationStatus,
|
|
95
|
+
PromotionEvidenceBundle,
|
|
96
|
+
PromotionReceipt,
|
|
97
|
+
ProtectedAction,
|
|
98
|
+
ProtectedActionRequest,
|
|
99
|
+
QuarantineRecord,
|
|
100
|
+
RationalValue,
|
|
101
|
+
ReceiptObligation,
|
|
102
|
+
RecordAbsenceNotice,
|
|
103
|
+
ReplayEvidence,
|
|
104
|
+
ReportTermBinding,
|
|
105
|
+
RetrievalDecision,
|
|
106
|
+
RollbackReceipt,
|
|
107
|
+
RollbackSnapshot,
|
|
108
|
+
SchemaMigrationRecord,
|
|
109
|
+
SemanticRule,
|
|
110
|
+
ShadowTrialReceipt,
|
|
111
|
+
TelemetryAuditReport,
|
|
112
|
+
TelemetryEventOutcome,
|
|
113
|
+
TelemetryEventType,
|
|
114
|
+
TelemetryIngestResult,
|
|
115
|
+
TelemetryLineDiagnostic,
|
|
116
|
+
TelemetryOutcomeStatus,
|
|
117
|
+
TelemetryReplayReport,
|
|
118
|
+
TelemetryStateReplay,
|
|
119
|
+
VerificationWitness,
|
|
120
|
+
VersionedMemoryRef,
|
|
121
|
+
WorkflowBottleneckReport,
|
|
122
|
+
WorkflowEvidenceSet,
|
|
123
|
+
WorkflowLayer,
|
|
124
|
+
)
|
|
125
|
+
from cmgl.pipeline import PromotionPipeline, PromotionPipelineResult
|
|
126
|
+
from cmgl.policy import AdmissionPolicy
|
|
127
|
+
from cmgl.receipt_verifier import PromotionVerifier
|
|
128
|
+
from cmgl.state import current_memory_view_from_events, current_memory_view_from_ledger
|
|
129
|
+
from cmgl.stress import SharedMemoryStressResult, shared_memory_stress_fixture
|
|
130
|
+
from cmgl.telemetry_ingest import ingest_telemetry_jsonl
|
|
131
|
+
from cmgl.telemetry_replay import replay_telemetry_jsonl
|
|
132
|
+
from cmgl.workflow import (
|
|
133
|
+
make_memory_governance_evidence_contract,
|
|
134
|
+
make_workflow_evidence_set,
|
|
135
|
+
workflow_report_from_evidence,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
__version__ = "1.1.0"
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"AbsenceNoticeType",
|
|
142
|
+
"ActivePromotionReceipt",
|
|
143
|
+
"AdapterOperationReceipt",
|
|
144
|
+
"AdapterOperationStatus",
|
|
145
|
+
"AdmissionDecision",
|
|
146
|
+
"AdmissionPolicy",
|
|
147
|
+
"AppendOnlyLedger",
|
|
148
|
+
"AuthorityBundle",
|
|
149
|
+
"AuthorityConfig",
|
|
150
|
+
"AuthorityEvidenceBundle",
|
|
151
|
+
"AuthorityReceipt",
|
|
152
|
+
"BackendName",
|
|
153
|
+
"CMGLConfig",
|
|
154
|
+
"ChallengeStatus",
|
|
155
|
+
"CompressionAuditReport",
|
|
156
|
+
"CompressionBridgeProbe",
|
|
157
|
+
"CompressionCertificate",
|
|
158
|
+
"CompressionDeploymentProbe",
|
|
159
|
+
"CompressionFailureClass",
|
|
160
|
+
"CompressionGluingProbe",
|
|
161
|
+
"CompressionProbeSuite",
|
|
162
|
+
"ConformanceFinding",
|
|
163
|
+
"ConformanceLevel",
|
|
164
|
+
"ConformanceProfile",
|
|
165
|
+
"ConformanceReport",
|
|
166
|
+
"ConformanceSeverity",
|
|
167
|
+
"ContaminationAuditReport",
|
|
168
|
+
"ContaminationContext",
|
|
169
|
+
"ContaminationLane",
|
|
170
|
+
"ContaminationStateReplay",
|
|
171
|
+
"CurrentMemoryView",
|
|
172
|
+
"DeclaredScope",
|
|
173
|
+
"DuplicatePolicyReceipt",
|
|
174
|
+
"EvidenceBindingReport",
|
|
175
|
+
"EvidenceManifest",
|
|
176
|
+
"ExternalMemoryRef",
|
|
177
|
+
"GovernanceLayer",
|
|
178
|
+
"GovernanceProfile",
|
|
179
|
+
"GovernanceReceiptBundle",
|
|
180
|
+
"GuardedMemoryBackend",
|
|
181
|
+
"InputSetManifest",
|
|
182
|
+
"LeaseReceipt",
|
|
183
|
+
"LedgerAppendReceipt",
|
|
184
|
+
"LedgerConfig",
|
|
185
|
+
"LedgerIntegrityReceipt",
|
|
186
|
+
"LedgerLineStatus",
|
|
187
|
+
"LedgerRecord",
|
|
188
|
+
"LedgerVerificationResult",
|
|
189
|
+
"MemoryCandidate",
|
|
190
|
+
"MemoryChallengeRecord",
|
|
191
|
+
"MemoryEvent",
|
|
192
|
+
"MemoryEventType",
|
|
193
|
+
"MemoryGovernanceEvidenceContract",
|
|
194
|
+
"MemoryRevision",
|
|
195
|
+
"MemoryStateSnapshot",
|
|
196
|
+
"MemoryStatus",
|
|
197
|
+
"MemoryTelemetryEvent",
|
|
198
|
+
"MetricResult",
|
|
199
|
+
"MetricStatus",
|
|
200
|
+
"ObligationGraph",
|
|
201
|
+
"ObligationStatus",
|
|
202
|
+
"PolicyConfig",
|
|
203
|
+
"PromotionEvidenceBundle",
|
|
204
|
+
"PromotionPipeline",
|
|
205
|
+
"PromotionPipelineResult",
|
|
206
|
+
"PromotionReceipt",
|
|
207
|
+
"PromotionVerifier",
|
|
208
|
+
"ProtectedAction",
|
|
209
|
+
"ProtectedActionRequest",
|
|
210
|
+
"QuarantineRecord",
|
|
211
|
+
"RationalValue",
|
|
212
|
+
"ReceiptObligation",
|
|
213
|
+
"RecordAbsenceNotice",
|
|
214
|
+
"ReplayEvidence",
|
|
215
|
+
"ReportTermBinding",
|
|
216
|
+
"RetrievalDecision",
|
|
217
|
+
"RetrievalFilterResult",
|
|
218
|
+
"RollbackReceipt",
|
|
219
|
+
"RollbackSnapshot",
|
|
220
|
+
"SchemaMigrationRecord",
|
|
221
|
+
"SemanticRule",
|
|
222
|
+
"ShadowTrialReceipt",
|
|
223
|
+
"SharedMemoryStressResult",
|
|
224
|
+
"StrictAuthorityVerifier",
|
|
225
|
+
"TelemetryAuditReport",
|
|
226
|
+
"TelemetryEventOutcome",
|
|
227
|
+
"TelemetryEventType",
|
|
228
|
+
"TelemetryIngestResult",
|
|
229
|
+
"TelemetryLineDiagnostic",
|
|
230
|
+
"TelemetryOutcomeStatus",
|
|
231
|
+
"TelemetryReplayReport",
|
|
232
|
+
"TelemetryStateReplay",
|
|
233
|
+
"VerificationWitness",
|
|
234
|
+
"VersionedMemoryRef",
|
|
235
|
+
"WorkflowBottleneckReport",
|
|
236
|
+
"WorkflowEvidenceSet",
|
|
237
|
+
"WorkflowLayer",
|
|
238
|
+
"__version__",
|
|
239
|
+
"audit_compression_certificate",
|
|
240
|
+
"audit_ledger_conformance",
|
|
241
|
+
"authorize_bundle",
|
|
242
|
+
"authorize_request",
|
|
243
|
+
"build_evidence_manifest",
|
|
244
|
+
"build_input_set_manifest",
|
|
245
|
+
"build_promotion_evidence_bundle",
|
|
246
|
+
"build_replay_evidence",
|
|
247
|
+
"candidate_from_event",
|
|
248
|
+
"compression_metrics",
|
|
249
|
+
"current_memory_view_from_events",
|
|
250
|
+
"current_memory_view_from_ledger",
|
|
251
|
+
"filter_retrieval",
|
|
252
|
+
"ingest_telemetry_jsonl",
|
|
253
|
+
"load_config",
|
|
254
|
+
"make_authority_bundle",
|
|
255
|
+
"make_authority_evidence_bundle",
|
|
256
|
+
"make_authority_receipt",
|
|
257
|
+
"make_compression_certificate",
|
|
258
|
+
"make_declared_scope",
|
|
259
|
+
"make_duplicate_policy_receipt",
|
|
260
|
+
"make_memory_governance_evidence_contract",
|
|
261
|
+
"make_protected_action_request",
|
|
262
|
+
"make_schema_migration_record",
|
|
263
|
+
"make_workflow_evidence_set",
|
|
264
|
+
"replay_telemetry_jsonl",
|
|
265
|
+
"sha256_digest",
|
|
266
|
+
"shared_memory_stress_fixture",
|
|
267
|
+
"versioned_ref_from_event",
|
|
268
|
+
"workflow_report_from_evidence",
|
|
269
|
+
]
|
cmgl/__main__.py
ADDED
cmgl/absence.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from cmgl.digest import sha256_digest
|
|
6
|
+
from cmgl.models import AbsenceNoticeType, RecordAbsenceNotice
|
|
7
|
+
from cmgl.time import now_utc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def make_record_absence_notice(
|
|
11
|
+
*,
|
|
12
|
+
notice_type: AbsenceNoticeType,
|
|
13
|
+
memory_id: str | None = None,
|
|
14
|
+
missing_record: object | None = None,
|
|
15
|
+
disclosure: object | None = None,
|
|
16
|
+
reason_codes: list[str] | None = None,
|
|
17
|
+
) -> RecordAbsenceNotice:
|
|
18
|
+
default_code = f"absence.{notice_type.value}"
|
|
19
|
+
body = {
|
|
20
|
+
"schema_version": "cmgl.record_absence_notice.v1",
|
|
21
|
+
"notice_id": f"absence:{uuid4()}",
|
|
22
|
+
"notice_type": notice_type,
|
|
23
|
+
"memory_id": memory_id,
|
|
24
|
+
"missing_record_digest": None if missing_record is None else sha256_digest(missing_record),
|
|
25
|
+
"disclosure_digest": None if disclosure is None else sha256_digest(disclosure),
|
|
26
|
+
"reason_codes": list(reason_codes or [default_code]),
|
|
27
|
+
"timestamp": now_utc(),
|
|
28
|
+
}
|
|
29
|
+
return RecordAbsenceNotice(**body, notice_digest=sha256_digest(body))
|
cmgl/adapters/binding.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Literal, cast
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from cmgl.adapters.common import object_to_mapping
|
|
12
|
+
from cmgl.digest import sha256_digest
|
|
13
|
+
from cmgl.lifecycle import make_quarantine_record
|
|
14
|
+
from cmgl.models import (
|
|
15
|
+
AdapterOperationReceipt,
|
|
16
|
+
AdapterOperationStatus,
|
|
17
|
+
AdmissionDecision,
|
|
18
|
+
BackendName,
|
|
19
|
+
ExternalMemoryRef,
|
|
20
|
+
GovernanceReceiptBundle,
|
|
21
|
+
MemoryEvent,
|
|
22
|
+
)
|
|
23
|
+
from cmgl.pipeline import PromotionPipelineResult
|
|
24
|
+
from cmgl.time import now_utc
|
|
25
|
+
|
|
26
|
+
_EXTERNAL_ID_KEYS = (
|
|
27
|
+
"id",
|
|
28
|
+
"memory_id",
|
|
29
|
+
"uuid",
|
|
30
|
+
"episode_uuid",
|
|
31
|
+
"episode_id",
|
|
32
|
+
"node_uuid",
|
|
33
|
+
"edge_uuid",
|
|
34
|
+
"source_node_uuid",
|
|
35
|
+
"target_node_uuid",
|
|
36
|
+
)
|
|
37
|
+
_EXTERNAL_UPDATE_KEYS = (
|
|
38
|
+
"memory_update_id",
|
|
39
|
+
"update_id",
|
|
40
|
+
"revision_id",
|
|
41
|
+
"version",
|
|
42
|
+
"updated_at",
|
|
43
|
+
"created_at",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def external_ref_from_result(
|
|
48
|
+
result: Any,
|
|
49
|
+
*,
|
|
50
|
+
event: MemoryEvent,
|
|
51
|
+
backend: BackendName | str,
|
|
52
|
+
namespace: str | None = None,
|
|
53
|
+
external_id: str | None = None,
|
|
54
|
+
external_update_id: str | None = None,
|
|
55
|
+
metadata: Mapping[str, Any] | None = None,
|
|
56
|
+
) -> ExternalMemoryRef:
|
|
57
|
+
"""Create a stable CMGL-to-external-memory binding from an SDK result."""
|
|
58
|
+
|
|
59
|
+
mapping = object_to_mapping(result)
|
|
60
|
+
payload_digest = sha256_digest(_jsonable(result))
|
|
61
|
+
resolved_external_id = external_id or _first_present(mapping, _EXTERNAL_ID_KEYS)
|
|
62
|
+
resolved_update_id = external_update_id or _first_present(mapping, _EXTERNAL_UPDATE_KEYS)
|
|
63
|
+
body = {
|
|
64
|
+
"schema_version": "cmgl.external_memory_ref.v1",
|
|
65
|
+
"backend": backend,
|
|
66
|
+
"external_id": str(resolved_external_id or payload_digest),
|
|
67
|
+
"external_update_id": None if resolved_update_id is None else str(resolved_update_id),
|
|
68
|
+
"cmgl_memory_id": event.memory_id,
|
|
69
|
+
"cmgl_memory_update_id": event.memory_update_id,
|
|
70
|
+
"authority_scope": event.authority_scope,
|
|
71
|
+
"content_digest": event.content_digest,
|
|
72
|
+
"source_payload_digest": payload_digest,
|
|
73
|
+
"namespace": namespace,
|
|
74
|
+
"metadata": dict(metadata or {}),
|
|
75
|
+
"timestamp": now_utc(),
|
|
76
|
+
}
|
|
77
|
+
return ExternalMemoryRef(**body)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def make_adapter_operation_receipt(
|
|
81
|
+
*,
|
|
82
|
+
backend: BackendName | str,
|
|
83
|
+
operation: Literal["write", "update", "delete", "retrieve"],
|
|
84
|
+
event: MemoryEvent | None = None,
|
|
85
|
+
status: AdapterOperationStatus,
|
|
86
|
+
decision: AdmissionDecision,
|
|
87
|
+
external_ref: ExternalMemoryRef | None = None,
|
|
88
|
+
reason_codes: list[str] | None = None,
|
|
89
|
+
error: BaseException | None = None,
|
|
90
|
+
timestamp: datetime | None = None,
|
|
91
|
+
) -> AdapterOperationReceipt:
|
|
92
|
+
"""Build a digest-bound adapter operation receipt."""
|
|
93
|
+
|
|
94
|
+
body = {
|
|
95
|
+
"schema_version": "cmgl.adapter_operation_receipt.v1",
|
|
96
|
+
"operation_id": f"adapter-op:{uuid4()}",
|
|
97
|
+
"backend": backend,
|
|
98
|
+
"operation": operation,
|
|
99
|
+
"status": status,
|
|
100
|
+
"decision": decision,
|
|
101
|
+
"cmgl_memory_id": None if event is None else event.memory_id,
|
|
102
|
+
"cmgl_memory_update_id": None if event is None else event.memory_update_id,
|
|
103
|
+
"external_ref": external_ref,
|
|
104
|
+
"reason_codes": list(reason_codes or []),
|
|
105
|
+
"error_type": None if error is None else type(error).__name__,
|
|
106
|
+
"error_message": None if error is None else _safe_error_message(error),
|
|
107
|
+
"timestamp": timestamp or now_utc(),
|
|
108
|
+
}
|
|
109
|
+
return AdapterOperationReceipt(**body, receipt_digest=sha256_digest(body))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def success_reason(operation: Literal["write", "update", "delete", "retrieve"]) -> str:
|
|
113
|
+
return f"adapter.external_{operation}_succeeded"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def append_adapter_receipt(
|
|
117
|
+
layer: Any,
|
|
118
|
+
receipt: AdapterOperationReceipt,
|
|
119
|
+
*,
|
|
120
|
+
quarantine_on_failure: bool = True,
|
|
121
|
+
) -> str | None:
|
|
122
|
+
"""Append adapter operation evidence and optionally quarantine failures."""
|
|
123
|
+
|
|
124
|
+
layer.ledger.append_with_receipt(
|
|
125
|
+
"adapter_operation_receipt",
|
|
126
|
+
receipt,
|
|
127
|
+
persist_receipt=layer.config.ledger.persist_append_receipts,
|
|
128
|
+
)
|
|
129
|
+
if receipt.status != AdapterOperationStatus.FAILED or not quarantine_on_failure:
|
|
130
|
+
return None
|
|
131
|
+
quarantine = make_quarantine_record(
|
|
132
|
+
target=receipt,
|
|
133
|
+
target_type="adapter_operation_receipt",
|
|
134
|
+
reason_codes=["adapter.external_persistence_failed"],
|
|
135
|
+
)
|
|
136
|
+
record, _ = layer.ledger.append_with_receipt(
|
|
137
|
+
"quarantine_record",
|
|
138
|
+
quarantine,
|
|
139
|
+
persist_receipt=layer.config.ledger.persist_append_receipts,
|
|
140
|
+
)
|
|
141
|
+
return cast(str, record.record_digest)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def bundle_with_adapter_receipt(
|
|
145
|
+
layer: Any,
|
|
146
|
+
result: PromotionPipelineResult,
|
|
147
|
+
*,
|
|
148
|
+
adapter_receipt: AdapterOperationReceipt,
|
|
149
|
+
backend_result: Any | None = None,
|
|
150
|
+
quarantine_record_digest: str | None = None,
|
|
151
|
+
) -> GovernanceReceiptBundle:
|
|
152
|
+
bundle = GovernanceReceiptBundle.model_validate(
|
|
153
|
+
layer.receipt_bundle(
|
|
154
|
+
result,
|
|
155
|
+
backend_result=backend_result,
|
|
156
|
+
adapter_operation_receipt=adapter_receipt,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if quarantine_record_digest is None:
|
|
160
|
+
return bundle
|
|
161
|
+
body = bundle.model_dump()
|
|
162
|
+
body["quarantine_record_digest"] = quarantine_record_digest
|
|
163
|
+
body.pop("bundle_digest", None)
|
|
164
|
+
return GovernanceReceiptBundle(**body, bundle_digest=sha256_digest(body))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def has_successful_binding(layer: Any, memory_id: str) -> bool:
|
|
168
|
+
"""Return true when the ledger has a prior succeeded external binding."""
|
|
169
|
+
|
|
170
|
+
for record in layer.ledger.iter_records():
|
|
171
|
+
if record.record_type != "adapter_operation_receipt":
|
|
172
|
+
continue
|
|
173
|
+
payload = record.payload
|
|
174
|
+
if not isinstance(payload, Mapping):
|
|
175
|
+
continue
|
|
176
|
+
if payload.get("status") != AdapterOperationStatus.SUCCEEDED.value:
|
|
177
|
+
continue
|
|
178
|
+
external_ref = payload.get("external_ref")
|
|
179
|
+
if isinstance(external_ref, Mapping) and external_ref.get("cmgl_memory_id") == memory_id:
|
|
180
|
+
return True
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _first_present(mapping: Mapping[str, Any], keys: Sequence[str]) -> Any:
|
|
185
|
+
for key in keys:
|
|
186
|
+
value = mapping.get(key)
|
|
187
|
+
if value not in (None, ""):
|
|
188
|
+
return value
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _jsonable(value: Any) -> Any:
|
|
193
|
+
if isinstance(value, BaseModel):
|
|
194
|
+
return value.model_dump(mode="json")
|
|
195
|
+
if isinstance(value, Mapping):
|
|
196
|
+
return {str(key): _jsonable(item) for key, item in value.items()}
|
|
197
|
+
if isinstance(value, Sequence) and not isinstance(value, str | bytes):
|
|
198
|
+
return [_jsonable(item) for item in value]
|
|
199
|
+
if isinstance(value, datetime):
|
|
200
|
+
return value.isoformat()
|
|
201
|
+
if isinstance(value, Enum):
|
|
202
|
+
return value.value
|
|
203
|
+
if isinstance(value, str | int | float | bool) or value is None:
|
|
204
|
+
return value
|
|
205
|
+
return str(value)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _safe_error_message(error: BaseException) -> str:
|
|
209
|
+
message = str(error).replace("\n", " ").strip()
|
|
210
|
+
if not message:
|
|
211
|
+
return type(error).__name__
|
|
212
|
+
return message[:240]
|