claude-smart 0.2.46 → 0.2.48
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any, Literal, Protocol, cast
|
|
7
|
+
|
|
8
|
+
from reflexio.models.api_schema.domain import AgentPlaybook, AgentPlaybookSourceWindow
|
|
9
|
+
from reflexio.models.api_schema.domain.governance import (
|
|
10
|
+
AuditEvent,
|
|
11
|
+
PurgeOperation,
|
|
12
|
+
PurgeOperationTarget,
|
|
13
|
+
SubjectBarrierStatus,
|
|
14
|
+
SubjectWriteBarrier,
|
|
15
|
+
)
|
|
16
|
+
from reflexio.server.services.storage.governance_validation import (
|
|
17
|
+
_CANONICAL_DELETE_TARGET_NAMES,
|
|
18
|
+
_PREPARE_PHASE,
|
|
19
|
+
_SNAPSHOT_TARGET_NAME,
|
|
20
|
+
_validate_governance_int_list,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
_LEGACY_AUDIT_REQUEST_REF = "reqref_v1_legacy_unknown"
|
|
24
|
+
|
|
25
|
+
_PURGE_OPERATION_TARGETS_TABLE_DDL = """
|
|
26
|
+
CREATE TABLE IF NOT EXISTS purge_operation_targets (
|
|
27
|
+
org_id TEXT NOT NULL,
|
|
28
|
+
purge_id TEXT NOT NULL,
|
|
29
|
+
target_name TEXT NOT NULL,
|
|
30
|
+
target_ref TEXT NOT NULL DEFAULT '',
|
|
31
|
+
phase TEXT NOT NULL,
|
|
32
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
33
|
+
detail TEXT,
|
|
34
|
+
deleted_count INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
error_detail TEXT,
|
|
36
|
+
started_at INTEGER,
|
|
37
|
+
completed_at INTEGER,
|
|
38
|
+
PRIMARY KEY (org_id, purge_id, target_name, target_ref, phase),
|
|
39
|
+
FOREIGN KEY (org_id, purge_id) REFERENCES purge_operations(org_id, purge_id) ON DELETE CASCADE
|
|
40
|
+
);
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
GOVERNANCE_DDL = f"""
|
|
44
|
+
CREATE TABLE IF NOT EXISTS audit_events (
|
|
45
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
org_id TEXT NOT NULL,
|
|
47
|
+
actor_type TEXT NOT NULL DEFAULT 'system',
|
|
48
|
+
actor_ref TEXT,
|
|
49
|
+
operation TEXT NOT NULL,
|
|
50
|
+
entity_type TEXT NOT NULL,
|
|
51
|
+
entity_id TEXT,
|
|
52
|
+
subject_ref TEXT,
|
|
53
|
+
request_ref TEXT NOT NULL,
|
|
54
|
+
idempotency_key TEXT,
|
|
55
|
+
status TEXT NOT NULL DEFAULT 'ok',
|
|
56
|
+
detail TEXT,
|
|
57
|
+
created_at INTEGER NOT NULL
|
|
58
|
+
);
|
|
59
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_events_org_idem
|
|
60
|
+
ON audit_events(org_id, idempotency_key)
|
|
61
|
+
WHERE idempotency_key IS NOT NULL;
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_audit_events_subject_created
|
|
63
|
+
ON audit_events(org_id, subject_ref, created_at, event_id);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_audit_events_org_created
|
|
65
|
+
ON audit_events(org_id, created_at, event_id);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS purge_operations (
|
|
68
|
+
org_id TEXT NOT NULL,
|
|
69
|
+
purge_id TEXT NOT NULL,
|
|
70
|
+
operation_type TEXT NOT NULL,
|
|
71
|
+
scope_type TEXT NOT NULL,
|
|
72
|
+
subject_ref TEXT,
|
|
73
|
+
request_ref TEXT NOT NULL,
|
|
74
|
+
idempotency_key TEXT NOT NULL,
|
|
75
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
76
|
+
error_code TEXT,
|
|
77
|
+
error_detail TEXT,
|
|
78
|
+
created_at INTEGER NOT NULL,
|
|
79
|
+
updated_at INTEGER NOT NULL,
|
|
80
|
+
completed_at INTEGER,
|
|
81
|
+
PRIMARY KEY (org_id, purge_id)
|
|
82
|
+
);
|
|
83
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_purge_operations_org_idem
|
|
84
|
+
ON purge_operations(org_id, idempotency_key);
|
|
85
|
+
|
|
86
|
+
{_PURGE_OPERATION_TARGETS_TABLE_DDL}
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_purge_targets_purge_phase
|
|
88
|
+
ON purge_operation_targets(org_id, purge_id, phase, status);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS subject_write_barriers (
|
|
91
|
+
org_id TEXT NOT NULL,
|
|
92
|
+
subject_ref TEXT NOT NULL,
|
|
93
|
+
purge_id TEXT NOT NULL,
|
|
94
|
+
status TEXT NOT NULL,
|
|
95
|
+
error_code TEXT,
|
|
96
|
+
error_detail TEXT,
|
|
97
|
+
created_at INTEGER NOT NULL,
|
|
98
|
+
updated_at INTEGER NOT NULL,
|
|
99
|
+
PRIMARY KEY (org_id, subject_ref),
|
|
100
|
+
CHECK (status IN ('erasing', 'erased', 'failed'))
|
|
101
|
+
);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_subject_write_barriers_org_purge
|
|
103
|
+
ON subject_write_barriers(org_id, purge_id);
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def init_governance_tables(conn: sqlite3.Connection) -> None:
|
|
108
|
+
_upgrade_legacy_purge_operation_targets_table(conn)
|
|
109
|
+
conn.executescript(GOVERNANCE_DDL)
|
|
110
|
+
_enforce_audit_request_ref_not_null(conn)
|
|
111
|
+
_ensure_governance_subject_ref_columns(conn)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _ensure_governance_subject_ref_columns(conn: sqlite3.Connection) -> None:
|
|
115
|
+
for table in (
|
|
116
|
+
"requests",
|
|
117
|
+
"interactions",
|
|
118
|
+
"profiles",
|
|
119
|
+
"user_playbooks",
|
|
120
|
+
"agent_success_evaluation_result",
|
|
121
|
+
):
|
|
122
|
+
columns = [row[1] for row in conn.execute(f"PRAGMA table_info({table})")]
|
|
123
|
+
if not columns:
|
|
124
|
+
continue
|
|
125
|
+
if "governance_subject_ref" in columns:
|
|
126
|
+
pass
|
|
127
|
+
else:
|
|
128
|
+
conn.execute(f"ALTER TABLE {table} ADD COLUMN governance_subject_ref TEXT")
|
|
129
|
+
conn.execute(
|
|
130
|
+
f"CREATE INDEX IF NOT EXISTS idx_{table}_governance_subject_ref "
|
|
131
|
+
f"ON {table}(governance_subject_ref)"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _json_dumps(obj: Any) -> str | None:
|
|
136
|
+
if obj is None:
|
|
137
|
+
return None
|
|
138
|
+
return json.dumps(obj, default=str)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _json_loads(text: str | None) -> Any:
|
|
142
|
+
if not text:
|
|
143
|
+
return None
|
|
144
|
+
return json.loads(text)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _upgrade_legacy_purge_operation_targets_table(conn: sqlite3.Connection) -> None:
|
|
148
|
+
target_columns = [
|
|
149
|
+
row[1] for row in conn.execute("PRAGMA table_info(purge_operation_targets)")
|
|
150
|
+
]
|
|
151
|
+
if not target_columns or "org_id" in target_columns:
|
|
152
|
+
return
|
|
153
|
+
conn.execute(
|
|
154
|
+
"ALTER TABLE purge_operation_targets RENAME TO purge_operation_targets_legacy"
|
|
155
|
+
)
|
|
156
|
+
conn.executescript(_PURGE_OPERATION_TARGETS_TABLE_DDL)
|
|
157
|
+
conn.execute(
|
|
158
|
+
"""INSERT INTO purge_operation_targets (
|
|
159
|
+
org_id, purge_id, target_name, target_ref, phase, status, detail,
|
|
160
|
+
deleted_count, error_detail, started_at, completed_at
|
|
161
|
+
)
|
|
162
|
+
SELECT uniquely_mapped_purges.org_id, legacy.purge_id, legacy.target_name,
|
|
163
|
+
legacy.target_ref, legacy.phase, legacy.status, legacy.detail,
|
|
164
|
+
legacy.deleted_count, legacy.error_detail, legacy.started_at,
|
|
165
|
+
legacy.completed_at
|
|
166
|
+
FROM purge_operation_targets_legacy AS legacy
|
|
167
|
+
JOIN (
|
|
168
|
+
SELECT MIN(org_id) AS org_id, purge_id
|
|
169
|
+
FROM purge_operations
|
|
170
|
+
GROUP BY purge_id
|
|
171
|
+
HAVING COUNT(*) = 1
|
|
172
|
+
) AS uniquely_mapped_purges
|
|
173
|
+
ON uniquely_mapped_purges.purge_id = legacy.purge_id"""
|
|
174
|
+
)
|
|
175
|
+
conn.execute("DROP TABLE purge_operation_targets_legacy")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _enforce_audit_request_ref_not_null(conn: sqlite3.Connection) -> None:
|
|
179
|
+
audit_columns = [row[1] for row in conn.execute("PRAGMA table_info(audit_events)")]
|
|
180
|
+
if not audit_columns:
|
|
181
|
+
return
|
|
182
|
+
conn.execute(
|
|
183
|
+
"UPDATE audit_events SET request_ref = ? WHERE request_ref IS NULL",
|
|
184
|
+
(_LEGACY_AUDIT_REQUEST_REF,),
|
|
185
|
+
)
|
|
186
|
+
conn.execute(
|
|
187
|
+
"""
|
|
188
|
+
CREATE TRIGGER IF NOT EXISTS audit_events_request_ref_not_null
|
|
189
|
+
BEFORE INSERT ON audit_events
|
|
190
|
+
WHEN NEW.request_ref IS NULL
|
|
191
|
+
BEGIN
|
|
192
|
+
SELECT RAISE(ABORT, 'audit_events.request_ref is required');
|
|
193
|
+
END
|
|
194
|
+
"""
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _row_to_audit_event(row: sqlite3.Row) -> AuditEvent:
|
|
199
|
+
return AuditEvent(
|
|
200
|
+
org_id=row["org_id"],
|
|
201
|
+
actor_type=row["actor_type"],
|
|
202
|
+
actor_ref=row["actor_ref"],
|
|
203
|
+
operation=row["operation"],
|
|
204
|
+
entity_type=row["entity_type"],
|
|
205
|
+
entity_id=row["entity_id"],
|
|
206
|
+
subject_ref=row["subject_ref"],
|
|
207
|
+
request_ref=row["request_ref"],
|
|
208
|
+
idempotency_key=row["idempotency_key"],
|
|
209
|
+
status=row["status"],
|
|
210
|
+
detail=_json_loads(row["detail"]),
|
|
211
|
+
created_at=row["created_at"],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _row_to_purge_operation(row: sqlite3.Row) -> PurgeOperation:
|
|
216
|
+
return PurgeOperation(
|
|
217
|
+
purge_id=row["purge_id"],
|
|
218
|
+
org_id=row["org_id"],
|
|
219
|
+
operation_type=row["operation_type"],
|
|
220
|
+
scope_type=row["scope_type"],
|
|
221
|
+
subject_ref=row["subject_ref"],
|
|
222
|
+
request_ref=row["request_ref"],
|
|
223
|
+
idempotency_key=row["idempotency_key"],
|
|
224
|
+
status=row["status"],
|
|
225
|
+
error_code=row["error_code"],
|
|
226
|
+
error_detail=row["error_detail"],
|
|
227
|
+
created_at=row["created_at"],
|
|
228
|
+
updated_at=row["updated_at"],
|
|
229
|
+
completed_at=row["completed_at"],
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _row_to_purge_target(row: sqlite3.Row) -> PurgeOperationTarget:
|
|
234
|
+
return PurgeOperationTarget(
|
|
235
|
+
purge_id=row["purge_id"],
|
|
236
|
+
target_name=row["target_name"],
|
|
237
|
+
target_ref=row["target_ref"],
|
|
238
|
+
phase=row["phase"],
|
|
239
|
+
status=row["status"],
|
|
240
|
+
detail=_json_loads(row["detail"]),
|
|
241
|
+
deleted_count=row["deleted_count"],
|
|
242
|
+
error_detail=row["error_detail"],
|
|
243
|
+
started_at=row["started_at"],
|
|
244
|
+
completed_at=row["completed_at"],
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _row_to_subject_write_barrier(row: sqlite3.Row) -> SubjectWriteBarrier:
|
|
249
|
+
return SubjectWriteBarrier(
|
|
250
|
+
org_id=str(row["org_id"]),
|
|
251
|
+
subject_ref=str(row["subject_ref"]),
|
|
252
|
+
purge_id=str(row["purge_id"]),
|
|
253
|
+
status=cast(SubjectBarrierStatus, str(row["status"])),
|
|
254
|
+
error_code=row["error_code"],
|
|
255
|
+
error_detail=row["error_detail"],
|
|
256
|
+
created_at=int(row["created_at"]),
|
|
257
|
+
updated_at=int(row["updated_at"]),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class _SQLiteGovernanceDeps(Protocol):
|
|
262
|
+
conn: sqlite3.Connection
|
|
263
|
+
_lock: threading.RLock
|
|
264
|
+
org_id: str
|
|
265
|
+
_has_sqlite_vec: bool
|
|
266
|
+
|
|
267
|
+
def _fetchall(
|
|
268
|
+
self, sql: str, params: list[Any] | tuple[Any, ...]
|
|
269
|
+
) -> list[sqlite3.Row]: ...
|
|
270
|
+
|
|
271
|
+
def _fetchone(
|
|
272
|
+
self, sql: str, params: list[Any] | tuple[Any, ...]
|
|
273
|
+
) -> sqlite3.Row | None: ...
|
|
274
|
+
|
|
275
|
+
def _partition_purge_vs_delete(
|
|
276
|
+
self, entity_type: Literal["profile", "user_playbook"], ids: list[str]
|
|
277
|
+
) -> tuple[list[str], list[str]]: ...
|
|
278
|
+
|
|
279
|
+
def _delete_in_chunks(
|
|
280
|
+
self, table_name: str, column_name: str, values: list[Any]
|
|
281
|
+
) -> None: ...
|
|
282
|
+
|
|
283
|
+
def _delete_source_windows_for_user_playbook_ids(
|
|
284
|
+
self, user_playbook_ids: list[int]
|
|
285
|
+
) -> None: ...
|
|
286
|
+
|
|
287
|
+
def _get_embedding(self, text: str) -> list[float]: ...
|
|
288
|
+
|
|
289
|
+
def set_source_windows_for_agent_playbook(
|
|
290
|
+
self, agent_playbook_id: int, windows: list[AgentPlaybookSourceWindow]
|
|
291
|
+
) -> None: ...
|
|
292
|
+
|
|
293
|
+
def get_source_windows_for_agent_playbook(
|
|
294
|
+
self, agent_playbook_id: int
|
|
295
|
+
) -> list[AgentPlaybookSourceWindow]: ...
|
|
296
|
+
|
|
297
|
+
def get_agent_playbook_by_id(
|
|
298
|
+
self,
|
|
299
|
+
agent_playbook_id: int,
|
|
300
|
+
*,
|
|
301
|
+
include_tombstones: bool = False,
|
|
302
|
+
) -> AgentPlaybook | None: ...
|
|
303
|
+
|
|
304
|
+
def _index_agent_playbook_fts_vec(self, ap: AgentPlaybook) -> None: ...
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class SQLiteGovernanceMixin:
|
|
308
|
+
"""SQLite governance storage primitives."""
|
|
309
|
+
|
|
310
|
+
conn: sqlite3.Connection
|
|
311
|
+
_lock: threading.RLock
|
|
312
|
+
org_id: str
|
|
313
|
+
|
|
314
|
+
def _deps(self) -> _SQLiteGovernanceDeps:
|
|
315
|
+
return cast(_SQLiteGovernanceDeps, self)
|
|
316
|
+
|
|
317
|
+
def _validate_prepared_delete_target_matrix_locked(self, purge_id: str) -> None:
|
|
318
|
+
snapshot = self.conn.execute(
|
|
319
|
+
"""SELECT 1 FROM purge_operation_targets
|
|
320
|
+
WHERE org_id = ? AND purge_id = ? AND target_name = ? AND target_ref = 'all'
|
|
321
|
+
AND phase = ? AND status = 'complete'""",
|
|
322
|
+
(self.org_id, purge_id, _SNAPSHOT_TARGET_NAME, _PREPARE_PHASE),
|
|
323
|
+
).fetchone()
|
|
324
|
+
if snapshot is None:
|
|
325
|
+
raise ValueError("Cannot delete user data without target snapshot marker")
|
|
326
|
+
delete_rows = self.conn.execute(
|
|
327
|
+
"""SELECT target_name, status FROM purge_operation_targets
|
|
328
|
+
WHERE org_id = ? AND purge_id = ? AND phase = 'delete'
|
|
329
|
+
AND target_ref = 'all'""",
|
|
330
|
+
(self.org_id, purge_id),
|
|
331
|
+
).fetchall()
|
|
332
|
+
delete_statuses = {
|
|
333
|
+
str(row["target_name"]): str(row["status"]) for row in delete_rows
|
|
334
|
+
}
|
|
335
|
+
missing_delete_targets = [
|
|
336
|
+
target_name
|
|
337
|
+
for target_name in _CANONICAL_DELETE_TARGET_NAMES
|
|
338
|
+
if delete_statuses.get(target_name) not in {"pending", "complete"}
|
|
339
|
+
]
|
|
340
|
+
if missing_delete_targets:
|
|
341
|
+
raise ValueError(
|
|
342
|
+
"Cannot delete user data without complete delete target matrix: "
|
|
343
|
+
+ ", ".join(missing_delete_targets)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def _validate_hide_for_rebuild_targets_locked(self, purge_id: str) -> None:
|
|
347
|
+
rebuild_rows = self.conn.execute(
|
|
348
|
+
"""SELECT DISTINCT target_ref
|
|
349
|
+
FROM purge_operation_targets
|
|
350
|
+
WHERE org_id = ? AND purge_id = ? AND target_name = 'agent_playbook'
|
|
351
|
+
AND phase = 'rebuild_without_erased_sources' AND target_ref != ''
|
|
352
|
+
ORDER BY target_ref ASC""",
|
|
353
|
+
(self.org_id, purge_id),
|
|
354
|
+
).fetchall()
|
|
355
|
+
if not rebuild_rows:
|
|
356
|
+
return
|
|
357
|
+
hidden_refs = {
|
|
358
|
+
str(row["target_ref"])
|
|
359
|
+
for row in self.conn.execute(
|
|
360
|
+
"""SELECT target_ref
|
|
361
|
+
FROM purge_operation_targets
|
|
362
|
+
WHERE org_id = ? AND purge_id = ? AND target_name = 'agent_playbook'
|
|
363
|
+
AND phase = 'hide_for_rebuild' AND status = 'complete'""",
|
|
364
|
+
(self.org_id, purge_id),
|
|
365
|
+
).fetchall()
|
|
366
|
+
}
|
|
367
|
+
missing_hidden_refs = [
|
|
368
|
+
str(row["target_ref"])
|
|
369
|
+
for row in rebuild_rows
|
|
370
|
+
if str(row["target_ref"]) not in hidden_refs
|
|
371
|
+
]
|
|
372
|
+
if missing_hidden_refs:
|
|
373
|
+
raise ValueError(
|
|
374
|
+
"Cannot delete user data before hide_for_rebuild completes for "
|
|
375
|
+
f"planned agent_playbooks: {', '.join(missing_hidden_refs)}"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def _planned_governance_delete_counts(
|
|
379
|
+
self, user_id: str, owned_user_playbook_ids: set[int]
|
|
380
|
+
) -> dict[str, int]:
|
|
381
|
+
request_row = self.conn.execute(
|
|
382
|
+
"SELECT COUNT(*) AS cnt FROM requests WHERE user_id = ?",
|
|
383
|
+
(user_id,),
|
|
384
|
+
).fetchone()
|
|
385
|
+
interaction_row = self.conn.execute(
|
|
386
|
+
"SELECT COUNT(*) AS cnt FROM interactions WHERE user_id = ?",
|
|
387
|
+
(user_id,),
|
|
388
|
+
).fetchone()
|
|
389
|
+
eval_result_row = self.conn.execute(
|
|
390
|
+
"""SELECT COUNT(*) AS cnt
|
|
391
|
+
FROM agent_success_evaluation_result
|
|
392
|
+
WHERE user_id = ?""",
|
|
393
|
+
(user_id,),
|
|
394
|
+
).fetchone()
|
|
395
|
+
profile_rows = self.conn.execute(
|
|
396
|
+
"SELECT profile_id FROM profiles WHERE user_id = ?",
|
|
397
|
+
(user_id,),
|
|
398
|
+
).fetchall()
|
|
399
|
+
if request_row is None or interaction_row is None or eval_result_row is None:
|
|
400
|
+
raise ValueError("Missing governance count rows")
|
|
401
|
+
profile_ids = [str(row["profile_id"]) for row in profile_rows]
|
|
402
|
+
purge_profile_ids, delete_profile_ids = self._deps()._partition_purge_vs_delete(
|
|
403
|
+
"profile",
|
|
404
|
+
profile_ids,
|
|
405
|
+
)
|
|
406
|
+
playbook_ids = [
|
|
407
|
+
str(user_playbook_id)
|
|
408
|
+
for user_playbook_id in sorted(owned_user_playbook_ids)
|
|
409
|
+
]
|
|
410
|
+
purge_playbook_ids, delete_playbook_ids = (
|
|
411
|
+
self._deps()._partition_purge_vs_delete(
|
|
412
|
+
"user_playbook",
|
|
413
|
+
playbook_ids,
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
return {
|
|
417
|
+
"request": int(request_row["cnt"]),
|
|
418
|
+
"interaction": int(interaction_row["cnt"]),
|
|
419
|
+
"profile": len(delete_profile_ids),
|
|
420
|
+
"profile_purge": len(purge_profile_ids),
|
|
421
|
+
"user_playbook": len(delete_playbook_ids),
|
|
422
|
+
"agent_success_evaluation_result": int(eval_result_row["cnt"]),
|
|
423
|
+
"user_playbook_purge": len(purge_playbook_ids),
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
def _owned_user_playbook_ids_locked(self, user_id: str) -> set[int]:
|
|
427
|
+
return {
|
|
428
|
+
int(row["user_playbook_id"])
|
|
429
|
+
for row in self.conn.execute(
|
|
430
|
+
"SELECT user_playbook_id FROM user_playbooks WHERE user_id = ?",
|
|
431
|
+
(user_id,),
|
|
432
|
+
).fetchall()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
def _prepared_owned_user_playbook_ids_locked(self, purge_id: str) -> set[int]:
|
|
436
|
+
row = self.conn.execute(
|
|
437
|
+
"""SELECT detail FROM purge_operation_targets
|
|
438
|
+
WHERE org_id = ? AND purge_id = ? AND target_name = ?
|
|
439
|
+
AND target_ref = 'all' AND phase = ? AND status = 'complete'""",
|
|
440
|
+
(self.org_id, purge_id, _SNAPSHOT_TARGET_NAME, _PREPARE_PHASE),
|
|
441
|
+
).fetchone()
|
|
442
|
+
if row is None:
|
|
443
|
+
raise ValueError("Prepared target snapshot is missing")
|
|
444
|
+
detail = _json_loads(row["detail"])
|
|
445
|
+
if not isinstance(detail, dict):
|
|
446
|
+
raise ValueError("Prepared target snapshot detail is missing")
|
|
447
|
+
return set(
|
|
448
|
+
_validate_governance_int_list(
|
|
449
|
+
"owned_user_playbook_ids",
|
|
450
|
+
detail.get("owned_user_playbook_ids"),
|
|
451
|
+
)
|
|
452
|
+
)
|
|
@@ -17,7 +17,12 @@ EntityType = Literal["user_playbook", "agent_playbook", "profile"]
|
|
|
17
17
|
# Also used as the merge guard: a source that already carries any of these
|
|
18
18
|
# statuses is skipped (no re-tombstone, no clock reset).
|
|
19
19
|
_GC_ELIGIBLE_STATUSES: frozenset[str] = frozenset(
|
|
20
|
-
{
|
|
20
|
+
{
|
|
21
|
+
Status.MERGED.value,
|
|
22
|
+
Status.SUPERSEDED.value,
|
|
23
|
+
Status.ARCHIVED.value,
|
|
24
|
+
Status.EXPIRED.value,
|
|
25
|
+
}
|
|
21
26
|
)
|
|
22
27
|
|
|
23
28
|
# Mapping from entity_type string to (table_name, primary_key_column).
|
|
@@ -95,15 +100,17 @@ _PROFILE_PURGE_SQL = (
|
|
|
95
100
|
"UPDATE profiles SET "
|
|
96
101
|
"content='', user_id='', generated_from_request_id='', source='', "
|
|
97
102
|
"embedding=NULL, extractor_names=NULL, expanded_terms=NULL, tags=NULL, "
|
|
98
|
-
"custom_features=NULL, notes=NULL, source_span=NULL, reader_angle=NULL "
|
|
103
|
+
"custom_features=NULL, notes=NULL, source_span=NULL, reader_angle=NULL, "
|
|
104
|
+
"governance_subject_ref=NULL "
|
|
99
105
|
"WHERE profile_id=?"
|
|
100
106
|
)
|
|
101
107
|
_USER_PLAYBOOK_PURGE_SQL = (
|
|
102
108
|
"UPDATE user_playbooks SET "
|
|
103
|
-
"content='', user_id=NULL, source=NULL, "
|
|
109
|
+
"content='', user_id=NULL, request_id='', source=NULL, "
|
|
104
110
|
"trigger=NULL, rationale=NULL, blocking_issue=NULL, "
|
|
105
111
|
"source_interaction_ids=NULL, embedding=NULL, expanded_terms=NULL, "
|
|
106
|
-
"tags=NULL, source_span=NULL, notes=NULL, reader_angle=NULL "
|
|
112
|
+
"tags=NULL, source_span=NULL, notes=NULL, reader_angle=NULL, "
|
|
113
|
+
"governance_subject_ref=NULL "
|
|
107
114
|
"WHERE user_playbook_id=?"
|
|
108
115
|
)
|
|
109
116
|
# agent_playbook purge not yet required; added when Task 3/4 needs it.
|