superlocalmemory 3.8.13 → 4.0.0
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.
- package/ATTRIBUTION.md +4 -4
- package/CHANGELOG.md +113 -121
- package/README.md +65 -63
- package/docs/pi-dev-integration.md +1 -1
- package/package.json +6 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +11 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +125 -11
- package/src/superlocalmemory/cli/daemon.py +5 -1
- package/src/superlocalmemory/cli/main.py +35 -2
- package/src/superlocalmemory/cli/ops_cmd.py +281 -0
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/compliance/audit.py +65 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
- package/src/superlocalmemory/compliance/gdpr.py +416 -20
- package/src/superlocalmemory/compliance/retention.py +74 -22
- package/src/superlocalmemory/compliance/scheduler.py +78 -9
- package/src/superlocalmemory/core/actor_context.py +166 -0
- package/src/superlocalmemory/core/admission.py +549 -0
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
- package/src/superlocalmemory/core/config.py +202 -24
- package/src/superlocalmemory/core/consolidation_engine.py +13 -13
- package/src/superlocalmemory/core/context_cache.py +28 -0
- package/src/superlocalmemory/core/embeddings.py +64 -2
- package/src/superlocalmemory/core/engine.py +7 -2
- package/src/superlocalmemory/core/engine_ingestion.py +65 -3
- package/src/superlocalmemory/core/engine_wiring.py +36 -9
- package/src/superlocalmemory/core/ingest_policy.py +38 -0
- package/src/superlocalmemory/core/maintenance.py +255 -0
- package/src/superlocalmemory/core/modes.py +40 -13
- package/src/superlocalmemory/core/mutations.py +437 -44
- package/src/superlocalmemory/core/operation_policy.py +92 -0
- package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
- package/src/superlocalmemory/core/operation_request.py +127 -0
- package/src/superlocalmemory/core/ops_remediation.py +542 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -0
- package/src/superlocalmemory/core/remember_runtime.py +202 -4
- package/src/superlocalmemory/core/remote_mode.py +20 -5
- package/src/superlocalmemory/core/store_pipeline.py +150 -0
- package/src/superlocalmemory/core/topic_signature.py +19 -4
- package/src/superlocalmemory/core/transactions/__init__.py +78 -0
- package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
- package/src/superlocalmemory/core/transactions/erasure.py +825 -0
- package/src/superlocalmemory/core/transactions/manifest.py +255 -0
- package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
- package/src/superlocalmemory/core/transactions/obligations.py +272 -0
- package/src/superlocalmemory/core/transactions/owners.py +114 -0
- package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
- package/src/superlocalmemory/core/transactions/service.py +330 -0
- package/src/superlocalmemory/core/worker_pool.py +33 -5
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
- package/src/superlocalmemory/encoding/emotional.py +75 -14
- package/src/superlocalmemory/encoding/scene_builder.py +115 -13
- package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
- package/src/superlocalmemory/evolution/evolution_store.py +244 -4
- package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
- package/src/superlocalmemory/evolution/model_selection.py +18 -3
- package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
- package/src/superlocalmemory/evolution/skill_activator.py +270 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
- package/src/superlocalmemory/evolution/types.py +30 -8
- package/src/superlocalmemory/graph/cozo_backend.py +17 -9
- package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
- package/src/superlocalmemory/hooks/auto_recall.py +64 -30
- package/src/superlocalmemory/hooks/codex_assets.py +14 -1
- package/src/superlocalmemory/infra/backup.py +434 -7
- package/src/superlocalmemory/infra/process_reaper.py +18 -0
- package/src/superlocalmemory/infra/self_heal.py +401 -0
- package/src/superlocalmemory/learning/feedback.py +52 -9
- package/src/superlocalmemory/loops/engine.py +10 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/http_transport.py +30 -331
- package/src/superlocalmemory/mcp/profiles.py +5 -0
- package/src/superlocalmemory/mcp/resources.py +8 -0
- package/src/superlocalmemory/mcp/server.py +51 -4
- package/src/superlocalmemory/mcp/shared.py +19 -0
- package/src/superlocalmemory/mcp/tools_active.py +25 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
- package/src/superlocalmemory/mcp/tools_context.py +50 -8
- package/src/superlocalmemory/mcp/tools_core.py +69 -21
- package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
- package/src/superlocalmemory/mcp/tools_learning.py +21 -10
- package/src/superlocalmemory/mcp/tools_loops.py +29 -18
- package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
- package/src/superlocalmemory/mcp/tools_ops.py +115 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
- package/src/superlocalmemory/mcp/tools_v28.py +10 -3
- package/src/superlocalmemory/mcp/tools_v3.py +34 -14
- package/src/superlocalmemory/mcp/tools_v33.py +18 -33
- package/src/superlocalmemory/mesh/broker.py +124 -46
- package/src/superlocalmemory/mesh/broker_security.py +470 -0
- package/src/superlocalmemory/mesh/discovery.py +365 -0
- package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
- package/src/superlocalmemory/mesh/node_identity.py +97 -0
- package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
- package/src/superlocalmemory/mesh/remote_sync.py +511 -28
- package/src/superlocalmemory/mesh/state_sync.py +286 -0
- package/src/superlocalmemory/optimize/config/store.py +45 -0
- package/src/superlocalmemory/parameterization/cross_project.py +12 -0
- package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
- package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
- package/src/superlocalmemory/retrieval/ann_index.py +5 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
- package/src/superlocalmemory/retrieval/engine.py +19 -4
- package/src/superlocalmemory/retrieval/fusion.py +4 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
- package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
- package/src/superlocalmemory/retrieval/reranker.py +32 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
- package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
- package/src/superlocalmemory/retrieval/vector_store.py +214 -8
- package/src/superlocalmemory/server/api.py +5 -5
- package/src/superlocalmemory/server/egress_policy.py +258 -0
- package/src/superlocalmemory/server/rbac_enforce.py +32 -0
- package/src/superlocalmemory/server/route_mutations.py +20 -0
- package/src/superlocalmemory/server/routes/compliance.py +153 -7
- package/src/superlocalmemory/server/routes/data_io.py +43 -2
- package/src/superlocalmemory/server/routes/events.py +15 -0
- package/src/superlocalmemory/server/routes/memories.py +56 -3
- package/src/superlocalmemory/server/routes/mesh.py +82 -1
- package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
- package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
- package/src/superlocalmemory/server/routes/v3_api.py +50 -27
- package/src/superlocalmemory/server/routes/ws.py +86 -0
- package/src/superlocalmemory/server/ui.py +6 -6
- package/src/superlocalmemory/server/unified_daemon.py +942 -119
- package/src/superlocalmemory/storage/_migration_internals.py +568 -0
- package/src/superlocalmemory/storage/_schema_version.py +110 -0
- package/src/superlocalmemory/storage/database.py +329 -24
- package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
- package/src/superlocalmemory/storage/erasure_fence.py +45 -0
- package/src/superlocalmemory/storage/generation_fence.py +63 -0
- package/src/superlocalmemory/storage/migration_runner.py +140 -417
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
- package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
- package/src/superlocalmemory/storage/schema.py +67 -0
- package/src/superlocalmemory/storage/write_coordinator.py +125 -0
- package/src/superlocalmemory/trust/scorer.py +28 -4
- package/src/superlocalmemory/ui/index.html +14 -3
- package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
- package/src/superlocalmemory/ui/js/brain.js +6 -4
- package/src/superlocalmemory/ui/js/compliance.js +66 -12
- package/src/superlocalmemory/ui/js/dashboard.js +13 -3
- package/src/superlocalmemory/ui/js/feedback.js +8 -2
- package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
- package/src/superlocalmemory/ui/js/modal.js +272 -5
- package/src/superlocalmemory/ui/js/od-backup.js +9 -2
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
- package/src/superlocalmemory/ui/js/od-operations.js +154 -23
- package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
- package/src/superlocalmemory/ui/js/od-team.js +9 -2
- package/src/superlocalmemory/ui/js/optimize.js +13 -16
- package/src/superlocalmemory/ui/js/profiles.js +7 -3
- package/src/superlocalmemory/ui/js/settings.js +7 -1
- package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
- package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
- package/src/superlocalmemory/cli/post_install.py +0 -114
- package/src/superlocalmemory/core/clock_monitor.py +0 -45
- package/src/superlocalmemory/core/db_pool.py +0 -80
- package/src/superlocalmemory/core/error_catalog.py +0 -113
- package/src/superlocalmemory/core/loop_watchdog.py +0 -56
- package/src/superlocalmemory/core/priority_queue.py +0 -61
- package/src/superlocalmemory/core/pruning_engine.py +0 -216
- package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
- package/src/superlocalmemory/core/slmignore.py +0 -125
- package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
- package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
- package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
- package/src/superlocalmemory/storage/access_control.py +0 -182
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import Any, Mapping, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObligationKind(StrEnum):
|
|
13
|
+
APPLY = "apply"
|
|
14
|
+
ERASE = "erase"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ObligationState(StrEnum):
|
|
18
|
+
PENDING = "pending"
|
|
19
|
+
APPLIED = "applied"
|
|
20
|
+
VERIFIED = "verified"
|
|
21
|
+
FAILED = "failed"
|
|
22
|
+
COMPENSATED = "compensated"
|
|
23
|
+
ERASED = "erased"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_TERMINAL_SUCCESS = frozenset({ObligationState.VERIFIED, ObligationState.ERASED})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_terminal_success(state: ObligationState) -> bool:
|
|
30
|
+
return state in _TERMINAL_SUCCESS
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _freeze(mapping: Mapping[str, Any] | None) -> Mapping[str, Any]:
|
|
34
|
+
return MappingProxyType(dict(mapping or {}))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class OperationContext:
|
|
39
|
+
operation_id: str
|
|
40
|
+
profile_id: str
|
|
41
|
+
subject_id: str
|
|
42
|
+
revision: int = 0
|
|
43
|
+
generation: int = 0
|
|
44
|
+
fact_ids: tuple[str, ...] = ()
|
|
45
|
+
payload: Mapping[str, Any] = field(default_factory=lambda: MappingProxyType({}))
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
if not self.operation_id:
|
|
49
|
+
raise ValueError("operation_id is required")
|
|
50
|
+
if not self.profile_id:
|
|
51
|
+
raise ValueError("profile_id is required")
|
|
52
|
+
if not self.subject_id:
|
|
53
|
+
raise ValueError("subject_id is required")
|
|
54
|
+
object.__setattr__(self, "fact_ids", tuple(self.fact_ids))
|
|
55
|
+
object.__setattr__(self, "payload", _freeze(self.payload))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class OwnerResult:
|
|
60
|
+
owner: str
|
|
61
|
+
ok: bool
|
|
62
|
+
checksum: str | None = None
|
|
63
|
+
detail: Mapping[str, Any] = field(default_factory=lambda: MappingProxyType({}))
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
object.__setattr__(self, "detail", _freeze(self.detail))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True, slots=True)
|
|
70
|
+
class OwnerErasureProof:
|
|
71
|
+
owner: str
|
|
72
|
+
erased: bool
|
|
73
|
+
checksum: str | None = None
|
|
74
|
+
detail: Mapping[str, Any] = field(default_factory=lambda: MappingProxyType({}))
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
object.__setattr__(self, "detail", _freeze(self.detail))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True, slots=True)
|
|
81
|
+
class OwnerHealth:
|
|
82
|
+
owner: str
|
|
83
|
+
healthy: bool
|
|
84
|
+
detail: str = ""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@runtime_checkable
|
|
88
|
+
class ProjectionOwner(Protocol):
|
|
89
|
+
@property
|
|
90
|
+
def name(self) -> str: ...
|
|
91
|
+
|
|
92
|
+
def apply(self, context: OperationContext) -> OwnerResult: ...
|
|
93
|
+
|
|
94
|
+
def verify(self, context: OperationContext) -> OwnerResult: ...
|
|
95
|
+
|
|
96
|
+
def compensate(self, context: OperationContext) -> OwnerResult: ...
|
|
97
|
+
|
|
98
|
+
def erase(self, context: OperationContext) -> OwnerErasureProof: ...
|
|
99
|
+
|
|
100
|
+
def prove_erased(self, context: OperationContext) -> OwnerErasureProof: ...
|
|
101
|
+
|
|
102
|
+
def health(self) -> OwnerHealth: ...
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = [
|
|
106
|
+
"ObligationKind",
|
|
107
|
+
"ObligationState",
|
|
108
|
+
"OperationContext",
|
|
109
|
+
"OwnerErasureProof",
|
|
110
|
+
"OwnerHealth",
|
|
111
|
+
"OwnerResult",
|
|
112
|
+
"ProjectionOwner",
|
|
113
|
+
"is_terminal_success",
|
|
114
|
+
]
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib as _hashlib
|
|
7
|
+
import hmac as _hmac
|
|
8
|
+
import json
|
|
9
|
+
import sqlite3
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from superlocalmemory.core.transactions.manifest import (
|
|
13
|
+
MANIFEST_V1,
|
|
14
|
+
MANIFEST_V2,
|
|
15
|
+
CompletionManifest,
|
|
16
|
+
ManifestState,
|
|
17
|
+
OwnerEvidence,
|
|
18
|
+
build_evidence,
|
|
19
|
+
compute_envelope_hash,
|
|
20
|
+
compute_envelope_hmac,
|
|
21
|
+
derive_state,
|
|
22
|
+
evidence_json,
|
|
23
|
+
hash_envelope_fields,
|
|
24
|
+
verify_envelope_hmac,
|
|
25
|
+
)
|
|
26
|
+
from superlocalmemory.core.transactions.obligations import ObligationLedger
|
|
27
|
+
from superlocalmemory.core.transactions.owners import ObligationKind, ObligationState
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Reconciler:
|
|
31
|
+
def __init__(self, ledger: ObligationLedger | None = None) -> None:
|
|
32
|
+
self._ledger = ledger or ObligationLedger()
|
|
33
|
+
|
|
34
|
+
def reconcile(
|
|
35
|
+
self,
|
|
36
|
+
conn: sqlite3.Connection,
|
|
37
|
+
operation_id: str,
|
|
38
|
+
profile_id: str,
|
|
39
|
+
*,
|
|
40
|
+
canonical_committed: bool | None = None,
|
|
41
|
+
) -> CompletionManifest:
|
|
42
|
+
committed = self._resolve_canonical(conn, operation_id, canonical_committed)
|
|
43
|
+
obligations = self._ledger.fetch(conn, operation_id)
|
|
44
|
+
evidence = build_evidence(obligations)
|
|
45
|
+
state, all_met = derive_state(obligations, canonical_committed=committed)
|
|
46
|
+
|
|
47
|
+
version = _manifest_version_supported(conn)
|
|
48
|
+
if version >= MANIFEST_V2:
|
|
49
|
+
manifest_hash = compute_envelope_hmac(
|
|
50
|
+
operation_id=operation_id,
|
|
51
|
+
profile_id=profile_id,
|
|
52
|
+
state=state,
|
|
53
|
+
all_met=all_met,
|
|
54
|
+
obligation_count=len(obligations),
|
|
55
|
+
evidence=evidence,
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
manifest_hash = compute_envelope_hash(
|
|
59
|
+
operation_id=operation_id,
|
|
60
|
+
profile_id=profile_id,
|
|
61
|
+
state=state,
|
|
62
|
+
all_met=all_met,
|
|
63
|
+
obligation_count=len(obligations),
|
|
64
|
+
evidence=evidence,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
payload = evidence_json(evidence)
|
|
68
|
+
now = time.time()
|
|
69
|
+
existing = conn.execute(
|
|
70
|
+
"SELECT created_at FROM completion_manifests WHERE operation_id = ?",
|
|
71
|
+
(operation_id,),
|
|
72
|
+
).fetchone()
|
|
73
|
+
created_at = float(existing[0]) if existing is not None else now
|
|
74
|
+
|
|
75
|
+
if version >= MANIFEST_V2:
|
|
76
|
+
conn.execute(
|
|
77
|
+
"INSERT INTO completion_manifests "
|
|
78
|
+
"(operation_id, profile_id, state, all_met, obligation_count, "
|
|
79
|
+
"owner_evidence_json, manifest_hash, manifest_version, created_at, updated_at) "
|
|
80
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
|
|
81
|
+
"ON CONFLICT(operation_id) DO UPDATE SET "
|
|
82
|
+
"profile_id = excluded.profile_id, "
|
|
83
|
+
"state = excluded.state, "
|
|
84
|
+
"all_met = excluded.all_met, "
|
|
85
|
+
"obligation_count = excluded.obligation_count, "
|
|
86
|
+
"owner_evidence_json = excluded.owner_evidence_json, "
|
|
87
|
+
"manifest_hash = excluded.manifest_hash, "
|
|
88
|
+
"manifest_version = excluded.manifest_version, "
|
|
89
|
+
"updated_at = excluded.updated_at",
|
|
90
|
+
(
|
|
91
|
+
operation_id, profile_id, str(state), 1 if all_met else 0,
|
|
92
|
+
len(obligations), payload, manifest_hash, MANIFEST_V2,
|
|
93
|
+
created_at, now,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
conn.execute(
|
|
98
|
+
"INSERT INTO completion_manifests "
|
|
99
|
+
"(operation_id, profile_id, state, all_met, obligation_count, "
|
|
100
|
+
"owner_evidence_json, manifest_hash, created_at, updated_at) "
|
|
101
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) "
|
|
102
|
+
"ON CONFLICT(operation_id) DO UPDATE SET "
|
|
103
|
+
"profile_id = excluded.profile_id, "
|
|
104
|
+
"state = excluded.state, "
|
|
105
|
+
"all_met = excluded.all_met, "
|
|
106
|
+
"obligation_count = excluded.obligation_count, "
|
|
107
|
+
"owner_evidence_json = excluded.owner_evidence_json, "
|
|
108
|
+
"manifest_hash = excluded.manifest_hash, "
|
|
109
|
+
"updated_at = excluded.updated_at",
|
|
110
|
+
(
|
|
111
|
+
operation_id, profile_id, str(state), 1 if all_met else 0,
|
|
112
|
+
len(obligations), payload, manifest_hash, created_at, now,
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return CompletionManifest(
|
|
117
|
+
operation_id=operation_id,
|
|
118
|
+
profile_id=profile_id,
|
|
119
|
+
state=state,
|
|
120
|
+
all_met=all_met,
|
|
121
|
+
obligation_count=len(obligations),
|
|
122
|
+
owner_evidence=evidence,
|
|
123
|
+
manifest_hash=manifest_hash,
|
|
124
|
+
created_at=created_at,
|
|
125
|
+
updated_at=now,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def fetch_manifest(
|
|
129
|
+
self, conn: sqlite3.Connection, operation_id: str,
|
|
130
|
+
) -> CompletionManifest | None:
|
|
131
|
+
row = conn.execute(
|
|
132
|
+
"SELECT operation_id, profile_id, state, all_met, obligation_count, "
|
|
133
|
+
"owner_evidence_json, manifest_hash, created_at, updated_at "
|
|
134
|
+
"FROM completion_manifests WHERE operation_id = ?",
|
|
135
|
+
(operation_id,),
|
|
136
|
+
).fetchone()
|
|
137
|
+
if row is None:
|
|
138
|
+
return None
|
|
139
|
+
return CompletionManifest(
|
|
140
|
+
operation_id=row[0],
|
|
141
|
+
profile_id=row[1],
|
|
142
|
+
state=ManifestState(row[2]),
|
|
143
|
+
all_met=bool(row[3]),
|
|
144
|
+
obligation_count=int(row[4]),
|
|
145
|
+
owner_evidence=_evidence_from_json(row[5]),
|
|
146
|
+
manifest_hash=row[6],
|
|
147
|
+
created_at=float(row[7]),
|
|
148
|
+
updated_at=float(row[8]),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def verify_manifest(
|
|
152
|
+
self, conn: sqlite3.Connection, operation_id: str,
|
|
153
|
+
) -> bool:
|
|
154
|
+
row = conn.execute(
|
|
155
|
+
"SELECT operation_id, profile_id, state, all_met, obligation_count, "
|
|
156
|
+
"owner_evidence_json, manifest_hash "
|
|
157
|
+
"FROM completion_manifests WHERE operation_id = ?",
|
|
158
|
+
(operation_id,),
|
|
159
|
+
).fetchone()
|
|
160
|
+
if row is None:
|
|
161
|
+
return False
|
|
162
|
+
try:
|
|
163
|
+
evidence_dicts = json.loads(row[5])
|
|
164
|
+
if not isinstance(evidence_dicts, list):
|
|
165
|
+
return False
|
|
166
|
+
except (TypeError, json.JSONDecodeError):
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
db_version = _manifest_version_supported(conn)
|
|
170
|
+
row_version = _manifest_row_version(conn, operation_id)
|
|
171
|
+
|
|
172
|
+
if db_version >= MANIFEST_V2:
|
|
173
|
+
# On M037-capable DBs NEVER accept the unkeyed v1 SHA path —
|
|
174
|
+
# an attacker can write manifest_version=1 with a valid SHA of
|
|
175
|
+
# mutated content. Any v1 row here is either a downgrade attack
|
|
176
|
+
# or a legacy row that hasn't been re-sealed; both are rejected.
|
|
177
|
+
if row_version < MANIFEST_V2:
|
|
178
|
+
return False
|
|
179
|
+
return verify_envelope_hmac(
|
|
180
|
+
operation_id=row[0],
|
|
181
|
+
profile_id=row[1],
|
|
182
|
+
state=row[2],
|
|
183
|
+
all_met=bool(row[3]),
|
|
184
|
+
obligation_count=int(row[4]),
|
|
185
|
+
evidence_dicts=evidence_dicts,
|
|
186
|
+
stored_mac=row[6],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# v1 path (M037 absent) — use constant-time comparison to prevent
|
|
190
|
+
# timing oracles on old SHA256 hex digests.
|
|
191
|
+
recomputed = hash_envelope_fields(
|
|
192
|
+
operation_id=row[0],
|
|
193
|
+
profile_id=row[1],
|
|
194
|
+
state=row[2],
|
|
195
|
+
all_met=bool(row[3]),
|
|
196
|
+
obligation_count=int(row[4]),
|
|
197
|
+
evidence_dicts=evidence_dicts,
|
|
198
|
+
)
|
|
199
|
+
stored = row[6] or ""
|
|
200
|
+
return _hmac.compare_digest(
|
|
201
|
+
recomputed.encode("utf-8"), stored.encode("utf-8")
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _resolve_canonical(
|
|
205
|
+
self,
|
|
206
|
+
conn: sqlite3.Connection,
|
|
207
|
+
operation_id: str,
|
|
208
|
+
canonical_committed: bool | None,
|
|
209
|
+
) -> bool:
|
|
210
|
+
if canonical_committed is not None:
|
|
211
|
+
return canonical_committed
|
|
212
|
+
table = conn.execute(
|
|
213
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' "
|
|
214
|
+
"AND name='ingestion_operations'"
|
|
215
|
+
).fetchone()
|
|
216
|
+
if table is None:
|
|
217
|
+
return True
|
|
218
|
+
row = conn.execute(
|
|
219
|
+
"SELECT state FROM ingestion_operations WHERE operation_id = ? LIMIT 1",
|
|
220
|
+
(operation_id,),
|
|
221
|
+
).fetchone()
|
|
222
|
+
if row is None:
|
|
223
|
+
return False
|
|
224
|
+
return row[0] in ("queryable", "enriching", "complete")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _manifest_version_supported(conn: sqlite3.Connection) -> int:
|
|
228
|
+
"""Return the highest manifest version this DB supports.
|
|
229
|
+
|
|
230
|
+
Returns MANIFEST_V2 if the ``manifest_version`` column exists (M037 applied),
|
|
231
|
+
otherwise MANIFEST_V1.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
cols = {row[1] for row in conn.execute("PRAGMA table_info(completion_manifests)").fetchall()}
|
|
235
|
+
return MANIFEST_V2 if "manifest_version" in cols else MANIFEST_V1
|
|
236
|
+
except Exception: # noqa: BLE001
|
|
237
|
+
# Fail-closed: PRAGMA failure must not silently route verification through
|
|
238
|
+
# the unkeyed-SHA v1 path — that allows a downgrade-forgery attack where
|
|
239
|
+
# an attacker resets manifest_version=1 and recomputes a valid v1 SHA.
|
|
240
|
+
return MANIFEST_V2
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _manifest_row_version(conn: sqlite3.Connection, operation_id: str) -> int:
|
|
244
|
+
"""Read the manifest_version column for an existing row.
|
|
245
|
+
|
|
246
|
+
Falls back to MANIFEST_V1 when the column does not exist.
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
cols = {row[1] for row in conn.execute("PRAGMA table_info(completion_manifests)").fetchall()}
|
|
250
|
+
if "manifest_version" not in cols:
|
|
251
|
+
return MANIFEST_V1
|
|
252
|
+
row = conn.execute(
|
|
253
|
+
"SELECT manifest_version FROM completion_manifests WHERE operation_id = ?",
|
|
254
|
+
(operation_id,),
|
|
255
|
+
).fetchone()
|
|
256
|
+
return int(row[0]) if row else MANIFEST_V1
|
|
257
|
+
except Exception: # noqa: BLE001
|
|
258
|
+
return MANIFEST_V1
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _evidence_from_json(payload: str) -> tuple[OwnerEvidence, ...]:
|
|
262
|
+
try:
|
|
263
|
+
rows = json.loads(payload)
|
|
264
|
+
except (TypeError, json.JSONDecodeError):
|
|
265
|
+
return ()
|
|
266
|
+
if not isinstance(rows, list):
|
|
267
|
+
return ()
|
|
268
|
+
evidence: list[OwnerEvidence] = []
|
|
269
|
+
for entry in rows:
|
|
270
|
+
if not isinstance(entry, dict):
|
|
271
|
+
continue
|
|
272
|
+
try:
|
|
273
|
+
evidence.append(OwnerEvidence(
|
|
274
|
+
owner=str(entry["owner"]),
|
|
275
|
+
kind=ObligationKind(entry["kind"]),
|
|
276
|
+
state=ObligationState(entry["state"]),
|
|
277
|
+
revision=int(entry["revision"]),
|
|
278
|
+
checksum=str(entry["checksum"]),
|
|
279
|
+
))
|
|
280
|
+
except (KeyError, ValueError):
|
|
281
|
+
continue
|
|
282
|
+
return tuple(evidence)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
__all__ = ["Reconciler"]
|