generated from coulomb/repo-seed
feat(memory): add audit export surface
This commit is contained in:
@@ -21,10 +21,10 @@ with `markitect-tool`.
|
||||
- `InMemoryMemoryGraphRepository` provides deterministic local storage for
|
||||
tests and future service wiring.
|
||||
- `MemoryRuntimeService.import_markitect_graph()` persists an imported graph and
|
||||
can attach an audit event when an `OperationContext` is supplied.
|
||||
persists an audit event when an `OperationContext` is supplied.
|
||||
- `MemoryRuntimeService.query_memory()` retrieves graph nodes through a scope
|
||||
policy check plus per-node policy checks, returns source-grounded context
|
||||
items, preserves safe denied diagnostics, and emits an audit event in the
|
||||
items, preserves safe denied diagnostics, and persists an audit event in the
|
||||
result envelope.
|
||||
- `MemoryRuntimeService.apply_retention()` marks stale memories for review or
|
||||
transitions old memories to `delete_requested` without physical deletion.
|
||||
@@ -40,6 +40,12 @@ with `markitect-tool`.
|
||||
- `MemoryRuntimeService.export_context_package_inputs()` emits
|
||||
Markitect-compatible context package input envelopes without invoking the
|
||||
Markitect compiler.
|
||||
- `MemoryGraphRepository` persists memory audit events separately from
|
||||
Markitect memory events, allowing operations to be queried by graph,
|
||||
correlation id, and operation.
|
||||
- `MemoryRuntimeService.export_runtime_envelope()` emits a portable runtime
|
||||
envelope containing graph nodes, edges, memory events, and audit traces with
|
||||
operation id, actor, policy decision, and filter metadata.
|
||||
|
||||
## Boundary
|
||||
|
||||
@@ -53,6 +59,7 @@ with `markitect-tool`.
|
||||
|
||||
- runtime ids and persistence
|
||||
- append-only event storage
|
||||
- durable audit event storage
|
||||
- permission-aware retrieval and context assembly
|
||||
- retention, refresh, compaction, review gates, and audit behavior
|
||||
- agent-safe update plans and Markitect-compatible export envelopes
|
||||
|
||||
@@ -148,6 +148,8 @@ from .services import (
|
||||
MemoryRetrievalItem,
|
||||
MemoryRetrievalResult,
|
||||
MemoryRetentionRequest,
|
||||
MemoryRuntimeExportRequest,
|
||||
MemoryRuntimeExportResult,
|
||||
MemoryRuntimeService,
|
||||
MemoryUpdatePlan,
|
||||
MemoryUpdateRequest,
|
||||
@@ -288,6 +290,8 @@ __all__ = [
|
||||
"MemoryRetrievalItem",
|
||||
"MemoryRetrievalResult",
|
||||
"MemoryRetentionRequest",
|
||||
"MemoryRuntimeExportRequest",
|
||||
"MemoryRuntimeExportResult",
|
||||
"MemoryRuntimeService",
|
||||
"MemorySourceSpan",
|
||||
"MemoryUpdatePlan",
|
||||
|
||||
@@ -21,6 +21,8 @@ from .memory_service import (
|
||||
MemoryRetrievalItem,
|
||||
MemoryRetrievalResult,
|
||||
MemoryRetentionRequest,
|
||||
MemoryRuntimeExportRequest,
|
||||
MemoryRuntimeExportResult,
|
||||
MemoryRuntimeService,
|
||||
MemoryUpdatePlan,
|
||||
MemoryUpdateRequest,
|
||||
@@ -85,6 +87,8 @@ __all__ = [
|
||||
"MemoryRetrievalItem",
|
||||
"MemoryRetrievalResult",
|
||||
"MemoryRetentionRequest",
|
||||
"MemoryRuntimeExportRequest",
|
||||
"MemoryRuntimeExportResult",
|
||||
"MemoryRuntimeService",
|
||||
"MemoryUpdatePlan",
|
||||
"MemoryUpdateRequest",
|
||||
|
||||
@@ -537,18 +537,20 @@ class MemoryRuntimeService:
|
||||
|
||||
audit_event = None
|
||||
if context:
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.import_markitect_graph",
|
||||
f"memory-graph:{imported.graph_id}",
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"contract_graph_id": imported.contract_graph_id,
|
||||
"profile_id": imported.profile.profile_id if imported.profile else None,
|
||||
"nodes": len(imported.nodes),
|
||||
"edges": len(imported.edges),
|
||||
"events": len(imported.events),
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.import_markitect_graph",
|
||||
f"memory-graph:{imported.graph_id}",
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"contract_graph_id": imported.contract_graph_id,
|
||||
"profile_id": imported.profile.profile_id if imported.profile else None,
|
||||
"nodes": len(imported.nodes),
|
||||
"edges": len(imported.edges),
|
||||
"events": len(imported.events),
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryGraphImportSummary(
|
||||
graph_id=imported.graph_id,
|
||||
@@ -588,13 +590,15 @@ class MemoryRuntimeService:
|
||||
resource_metadata={"query": request.to_dict()},
|
||||
)
|
||||
if not scope_decision.allowed:
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.query",
|
||||
scope_resource,
|
||||
AuditOutcome.DENIED,
|
||||
context,
|
||||
policy_decision=scope_decision,
|
||||
details={"query": request.to_dict()},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.query",
|
||||
scope_resource,
|
||||
AuditOutcome.DENIED,
|
||||
context,
|
||||
policy_decision=scope_decision,
|
||||
details={"query": request.to_dict()},
|
||||
)
|
||||
)
|
||||
return MemoryRetrievalResult(
|
||||
request=request,
|
||||
@@ -653,18 +657,20 @@ class MemoryRuntimeService:
|
||||
total = len(items)
|
||||
page = tuple(items[request.offset : request.offset + request.limit])
|
||||
outcome = AuditOutcome.PARTIAL if denied_count else AuditOutcome.SUCCESS
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.query",
|
||||
scope_resource,
|
||||
outcome,
|
||||
context,
|
||||
policy_decision=scope_decision,
|
||||
details={
|
||||
"query": request.to_dict(),
|
||||
"matched_count": len(nodes),
|
||||
"permission_filtered_count": denied_count,
|
||||
"result_count": len(page),
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.query",
|
||||
scope_resource,
|
||||
outcome,
|
||||
context,
|
||||
policy_decision=scope_decision,
|
||||
details={
|
||||
"query": request.to_dict(),
|
||||
"matched_count": len(nodes),
|
||||
"permission_filtered_count": denied_count,
|
||||
"result_count": len(page),
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryRetrievalResult(
|
||||
request=request,
|
||||
@@ -737,16 +743,18 @@ class MemoryRuntimeService:
|
||||
node_updates=[update.to_dict() for update in updates],
|
||||
metadata={"request": request.to_dict()},
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.retention.apply",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"updated_nodes": len(updates),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.retention.apply",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"updated_nodes": len(updates),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryLifecycleResult(
|
||||
operation="memory.retention.apply",
|
||||
@@ -797,16 +805,18 @@ class MemoryRuntimeService:
|
||||
node_updates=[update.to_dict() for update in updates],
|
||||
metadata={"request": request.to_dict()},
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.refresh",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"updated_nodes": len(updates),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.refresh",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"updated_nodes": len(updates),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryLifecycleResult(
|
||||
operation="memory.refresh",
|
||||
@@ -891,17 +901,19 @@ class MemoryRuntimeService:
|
||||
"operation_id": operation_id,
|
||||
},
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.compact",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"summary_node_id": summary.node_id,
|
||||
"source_nodes": len(nodes),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.compact",
|
||||
f"memory-graph:{request.graph_id}",
|
||||
AuditOutcome.DRY_RUN if request.dry_run else AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"summary_node_id": summary.node_id,
|
||||
"source_nodes": len(nodes),
|
||||
"event_id": event.event_id if event else None,
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryLifecycleResult(
|
||||
operation="memory.compact",
|
||||
@@ -979,12 +991,14 @@ class MemoryRuntimeService:
|
||||
"Memory update requires explicit review approval before durable write.",
|
||||
details={"plan_id": plan.plan_id, "review_decision": review_decision},
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.REVIEW_REQUIRED,
|
||||
context,
|
||||
details={"plan_id": plan.plan_id, "review_decision": review_decision},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.REVIEW_REQUIRED,
|
||||
context,
|
||||
details={"plan_id": plan.plan_id, "review_decision": review_decision},
|
||||
)
|
||||
)
|
||||
return MemoryUpdateResult(
|
||||
plan=plan,
|
||||
@@ -1001,13 +1015,15 @@ class MemoryRuntimeService:
|
||||
)
|
||||
if not decision.allowed:
|
||||
diagnostic = _permission_denied_diagnostic(decision)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.DENIED,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={"plan_id": plan.plan_id},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.DENIED,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={"plan_id": plan.plan_id},
|
||||
)
|
||||
)
|
||||
return MemoryUpdateResult(plan=plan, audit_event=audit_event, diagnostics=(diagnostic,), success=False)
|
||||
for update in plan.planned_updates:
|
||||
@@ -1019,13 +1035,15 @@ class MemoryRuntimeService:
|
||||
node_updates=[_planned_update_event_payload(update) for update in plan.planned_updates],
|
||||
metadata={"plan_id": plan.plan_id, "review_decision": review_decision},
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={"plan_id": plan.plan_id, "applied_nodes": len(plan.planned_updates), "event_id": event.event_id},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.update.apply",
|
||||
f"memory-graph:{plan.request.graph_id}",
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={"plan_id": plan.plan_id, "applied_nodes": len(plan.planned_updates), "event_id": event.event_id},
|
||||
)
|
||||
)
|
||||
return MemoryUpdateResult(
|
||||
plan=plan,
|
||||
@@ -1050,16 +1068,18 @@ class MemoryRuntimeService:
|
||||
)
|
||||
package_input = _markitect_package_input(request, retrieval)
|
||||
outcome = AuditOutcome.PARTIAL if retrieval.metadata.get("permission_filtered_count") else AuditOutcome.SUCCESS
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.package.export",
|
||||
f"memory-graph:{request.query.graph_id or '*'}",
|
||||
outcome,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"items": len(package_input.get("items", ())),
|
||||
"permission_filtered_count": retrieval.metadata.get("permission_filtered_count", 0),
|
||||
},
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.package.export",
|
||||
f"memory-graph:{request.query.graph_id or '*'}",
|
||||
outcome,
|
||||
context,
|
||||
details={
|
||||
"request": request.to_dict(),
|
||||
"items": len(package_input.get("items", ())),
|
||||
"permission_filtered_count": retrieval.metadata.get("permission_filtered_count", 0),
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryPackageExportResult(
|
||||
request=request,
|
||||
@@ -1070,6 +1090,132 @@ class MemoryRuntimeService:
|
||||
diagnostics=retrieval.diagnostics,
|
||||
)
|
||||
|
||||
def export_runtime_envelope(
|
||||
self,
|
||||
request: MemoryRuntimeExportRequest,
|
||||
context: OperationContext,
|
||||
) -> MemoryRuntimeExportResult:
|
||||
diagnostics = _validate_runtime_export_request(request)
|
||||
if diagnostics:
|
||||
return MemoryRuntimeExportResult(
|
||||
request=request,
|
||||
correlation_id=context.correlation_id,
|
||||
diagnostics=tuple(diagnostics),
|
||||
success=False,
|
||||
)
|
||||
|
||||
target = f"memory-graph:{request.graph_id}"
|
||||
decision = self._authorize(
|
||||
context,
|
||||
"memory.runtime.export",
|
||||
target,
|
||||
resource_metadata={"request": request.to_dict()},
|
||||
)
|
||||
if not decision.allowed:
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.runtime.export",
|
||||
target,
|
||||
AuditOutcome.DENIED,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={"request": request.to_dict()},
|
||||
)
|
||||
)
|
||||
return MemoryRuntimeExportResult(
|
||||
request=request,
|
||||
correlation_id=context.correlation_id,
|
||||
audit_event=audit_event,
|
||||
diagnostics=(_permission_denied_diagnostic(decision),),
|
||||
success=False,
|
||||
)
|
||||
|
||||
operation_id = new_id("memexport")
|
||||
exported_at = utc_now().isoformat()
|
||||
all_nodes = self.repository.list_memory_nodes(graph_id=request.graph_id)
|
||||
nodes = _runtime_export_nodes(all_nodes, include_retired=request.include_retired)
|
||||
visible_node_ids = {str(node.node_id) for node in nodes}
|
||||
edges: list[MemoryEdgeRecord] = []
|
||||
if request.include_edges:
|
||||
edges = self.repository.list_memory_edges(graph_id=request.graph_id)
|
||||
if not request.include_retired:
|
||||
edges = [
|
||||
edge
|
||||
for edge in edges
|
||||
if edge.lifecycle not in _runtime_export_hidden_lifecycles()
|
||||
and edge.source_node_id in visible_node_ids
|
||||
and edge.target_node_id in visible_node_ids
|
||||
]
|
||||
events: list[MemoryEventRecord] = []
|
||||
if request.include_events:
|
||||
wanted_kinds = set(request.event_kinds)
|
||||
events = [
|
||||
event
|
||||
for event in self.repository.list_memory_events(graph_id=request.graph_id)
|
||||
if not wanted_kinds or event.kind in wanted_kinds
|
||||
]
|
||||
audit_events: list[AuditEvent] = []
|
||||
if request.include_audit_events:
|
||||
wanted_operations = set(request.operations)
|
||||
audit_events = [
|
||||
event
|
||||
for event in self.repository.list_memory_audit_events(
|
||||
graph_id=request.graph_id,
|
||||
correlation_id=request.correlation_id,
|
||||
)
|
||||
if not wanted_operations or event.operation in wanted_operations
|
||||
]
|
||||
export_nodes = nodes if request.include_nodes else []
|
||||
counts = {
|
||||
"nodes": len(export_nodes),
|
||||
"edges": len(edges),
|
||||
"events": len(events),
|
||||
"audit_events": len(audit_events),
|
||||
}
|
||||
envelope = {
|
||||
"schema_version": "kontextual.memory.runtime-export.v1",
|
||||
"operation_id": operation_id,
|
||||
"graph_id": request.graph_id,
|
||||
"exported_at": exported_at,
|
||||
"actor": context.actor.to_dict(),
|
||||
"correlation_id": context.correlation_id,
|
||||
"request": request.to_dict(),
|
||||
"nodes": [node.to_dict() for node in export_nodes],
|
||||
"edges": [edge.to_dict() for edge in edges],
|
||||
"events": [event.to_dict() for event in events],
|
||||
"audit_events": [event.to_dict() for event in audit_events],
|
||||
"metadata": {
|
||||
"counts": counts,
|
||||
"filters": {
|
||||
"event_kinds": list(request.event_kinds),
|
||||
"operations": list(request.operations),
|
||||
"correlation_id": request.correlation_id,
|
||||
"include_retired": request.include_retired,
|
||||
},
|
||||
},
|
||||
}
|
||||
audit_event = self._record_audit(
|
||||
AuditEvent.from_context(
|
||||
"memory.runtime.export",
|
||||
target,
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
policy_decision=decision,
|
||||
details={
|
||||
"operation_id": operation_id,
|
||||
"graph_id": request.graph_id,
|
||||
"request": request.to_dict(),
|
||||
"counts": counts,
|
||||
},
|
||||
)
|
||||
)
|
||||
return MemoryRuntimeExportResult(
|
||||
request=request,
|
||||
correlation_id=context.correlation_id,
|
||||
envelope=envelope,
|
||||
audit_event=audit_event,
|
||||
)
|
||||
|
||||
def _authorize(
|
||||
self,
|
||||
context: OperationContext,
|
||||
@@ -1097,6 +1243,9 @@ class MemoryRuntimeService:
|
||||
},
|
||||
)
|
||||
|
||||
def _record_audit(self, audit_event: AuditEvent) -> AuditEvent:
|
||||
return self.repository.save_memory_audit_event(audit_event)
|
||||
|
||||
def _append_lifecycle_event(
|
||||
self,
|
||||
event_kind: str,
|
||||
@@ -1133,6 +1282,25 @@ def _validate_query(request: MemoryQueryRequest) -> list[Diagnostic]:
|
||||
return diagnostics
|
||||
|
||||
|
||||
def _validate_runtime_export_request(request: MemoryRuntimeExportRequest) -> list[Diagnostic]:
|
||||
return _validate_node_selection_request(request.graph_id, ())
|
||||
|
||||
|
||||
def _runtime_export_nodes(
|
||||
nodes: list[MemoryNodeRecord],
|
||||
*,
|
||||
include_retired: bool,
|
||||
) -> list[MemoryNodeRecord]:
|
||||
if include_retired:
|
||||
return nodes
|
||||
hidden_lifecycles = _runtime_export_hidden_lifecycles()
|
||||
return [node for node in nodes if node.lifecycle not in hidden_lifecycles]
|
||||
|
||||
|
||||
def _runtime_export_hidden_lifecycles() -> set[LifecycleState]:
|
||||
return {LifecycleState.RETIRED, LifecycleState.DELETE_REQUESTED, LifecycleState.DELETED}
|
||||
|
||||
|
||||
def _node_policy_metadata(node: MemoryNodeRecord) -> dict[str, Any]:
|
||||
return {
|
||||
"node_id": node.node_id,
|
||||
|
||||
@@ -13,6 +13,7 @@ from kontextual_engine import (
|
||||
MemoryRefreshRequest,
|
||||
MemoryRetentionRequest,
|
||||
MemoryQueryRequest,
|
||||
MemoryRuntimeExportRequest,
|
||||
MemoryRuntimeService,
|
||||
MemoryUpdateRequest,
|
||||
OperationContext,
|
||||
@@ -73,6 +74,7 @@ def test_memory_runtime_service_imports_contracts_and_reports_audit_context() ->
|
||||
assert summary.audit_event is not None
|
||||
assert summary.audit_event.actor_id == "agent-codex"
|
||||
assert summary.audit_event.correlation_id == "corr-memory"
|
||||
assert repo.get_memory_audit_event(summary.audit_event.event_id) == summary.audit_event
|
||||
assert repo.get_memory_profile(summary.profile_id).memory_kinds == (
|
||||
"reasoning",
|
||||
"knowledge",
|
||||
@@ -283,6 +285,53 @@ def test_memory_package_export_emits_markitect_context_package_inputs_without_de
|
||||
assert export.audit_event.operation == "memory.package.export"
|
||||
|
||||
|
||||
def test_memory_runtime_export_includes_persisted_audit_and_event_traces() -> None:
|
||||
repo = InMemoryMemoryGraphRepository()
|
||||
service = MemoryRuntimeService(repo)
|
||||
context = operation_context()
|
||||
summary = service.import_markitect_graph(_graph_contract(), context=context)
|
||||
|
||||
query = service.query_memory(MemoryQueryRequest(graph_id=summary.graph_id), context)
|
||||
retention = service.apply_retention(
|
||||
MemoryRetentionRequest(graph_id=summary.graph_id, stale_after_days=0),
|
||||
context,
|
||||
)
|
||||
audit_operations = {
|
||||
event.operation
|
||||
for event in repo.list_memory_audit_events(
|
||||
graph_id=summary.graph_id,
|
||||
correlation_id=context.correlation_id,
|
||||
)
|
||||
}
|
||||
export = service.export_runtime_envelope(
|
||||
MemoryRuntimeExportRequest(
|
||||
graph_id=summary.graph_id,
|
||||
event_kinds=("recorded", "retention"),
|
||||
operations=("memory.query", "memory.retention.apply"),
|
||||
),
|
||||
context,
|
||||
)
|
||||
|
||||
assert query.success is True
|
||||
assert retention.appended_events[0].kind == "retention"
|
||||
assert {"memory.import_markitect_graph", "memory.query", "memory.retention.apply"} <= audit_operations
|
||||
assert export.success is True
|
||||
assert export.envelope["schema_version"] == "kontextual.memory.runtime-export.v1"
|
||||
assert export.envelope["metadata"]["counts"] == {
|
||||
"nodes": 2,
|
||||
"edges": 1,
|
||||
"events": 2,
|
||||
"audit_events": 2,
|
||||
}
|
||||
assert {event["kind"] for event in export.envelope["events"]} == {"recorded", "retention"}
|
||||
assert {event["operation"] for event in export.envelope["audit_events"]} == {
|
||||
"memory.query",
|
||||
"memory.retention.apply",
|
||||
}
|
||||
assert export.audit_event is not None
|
||||
assert repo.get_memory_audit_event(export.audit_event.event_id) == export.audit_event
|
||||
|
||||
|
||||
def test_memory_runtime_service_rejects_invalid_edge_contracts() -> None:
|
||||
repo = InMemoryMemoryGraphRepository()
|
||||
service = MemoryRuntimeService(repo)
|
||||
|
||||
@@ -98,6 +98,12 @@ review gates for durable writes, source/policy explanations on planned node
|
||||
updates, and Markitect-compatible context package input export. The export
|
||||
method emits package inputs only; Markitect remains the package compiler.
|
||||
|
||||
The observability/export slice is implemented. Memory audit events are now
|
||||
persisted through the memory graph repository, queryable by graph, correlation,
|
||||
and operation. Runtime export envelopes include graph nodes, edges, memory
|
||||
events, audit traces, operation id, actor metadata, policy decisions, and
|
||||
filter metadata for portable inspection or handoff.
|
||||
|
||||
## P17.1 - Import and map Markitect memory contracts
|
||||
|
||||
```task
|
||||
@@ -215,7 +221,7 @@ Output: API contracts, tests, and agent-safe operation notes.
|
||||
|
||||
```task
|
||||
id: KONT-WP-0017-T006
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "54f51f87-6420-4170-913f-f6dae098fe71"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user