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,465 @@
|
|
|
1
|
+
"""SQLite governance-erase-execution store methods.
|
|
2
|
+
|
|
3
|
+
Extracted verbatim from ``_governance.py`` (the GovernanceEraseExecution bucket):
|
|
4
|
+
the two public methods (``apply_governance_user_data_delete``,
|
|
5
|
+
``complete_purge_operation_with_audit``) plus the two EraseExecution-owned
|
|
6
|
+
privates (``_purge_governance_entity_content_locked``,
|
|
7
|
+
``_clear_user_data_for_governance_locked``).
|
|
8
|
+
|
|
9
|
+
``complete_purge_operation_with_audit`` is one of the two methods authorized to
|
|
10
|
+
write a successful-ERASE audit row — the ERASE-audit-idempotency block, the
|
|
11
|
+
re-read+identity-verify, and the ``status='complete'`` flip are preserved
|
|
12
|
+
byte-for-byte, committing together with the audit insert.
|
|
13
|
+
``_purge_governance_entity_content_locked`` emits the ``wasPurged`` lineage event
|
|
14
|
+
only on an actual content purge (``rowcount <= 0 -> return False`` before
|
|
15
|
+
``_append_event_stmt``); the ``_PURGE_SQL`` / ``_append_event_stmt`` symbols are
|
|
16
|
+
function-imported LOCAL-in-method (from ``.._lineage``) to avoid an import cycle.
|
|
17
|
+
|
|
18
|
+
The residual ``SQLiteGovernanceMixin`` stays composed alongside this mixin and
|
|
19
|
+
permanently holds the shared infra (``conn``, ``_lock``, ``org_id``,
|
|
20
|
+
``_deps()``), the delete-target / hide-for-rebuild / prepared-snapshot
|
|
21
|
+
validators it reaches cross-bucket
|
|
22
|
+
(``_validate_prepared_delete_target_matrix_locked``,
|
|
23
|
+
``_validate_hide_for_rebuild_targets_locked``,
|
|
24
|
+
``_prepared_owned_user_playbook_ids_locked``), and the module-level helpers
|
|
25
|
+
(``_row_to_audit_event``, ``_row_to_purge_operation``), which are imported here
|
|
26
|
+
rather than duplicated. ``_append_audit_event_with_cursor`` (AuditEventStore),
|
|
27
|
+
``get_purge_operation`` / ``_record_purge_target_locked`` (PurgeOperationStore)
|
|
28
|
+
resolve through the composed MRO.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import sqlite3
|
|
35
|
+
import threading
|
|
36
|
+
from collections.abc import Callable
|
|
37
|
+
from typing import TYPE_CHECKING, Literal
|
|
38
|
+
|
|
39
|
+
from reflexio.models.api_schema.domain.governance import (
|
|
40
|
+
AuditEvent,
|
|
41
|
+
PurgeOperation,
|
|
42
|
+
)
|
|
43
|
+
from reflexio.server.services.storage.governance_validation import (
|
|
44
|
+
_CANONICAL_DELETE_TARGET_NAMES,
|
|
45
|
+
_PREPARE_PHASE,
|
|
46
|
+
_SNAPSHOT_TARGET_NAME,
|
|
47
|
+
_canonicalize_audit_event_for_persistence,
|
|
48
|
+
_epoch_now,
|
|
49
|
+
_is_successful_erase_event,
|
|
50
|
+
_successful_erase_identity,
|
|
51
|
+
_validate_governance_purge_id,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from .._governance import _row_to_audit_event, _row_to_purge_operation
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from .._governance import _SQLiteGovernanceDeps
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GovernanceEraseExecutionMixin:
|
|
61
|
+
"""SQLite governance-erase-execution store primitives."""
|
|
62
|
+
|
|
63
|
+
# Type hints for instance attributes/methods provided via MRO by the
|
|
64
|
+
# co-composed residual SQLiteGovernanceMixin / SQLiteStorageBase.
|
|
65
|
+
conn: sqlite3.Connection
|
|
66
|
+
_lock: threading.RLock
|
|
67
|
+
org_id: str
|
|
68
|
+
_deps: Callable[[], _SQLiteGovernanceDeps]
|
|
69
|
+
|
|
70
|
+
# Cross-bucket residents reached via MRO: the delete-target / hide /
|
|
71
|
+
# prepared-snapshot validators live on the residual SQLiteGovernanceMixin.
|
|
72
|
+
_validate_prepared_delete_target_matrix_locked: Callable[[str], None]
|
|
73
|
+
_validate_hide_for_rebuild_targets_locked: Callable[[str], None]
|
|
74
|
+
_prepared_owned_user_playbook_ids_locked: Callable[[str], set[int]]
|
|
75
|
+
|
|
76
|
+
# Provided via MRO by the co-composed AuditEventStoreMixin (audit bucket)
|
|
77
|
+
# and PurgeOperationStoreMixin (purge bucket); reached here by the
|
|
78
|
+
# cross-bucket erase-execution / purge completion methods.
|
|
79
|
+
_append_audit_event_with_cursor: Callable[
|
|
80
|
+
[sqlite3.Connection | sqlite3.Cursor, AuditEvent], bool
|
|
81
|
+
]
|
|
82
|
+
get_purge_operation: Callable[[str], PurgeOperation]
|
|
83
|
+
_record_purge_target_locked: Callable[..., None]
|
|
84
|
+
|
|
85
|
+
def _purge_governance_entity_content_locked(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
entity_type: Literal["profile", "user_playbook"],
|
|
89
|
+
entity_id: str,
|
|
90
|
+
rowid: int,
|
|
91
|
+
) -> bool:
|
|
92
|
+
from .._lineage import _PURGE_SQL, _append_event_stmt
|
|
93
|
+
|
|
94
|
+
sql = _PURGE_SQL[entity_type]
|
|
95
|
+
cur = self.conn.execute(sql, (entity_id,))
|
|
96
|
+
if cur.rowcount <= 0:
|
|
97
|
+
return False
|
|
98
|
+
_append_event_stmt(
|
|
99
|
+
self.conn,
|
|
100
|
+
org_id=self.org_id,
|
|
101
|
+
entity_type=entity_type,
|
|
102
|
+
entity_id=entity_id,
|
|
103
|
+
op="purge",
|
|
104
|
+
prov="wasPurged",
|
|
105
|
+
source_ids=[],
|
|
106
|
+
actor="erasure",
|
|
107
|
+
request_id=f"purge_{entity_id}",
|
|
108
|
+
reason="content_purge",
|
|
109
|
+
)
|
|
110
|
+
if entity_type == "profile":
|
|
111
|
+
self.conn.execute(
|
|
112
|
+
"DELETE FROM profiles_fts WHERE profile_id = ?",
|
|
113
|
+
(entity_id,),
|
|
114
|
+
)
|
|
115
|
+
if self._deps()._has_sqlite_vec:
|
|
116
|
+
self.conn.execute(
|
|
117
|
+
"DELETE FROM profiles_vec WHERE rowid = ?",
|
|
118
|
+
(rowid,),
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
self.conn.execute(
|
|
122
|
+
"DELETE FROM user_playbooks_fts WHERE rowid = ?",
|
|
123
|
+
(rowid,),
|
|
124
|
+
)
|
|
125
|
+
if self._deps()._has_sqlite_vec:
|
|
126
|
+
self.conn.execute(
|
|
127
|
+
"DELETE FROM user_playbooks_vec WHERE rowid = ?",
|
|
128
|
+
(rowid,),
|
|
129
|
+
)
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
def _clear_user_data_for_governance_locked(
|
|
133
|
+
self,
|
|
134
|
+
user_id: str,
|
|
135
|
+
*,
|
|
136
|
+
expected_user_playbook_ids: set[int] | None = None,
|
|
137
|
+
) -> dict[str, int]:
|
|
138
|
+
deps = self._deps()
|
|
139
|
+
interaction_ids = [
|
|
140
|
+
int(row["interaction_id"])
|
|
141
|
+
for row in self.conn.execute(
|
|
142
|
+
"SELECT interaction_id FROM interactions WHERE user_id = ?",
|
|
143
|
+
(user_id,),
|
|
144
|
+
).fetchall()
|
|
145
|
+
]
|
|
146
|
+
raw_upb_ids = [
|
|
147
|
+
int(row["user_playbook_id"])
|
|
148
|
+
for row in self.conn.execute(
|
|
149
|
+
"SELECT user_playbook_id FROM user_playbooks WHERE user_id = ?",
|
|
150
|
+
(user_id,),
|
|
151
|
+
).fetchall()
|
|
152
|
+
]
|
|
153
|
+
if (
|
|
154
|
+
expected_user_playbook_ids is not None
|
|
155
|
+
and set(raw_upb_ids) != expected_user_playbook_ids
|
|
156
|
+
):
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"Current user playbooks no longer match prepared purge snapshot"
|
|
159
|
+
)
|
|
160
|
+
request_ids = [
|
|
161
|
+
str(row["request_id"])
|
|
162
|
+
for row in self.conn.execute(
|
|
163
|
+
"SELECT request_id FROM requests WHERE user_id = ?",
|
|
164
|
+
(user_id,),
|
|
165
|
+
).fetchall()
|
|
166
|
+
]
|
|
167
|
+
profile_rows = self.conn.execute(
|
|
168
|
+
"SELECT rowid, profile_id FROM profiles WHERE user_id = ?",
|
|
169
|
+
(user_id,),
|
|
170
|
+
).fetchall()
|
|
171
|
+
profile_rowid_by_id = {
|
|
172
|
+
str(row["profile_id"]): int(row["rowid"]) for row in profile_rows
|
|
173
|
+
}
|
|
174
|
+
all_profile_ids = list(profile_rowid_by_id)
|
|
175
|
+
|
|
176
|
+
purge_profile_ids, delete_profile_ids = deps._partition_purge_vs_delete(
|
|
177
|
+
"profile",
|
|
178
|
+
all_profile_ids,
|
|
179
|
+
)
|
|
180
|
+
purge_upb_str_ids, delete_upb_str_ids = deps._partition_purge_vs_delete(
|
|
181
|
+
"user_playbook",
|
|
182
|
+
[str(user_playbook_id) for user_playbook_id in raw_upb_ids],
|
|
183
|
+
)
|
|
184
|
+
purge_upb_ids = [int(entity_id) for entity_id in purge_upb_str_ids]
|
|
185
|
+
delete_upb_ids = [int(entity_id) for entity_id in delete_upb_str_ids]
|
|
186
|
+
erased_entity_ids = [
|
|
187
|
+
*request_ids,
|
|
188
|
+
*[str(interaction_id) for interaction_id in interaction_ids],
|
|
189
|
+
*all_profile_ids,
|
|
190
|
+
*[str(user_playbook_id) for user_playbook_id in raw_upb_ids],
|
|
191
|
+
]
|
|
192
|
+
if erased_entity_ids:
|
|
193
|
+
erased_entity_id_set = set(erased_entity_ids)
|
|
194
|
+
lineage_source_event_ids: list[int] = []
|
|
195
|
+
for row in self.conn.execute(
|
|
196
|
+
"SELECT event_id, source_ids FROM lineage_event WHERE org_id = ?",
|
|
197
|
+
(self.org_id,),
|
|
198
|
+
).fetchall():
|
|
199
|
+
try:
|
|
200
|
+
source_ids = json.loads(str(row["source_ids"] or "[]"))
|
|
201
|
+
except json.JSONDecodeError:
|
|
202
|
+
source_ids = []
|
|
203
|
+
if any(
|
|
204
|
+
str(source_id) in erased_entity_id_set for source_id in source_ids
|
|
205
|
+
):
|
|
206
|
+
lineage_source_event_ids.append(int(row["event_id"]))
|
|
207
|
+
deps._delete_in_chunks(
|
|
208
|
+
"lineage_event", "event_id", lineage_source_event_ids
|
|
209
|
+
)
|
|
210
|
+
placeholders = ",".join("?" for _ in erased_entity_ids)
|
|
211
|
+
self.conn.execute(
|
|
212
|
+
f"""DELETE FROM lineage_event
|
|
213
|
+
WHERE org_id = ?
|
|
214
|
+
AND (
|
|
215
|
+
request_id IN ({placeholders})
|
|
216
|
+
OR entity_id IN ({placeholders})
|
|
217
|
+
)""", # noqa: S608
|
|
218
|
+
[self.org_id, *erased_entity_ids, *erased_entity_ids],
|
|
219
|
+
)
|
|
220
|
+
delete_profile_rowids = [
|
|
221
|
+
profile_rowid_by_id[profile_id]
|
|
222
|
+
for profile_id in delete_profile_ids
|
|
223
|
+
if profile_id in profile_rowid_by_id
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
deps._delete_in_chunks("interactions_fts", "rowid", interaction_ids)
|
|
227
|
+
deps._delete_in_chunks("user_playbooks_fts", "rowid", delete_upb_ids)
|
|
228
|
+
deps._delete_in_chunks("profiles_fts", "profile_id", delete_profile_ids)
|
|
229
|
+
if deps._has_sqlite_vec:
|
|
230
|
+
deps._delete_in_chunks("interactions_vec", "rowid", interaction_ids)
|
|
231
|
+
deps._delete_in_chunks("user_playbooks_vec", "rowid", delete_upb_ids)
|
|
232
|
+
deps._delete_in_chunks("profiles_vec", "rowid", delete_profile_rowids)
|
|
233
|
+
|
|
234
|
+
interactions_cur = self.conn.execute(
|
|
235
|
+
"DELETE FROM interactions WHERE user_id = ?",
|
|
236
|
+
(user_id,),
|
|
237
|
+
)
|
|
238
|
+
eval_results_cur = self.conn.execute(
|
|
239
|
+
"""DELETE FROM agent_success_evaluation_result
|
|
240
|
+
WHERE user_id = ?""",
|
|
241
|
+
(user_id,),
|
|
242
|
+
)
|
|
243
|
+
requests_cur = self.conn.execute(
|
|
244
|
+
"DELETE FROM requests WHERE user_id = ?",
|
|
245
|
+
(user_id,),
|
|
246
|
+
)
|
|
247
|
+
if raw_upb_ids:
|
|
248
|
+
deps._delete_source_windows_for_user_playbook_ids(raw_upb_ids)
|
|
249
|
+
if delete_upb_ids:
|
|
250
|
+
deps._delete_in_chunks("user_playbooks", "user_playbook_id", delete_upb_ids)
|
|
251
|
+
if delete_profile_ids:
|
|
252
|
+
deps._delete_in_chunks("profiles", "profile_id", delete_profile_ids)
|
|
253
|
+
|
|
254
|
+
purged_profiles = 0
|
|
255
|
+
for profile_id in purge_profile_ids:
|
|
256
|
+
rowid = profile_rowid_by_id.get(profile_id)
|
|
257
|
+
if rowid is None:
|
|
258
|
+
continue
|
|
259
|
+
purged_profiles += int(
|
|
260
|
+
self._purge_governance_entity_content_locked(
|
|
261
|
+
entity_type="profile",
|
|
262
|
+
entity_id=profile_id,
|
|
263
|
+
rowid=rowid,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
purged_user_playbooks = 0
|
|
268
|
+
for user_playbook_id in purge_upb_ids:
|
|
269
|
+
purged_user_playbooks += int(
|
|
270
|
+
self._purge_governance_entity_content_locked(
|
|
271
|
+
entity_type="user_playbook",
|
|
272
|
+
entity_id=str(user_playbook_id),
|
|
273
|
+
rowid=user_playbook_id,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"interactions": interactions_cur.rowcount,
|
|
279
|
+
"user_playbooks": len(delete_upb_ids),
|
|
280
|
+
"profiles": len(delete_profile_ids),
|
|
281
|
+
"requests": requests_cur.rowcount,
|
|
282
|
+
"agent_success_evaluation_results": eval_results_cur.rowcount,
|
|
283
|
+
"purged_profiles": purged_profiles,
|
|
284
|
+
"purged_user_playbooks": purged_user_playbooks,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
def apply_governance_user_data_delete(
|
|
288
|
+
self, purge_id: str, user_id: str
|
|
289
|
+
) -> dict[str, int]:
|
|
290
|
+
purge_id = _validate_governance_purge_id("purge_id", purge_id)
|
|
291
|
+
name_map = {
|
|
292
|
+
"interactions": "interaction",
|
|
293
|
+
"user_playbooks": "user_playbook",
|
|
294
|
+
"profiles": "profile",
|
|
295
|
+
"requests": "request",
|
|
296
|
+
"agent_success_evaluation_results": "agent_success_evaluation_result",
|
|
297
|
+
"purged_profiles": "profile_purge",
|
|
298
|
+
"purged_user_playbooks": "user_playbook_purge",
|
|
299
|
+
}
|
|
300
|
+
with self._lock:
|
|
301
|
+
try:
|
|
302
|
+
self.conn.execute("BEGIN")
|
|
303
|
+
self._validate_prepared_delete_target_matrix_locked(purge_id)
|
|
304
|
+
self._validate_hide_for_rebuild_targets_locked(purge_id)
|
|
305
|
+
expected_user_playbook_ids = (
|
|
306
|
+
self._prepared_owned_user_playbook_ids_locked(purge_id)
|
|
307
|
+
)
|
|
308
|
+
counts = self._clear_user_data_for_governance_locked(
|
|
309
|
+
user_id,
|
|
310
|
+
expected_user_playbook_ids=expected_user_playbook_ids,
|
|
311
|
+
)
|
|
312
|
+
for key, value in counts.items():
|
|
313
|
+
self._record_purge_target_locked(
|
|
314
|
+
purge_id=purge_id,
|
|
315
|
+
target_name=name_map.get(key, key),
|
|
316
|
+
target_ref="all",
|
|
317
|
+
phase="delete",
|
|
318
|
+
status="complete",
|
|
319
|
+
detail={"count": int(value)},
|
|
320
|
+
deleted_count=int(value),
|
|
321
|
+
error_detail=None,
|
|
322
|
+
)
|
|
323
|
+
self.conn.commit()
|
|
324
|
+
except Exception:
|
|
325
|
+
self.conn.rollback()
|
|
326
|
+
raise
|
|
327
|
+
return counts
|
|
328
|
+
|
|
329
|
+
def complete_purge_operation_with_audit(
|
|
330
|
+
self, purge_id: str, audit_event: AuditEvent
|
|
331
|
+
) -> PurgeOperation:
|
|
332
|
+
purge_id = _validate_governance_purge_id("purge_id", purge_id)
|
|
333
|
+
if audit_event.org_id != self.org_id:
|
|
334
|
+
raise ValueError("Audit event org_id must match storage org_id")
|
|
335
|
+
if audit_event.idempotency_key != purge_id:
|
|
336
|
+
raise ValueError("Audit event idempotency key must match purge_id")
|
|
337
|
+
if not _is_successful_erase_event(audit_event, purge_id=purge_id):
|
|
338
|
+
raise ValueError(
|
|
339
|
+
"Completion requires a successful ERASE audit event for this purge"
|
|
340
|
+
)
|
|
341
|
+
audit_event = _canonicalize_audit_event_for_persistence(audit_event)
|
|
342
|
+
now = _epoch_now()
|
|
343
|
+
with self._lock:
|
|
344
|
+
try:
|
|
345
|
+
self.conn.execute("BEGIN IMMEDIATE")
|
|
346
|
+
row = self.conn.execute(
|
|
347
|
+
"SELECT * FROM purge_operations WHERE purge_id = ? AND org_id = ?",
|
|
348
|
+
(purge_id, self.org_id),
|
|
349
|
+
).fetchone()
|
|
350
|
+
if row is None:
|
|
351
|
+
raise ValueError(f"Purge operation {purge_id!r} not found")
|
|
352
|
+
purge_operation = _row_to_purge_operation(row)
|
|
353
|
+
if purge_operation.subject_ref != audit_event.subject_ref:
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"Audit event subject_ref must match purge operation subject_ref"
|
|
356
|
+
)
|
|
357
|
+
if purge_operation.request_ref != audit_event.request_ref:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
"Audit event request_ref must match purge operation request_ref"
|
|
360
|
+
)
|
|
361
|
+
barrier_row = self.conn.execute(
|
|
362
|
+
"""SELECT status FROM subject_write_barriers
|
|
363
|
+
WHERE org_id = ? AND subject_ref = ? AND purge_id = ?""",
|
|
364
|
+
(self.org_id, audit_event.subject_ref, purge_id),
|
|
365
|
+
).fetchone()
|
|
366
|
+
if barrier_row is None or barrier_row["status"] != "erasing":
|
|
367
|
+
raise ValueError("subject erasure barrier is missing")
|
|
368
|
+
snapshot = self.conn.execute(
|
|
369
|
+
"""SELECT 1 FROM purge_operation_targets
|
|
370
|
+
WHERE org_id = ? AND purge_id = ? AND target_name = ? AND target_ref = 'all'
|
|
371
|
+
AND phase = ? AND status = 'complete'""",
|
|
372
|
+
(self.org_id, purge_id, _SNAPSHOT_TARGET_NAME, _PREPARE_PHASE),
|
|
373
|
+
).fetchone()
|
|
374
|
+
if snapshot is None:
|
|
375
|
+
raise ValueError(
|
|
376
|
+
"Cannot complete purge without target snapshot marker"
|
|
377
|
+
)
|
|
378
|
+
delete_rows = self.conn.execute(
|
|
379
|
+
"""SELECT target_name, status FROM purge_operation_targets
|
|
380
|
+
WHERE org_id = ? AND purge_id = ? AND phase = 'delete'
|
|
381
|
+
AND target_ref = 'all'""",
|
|
382
|
+
(self.org_id, purge_id),
|
|
383
|
+
).fetchall()
|
|
384
|
+
delete_statuses = {
|
|
385
|
+
str(row["target_name"]): str(row["status"]) for row in delete_rows
|
|
386
|
+
}
|
|
387
|
+
missing_delete_targets = [
|
|
388
|
+
target_name
|
|
389
|
+
for target_name in _CANONICAL_DELETE_TARGET_NAMES
|
|
390
|
+
if delete_statuses.get(target_name) != "complete"
|
|
391
|
+
]
|
|
392
|
+
if missing_delete_targets:
|
|
393
|
+
raise ValueError(
|
|
394
|
+
"Cannot complete purge without complete delete target matrix: "
|
|
395
|
+
+ ", ".join(missing_delete_targets)
|
|
396
|
+
)
|
|
397
|
+
incomplete = self.conn.execute(
|
|
398
|
+
"""SELECT 1 FROM purge_operation_targets
|
|
399
|
+
WHERE org_id = ? AND purge_id = ? AND status != 'complete'
|
|
400
|
+
LIMIT 1""",
|
|
401
|
+
(self.org_id, purge_id),
|
|
402
|
+
).fetchone()
|
|
403
|
+
if incomplete is not None:
|
|
404
|
+
raise ValueError("Cannot complete purge with incomplete targets")
|
|
405
|
+
existing_audit_row = self.conn.execute(
|
|
406
|
+
"""SELECT * FROM audit_events
|
|
407
|
+
WHERE org_id = ? AND idempotency_key = ?""",
|
|
408
|
+
(self.org_id, purge_id),
|
|
409
|
+
).fetchone()
|
|
410
|
+
if existing_audit_row is not None:
|
|
411
|
+
existing_event = _row_to_audit_event(existing_audit_row)
|
|
412
|
+
if not _is_successful_erase_event(
|
|
413
|
+
existing_event, purge_id=purge_id
|
|
414
|
+
):
|
|
415
|
+
raise ValueError(
|
|
416
|
+
"Existing audit row for purge_id must be the matching "
|
|
417
|
+
"successful ERASE row"
|
|
418
|
+
)
|
|
419
|
+
if _successful_erase_identity(
|
|
420
|
+
existing_event
|
|
421
|
+
) != _successful_erase_identity(audit_event):
|
|
422
|
+
raise ValueError(
|
|
423
|
+
"Existing audit row for purge_id must be the matching "
|
|
424
|
+
"successful ERASE row"
|
|
425
|
+
)
|
|
426
|
+
else:
|
|
427
|
+
self._append_audit_event_with_cursor(self.conn, audit_event)
|
|
428
|
+
existing_audit_row = self.conn.execute(
|
|
429
|
+
"""SELECT * FROM audit_events
|
|
430
|
+
WHERE org_id = ? AND idempotency_key = ?""",
|
|
431
|
+
(self.org_id, purge_id),
|
|
432
|
+
).fetchone()
|
|
433
|
+
if existing_audit_row is None:
|
|
434
|
+
raise ValueError(
|
|
435
|
+
"Completion requires exactly one successful ERASE audit row "
|
|
436
|
+
"for the purge_id"
|
|
437
|
+
)
|
|
438
|
+
existing_event = _row_to_audit_event(existing_audit_row)
|
|
439
|
+
if not _is_successful_erase_event(existing_event, purge_id=purge_id):
|
|
440
|
+
raise ValueError(
|
|
441
|
+
"Completion requires exactly one matching successful ERASE "
|
|
442
|
+
"audit row for the purge_id"
|
|
443
|
+
)
|
|
444
|
+
if _successful_erase_identity(
|
|
445
|
+
existing_event
|
|
446
|
+
) != _successful_erase_identity(audit_event):
|
|
447
|
+
raise ValueError(
|
|
448
|
+
"Completion requires exactly one matching successful ERASE "
|
|
449
|
+
"audit row for the purge_id"
|
|
450
|
+
)
|
|
451
|
+
self.conn.execute(
|
|
452
|
+
"""UPDATE purge_operations
|
|
453
|
+
SET status = 'complete',
|
|
454
|
+
error_code = NULL,
|
|
455
|
+
error_detail = NULL,
|
|
456
|
+
updated_at = ?,
|
|
457
|
+
completed_at = ?
|
|
458
|
+
WHERE purge_id = ? AND org_id = ?""",
|
|
459
|
+
(now, now, purge_id, self.org_id),
|
|
460
|
+
)
|
|
461
|
+
self.conn.commit()
|
|
462
|
+
except Exception:
|
|
463
|
+
self.conn.rollback()
|
|
464
|
+
raise
|
|
465
|
+
return self.get_purge_operation(purge_id)
|