dsh-plugin-xujie-memory 0.6.0 → 0.7.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/ACCEPTANCE.md +37 -0
- package/BUILD-PROVENANCE.json +53 -0
- package/CHANGELOG.md +12 -0
- package/README.md +48 -23
- package/README_EN.md +32 -9
- package/lib/client.js +17219 -0
- package/lib/controller.js +307 -0
- package/lib/index.js +87 -22
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +15104 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +15093 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +49 -5
- package/python/xujie_memory/__init__.py +123 -123
- package/python/xujie_memory/engine.py +1333 -1312
- package/python/xujie_memory/management.py +203 -0
- package/python/xujie_memory/memory.py +27 -21
- package/python/xujie_memory/privacy.py +105 -0
- package/python/xujie_memory/schema.sql +492 -480
- package/python/xujie_memory/server.py +52 -0
- package/scripts/build.mjs +19 -0
- package/scripts/prepack.mjs +9 -0
- package/scripts/test-all.mjs +9 -0
- package/scripts/test-node.mjs +10 -0
- package/src/client/MemoryPanel.tsx +536 -0
- package/src/client/NeuralField.tsx +88 -0
- package/src/client/css.d.ts +4 -0
- package/src/client/icons.tsx +28 -0
- package/src/client/index.tsx +59 -0
- package/src/client/memory-adapter.ts +67 -0
- package/src/client/model.ts +79 -0
- package/src/client/neural/engine.ts +999 -0
- package/src/client/neural/morphology.ts +219 -0
- package/src/client/panel.css +337 -0
- package/src/client/types.ts +6 -0
- package/src/controller.ts +88 -0
- package/src/generated/typert.host.js +449 -0
- package/src/generated/typert.remote-client.js +438 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/test/cordis-fixture.mjs +5 -0
- package/test/management_regression.py +187 -0
- package/test/node-regression.entry.mjs +52 -0
- package/vendor/DSH-LICENSE.txt +21 -0
- package/vendor/THIRD-PARTY.md +6 -0
- package/vendor/dsh-protocol/index.ts +484 -0
- package/vendor/dsh-protocol/json-value.ts +53 -0
- package/vendor/dsh-protocol/owned-value.ts +39 -0
- package/vendor/dsh-protocol/principal.ts +17 -0
- package/vendor/dsh-protocol/remote-access.ts +38 -0
- package/vendor/dsh-protocol/remote-error.ts +49 -0
- package/vendor/dsh-protocol/remote-info.ts +161 -0
- package/vendor/dsh-protocol/types.ts +939 -0
- package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
- package/vendor/licenses/zod-MIT.txt +21 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Token-protected data plane for the DSH owner-only, in-app memory panel.
|
|
2
|
+
|
|
3
|
+
The Host authenticates its owner using the DSH Gateway. The browser never gets
|
|
4
|
+
the kernel token. Explicit scope ids come from catalog(), never guessed user ids.
|
|
5
|
+
Source traces stay immutable: edits atomically replace evidence with a new trace.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
from .memory import Memory
|
|
13
|
+
from .profile import ProfileStore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ManagementError(ValueError):
|
|
17
|
+
def __init__(self, code: str):
|
|
18
|
+
self.code = code
|
|
19
|
+
super().__init__(code)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Management:
|
|
23
|
+
def __init__(self, service):
|
|
24
|
+
self.service = service
|
|
25
|
+
self.engine = service.engine
|
|
26
|
+
self.memory = Memory(self.engine)
|
|
27
|
+
|
|
28
|
+
def scope(self, scope_id):
|
|
29
|
+
row = self.engine._conn().execute(
|
|
30
|
+
"SELECT * FROM scopes WHERE scope_id=?", (scope_id,)
|
|
31
|
+
).fetchone()
|
|
32
|
+
if row is None:
|
|
33
|
+
raise ManagementError("scope_not_found")
|
|
34
|
+
return dict(row)
|
|
35
|
+
|
|
36
|
+
def catalog(self):
|
|
37
|
+
rows = self.engine._conn().execute(
|
|
38
|
+
"SELECT s.scope_id,s.agent_id,COUNT(t.trace_id) AS traces FROM scopes s"
|
|
39
|
+
" LEFT JOIN traces t ON s.scope_id=t.scope_id AND t.erased_at IS NULL"
|
|
40
|
+
" GROUP BY s.scope_id ORDER BY s.agent_id,s.scope_id"
|
|
41
|
+
).fetchall()
|
|
42
|
+
return {"ok": True, "scope_mode": self.service.scope_mode,
|
|
43
|
+
"scopes": [dict(r) for r in rows]}
|
|
44
|
+
|
|
45
|
+
def detail(self, scope_id, trace_id):
|
|
46
|
+
self.scope(scope_id)
|
|
47
|
+
row = self.engine.get_trace(trace_id)
|
|
48
|
+
if row is None or row["scope_id"] != scope_id or row["erased_at"]:
|
|
49
|
+
raise ManagementError("trace_not_found")
|
|
50
|
+
weights = self.memory.lifecycle.batch_priority([trace_id]).get(trace_id, {})
|
|
51
|
+
return {"ok": True, "trace": {
|
|
52
|
+
"id": trace_id, "trace_id": trace_id, "scope_id": scope_id,
|
|
53
|
+
"content": row["content"], "content_hash": row["content_hash"],
|
|
54
|
+
"recorded_at": row["recorded_at"], "source_kind": row["source_kind"],
|
|
55
|
+
"importance": weights.get("base", 1), "anchor": bool(weights.get("anchor")),
|
|
56
|
+
"priority": weights.get("priority", 0),
|
|
57
|
+
}}
|
|
58
|
+
|
|
59
|
+
def snapshot(self, scope_id, limit=250):
|
|
60
|
+
self.scope(scope_id)
|
|
61
|
+
conn = self.engine._conn()
|
|
62
|
+
limit = max(1, min(500, int(limit)))
|
|
63
|
+
# A short read transaction prevents mixed snapshots during concurrent edits.
|
|
64
|
+
with self.engine._tx():
|
|
65
|
+
rows = conn.execute(
|
|
66
|
+
"SELECT trace_id,content,content_hash,recorded_at FROM traces"
|
|
67
|
+
" WHERE scope_id=? AND erased_at IS NULL"
|
|
68
|
+
" ORDER BY recorded_at DESC,trace_id LIMIT ?", (scope_id, limit)
|
|
69
|
+
).fetchall()
|
|
70
|
+
weights = self.memory.lifecycle.batch_priority([r["trace_id"] for r in rows])
|
|
71
|
+
nodes, edges = [], []
|
|
72
|
+
visible = {r["trace_id"] for r in rows}
|
|
73
|
+
for i, r in enumerate(rows):
|
|
74
|
+
w = weights.get(r["trace_id"], {})
|
|
75
|
+
node = {"id": r["trace_id"], "trace_id": r["trace_id"],
|
|
76
|
+
"layer": "episodic", "kind": "trace", "content": r["content"],
|
|
77
|
+
"content_hash": r["content_hash"], "recorded_at": r["recorded_at"],
|
|
78
|
+
"importance": w.get("base", 1), "anchor": bool(w.get("anchor")),
|
|
79
|
+
"priority": w.get("priority", 0), "editable": True}
|
|
80
|
+
nodes.append(node)
|
|
81
|
+
if node["anchor"]:
|
|
82
|
+
aid = "anchor:" + r["trace_id"]
|
|
83
|
+
nodes.append({**node, "id": aid, "layer": "semantic", "kind": "anchor"})
|
|
84
|
+
edges.append({"source": r["trace_id"], "target": aid,
|
|
85
|
+
"kind": "anchor_projection"})
|
|
86
|
+
if i < 8:
|
|
87
|
+
wid = "working:" + r["trace_id"]
|
|
88
|
+
nodes.append({**node, "id": wid, "layer": "working", "kind": "recent"})
|
|
89
|
+
edges.append({"source": r["trace_id"], "target": wid,
|
|
90
|
+
"kind": "working_projection"})
|
|
91
|
+
states = conn.execute(
|
|
92
|
+
"SELECT state_key,value_json,status,evaluated_as_of FROM states"
|
|
93
|
+
" WHERE scope_id=? AND status IN ('known','stale','disputed')"
|
|
94
|
+
" ORDER BY evaluated_as_of DESC,state_key LIMIT ?", (scope_id, limit)
|
|
95
|
+
).fetchall()
|
|
96
|
+
for r in states:
|
|
97
|
+
evidence = conn.execute(
|
|
98
|
+
"SELECT DISTINCT v.trace_id FROM event_evidence v"
|
|
99
|
+
" JOIN events e ON e.event_id=v.event_id"
|
|
100
|
+
" JOIN traces t ON t.trace_id=v.trace_id"
|
|
101
|
+
" WHERE e.state_key=? AND e.void=0 AND t.erased_at IS NULL"
|
|
102
|
+
" ORDER BY v.trace_id", (r["state_key"],),
|
|
103
|
+
).fetchall()
|
|
104
|
+
parts = r["state_key"].split("|")
|
|
105
|
+
label = " · ".join(parts[1:4])
|
|
106
|
+
nodes.append({"id": r["state_key"], "layer": "semantic", "kind": "state",
|
|
107
|
+
"content": label + ": " + (r["value_json"] or "未知"),
|
|
108
|
+
"status": r["status"], "editable": False,
|
|
109
|
+
"evidence": [e["trace_id"] for e in evidence]})
|
|
110
|
+
for e in evidence:
|
|
111
|
+
if e["trace_id"] in visible:
|
|
112
|
+
edges.append({"source": e["trace_id"], "target": r["state_key"],
|
|
113
|
+
"kind": "evidence"})
|
|
114
|
+
summaries = conn.execute(
|
|
115
|
+
"SELECT summary_id,text,input_refs_json,created_at FROM summaries"
|
|
116
|
+
" WHERE scope_id=? AND lifecycle_state='active' AND text<>''"
|
|
117
|
+
" ORDER BY created_at DESC,summary_id LIMIT 60", (scope_id,),
|
|
118
|
+
).fetchall()
|
|
119
|
+
for r in summaries:
|
|
120
|
+
try:
|
|
121
|
+
refs = json.loads(r["input_refs_json"])
|
|
122
|
+
except (TypeError, ValueError):
|
|
123
|
+
continue
|
|
124
|
+
nodes.append({"id": r["summary_id"], "layer": "episodic", "kind": "summary",
|
|
125
|
+
"content": r["text"], "editable": False,
|
|
126
|
+
"recorded_at": r["created_at"], "evidence": refs})
|
|
127
|
+
for ref in refs:
|
|
128
|
+
if ref in visible:
|
|
129
|
+
edges.append({"source": ref, "target": r["summary_id"],
|
|
130
|
+
"kind": "summary_input"})
|
|
131
|
+
pending = conn.execute(
|
|
132
|
+
"SELECT request_id,kind,detail_json FROM clarifications"
|
|
133
|
+
" WHERE scope_id=? AND status='open' ORDER BY created_at DESC LIMIT 50",
|
|
134
|
+
(scope_id,),
|
|
135
|
+
).fetchall()
|
|
136
|
+
for r in pending:
|
|
137
|
+
nodes.append({"id": r["request_id"], "layer": "working",
|
|
138
|
+
"kind": "clarification", "editable": False,
|
|
139
|
+
"content": r["kind"] + ":" + (r["detail_json"] or "")})
|
|
140
|
+
cards = []
|
|
141
|
+
store = ProfileStore(self.engine)
|
|
142
|
+
for row in store.profiles(scope_id):
|
|
143
|
+
cards.append(store.card(scope_id, row["account_id"], limit=50))
|
|
144
|
+
stats = self.memory.stats(scope_id)
|
|
145
|
+
return {"ok": True, "scope_id": scope_id, "scope_mode": self.service.scope_mode,
|
|
146
|
+
"nodes": nodes, "edges": edges, "cards": cards, "stats": stats,
|
|
147
|
+
"truncated": stats["traces"] > len(rows),
|
|
148
|
+
"limit": limit, "render_links_are_not_semantic_inference": True}
|
|
149
|
+
|
|
150
|
+
def search(self, scope_id, query, limit=30):
|
|
151
|
+
self.scope(scope_id)
|
|
152
|
+
return {"ok": True, "hits": self.memory.search(scope_id, query, limit=limit, candidates=None)}
|
|
153
|
+
|
|
154
|
+
def edit(self, scope_id, trace_id, content, expected_hash):
|
|
155
|
+
self.scope(scope_id)
|
|
156
|
+
content = content.strip()
|
|
157
|
+
if not content or len(content) > 2000:
|
|
158
|
+
raise ManagementError("invalid_content_length")
|
|
159
|
+
with self.engine._tx():
|
|
160
|
+
trace = self.detail(scope_id, trace_id)["trace"]
|
|
161
|
+
if not expected_hash or trace["content_hash"] != expected_hash:
|
|
162
|
+
raise ManagementError("version_conflict")
|
|
163
|
+
if trace["content"] == content:
|
|
164
|
+
return {"ok": True, "changed": False, "trace": trace}
|
|
165
|
+
original = self.engine.get_trace(trace_id)
|
|
166
|
+
actor = original["actor_id"]
|
|
167
|
+
occurred_at = original.get("source_time") or original["recorded_at"]
|
|
168
|
+
self.memory.forget(scope_id, trace_id)
|
|
169
|
+
# A saved edit is an explicit user correction, not a fabricated
|
|
170
|
+
# conversational turn. Do not run profile-watching/auto-replies.
|
|
171
|
+
result = self.service.orchestrator.ingest.ingest(
|
|
172
|
+
scope_id, "edit:" + trace_id + ":" + uuid.uuid4().hex,
|
|
173
|
+
actor, "user_statement", content,
|
|
174
|
+
context={"peer_id": "assistant", "effective_at": occurred_at}, confirmed=True,
|
|
175
|
+
pattern_escalation=False,
|
|
176
|
+
)
|
|
177
|
+
self.engine._conn().execute(
|
|
178
|
+
"UPDATE traces SET source_time=?,source_time_precision=? WHERE trace_id=?",
|
|
179
|
+
(occurred_at, original.get("source_time_precision"), result.trace_id),
|
|
180
|
+
)
|
|
181
|
+
self.memory.mark(scope_id, result.trace_id, trace["importance"])
|
|
182
|
+
if trace["anchor"]:
|
|
183
|
+
self.memory.lifecycle.set_anchor(result.trace_id, True)
|
|
184
|
+
store = ProfileStore(self.engine)
|
|
185
|
+
for row in store.profiles(scope_id):
|
|
186
|
+
store.derive_from_memory(scope_id, row["account_id"])
|
|
187
|
+
self.engine._conn().execute(
|
|
188
|
+
"INSERT INTO memory_edits(edit_id,scope_id,old_trace_id,new_trace_id,edited_at)"
|
|
189
|
+
" VALUES (?,?,?,?,?)",
|
|
190
|
+
(uuid.uuid4().hex, scope_id, trace_id, result.trace_id, self.engine.now()),
|
|
191
|
+
)
|
|
192
|
+
saved = self.detail(scope_id, result.trace_id)["trace"]
|
|
193
|
+
return {"ok": True, "changed": True, "replaced_trace_id": trace_id, "trace": saved,
|
|
194
|
+
"pending": len(result.clarifications)}
|
|
195
|
+
|
|
196
|
+
def forget(self, scope_id, trace_id, expected_hash):
|
|
197
|
+
self.scope(scope_id)
|
|
198
|
+
with self.engine._tx():
|
|
199
|
+
trace = self.detail(scope_id, trace_id)["trace"]
|
|
200
|
+
if not expected_hash or trace["content_hash"] != expected_hash:
|
|
201
|
+
raise ManagementError("version_conflict")
|
|
202
|
+
result = self.memory.forget(scope_id, trace_id)
|
|
203
|
+
return {"ok": True, **result}
|
|
@@ -80,18 +80,21 @@ class FuzzySearch:
|
|
|
80
80
|
self.engine = engine
|
|
81
81
|
|
|
82
82
|
def search(self, scope_id: str, query: str, *, limit: int = 5,
|
|
83
|
-
candidates: int = 800) -> list[dict[str, Any]]:
|
|
83
|
+
candidates: int | None = 800) -> list[dict[str, Any]]:
|
|
84
84
|
query = (query or "").strip()
|
|
85
85
|
if not query:
|
|
86
86
|
return []
|
|
87
|
-
|
|
88
|
-
"SELECT trace_id, content, recorded_at FROM traces"
|
|
87
|
+
sql = (
|
|
88
|
+
"SELECT trace_id, content, content_hash, recorded_at FROM traces"
|
|
89
89
|
" WHERE scope_id=? AND erased_at IS NULL"
|
|
90
|
-
" ORDER BY recorded_at DESC
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
" ORDER BY recorded_at DESC"
|
|
91
|
+
)
|
|
92
|
+
params = (scope_id,)
|
|
93
|
+
if candidates is not None:
|
|
94
|
+
sql += " LIMIT ?"
|
|
95
|
+
params += (max(1, int(candidates)),)
|
|
96
|
+
rows = self.engine._conn().execute(sql, params)
|
|
97
|
+
# 自动召回保持候选上限,管理界面可显式搜索全部有效原话。
|
|
95
98
|
q = _normalize(query)
|
|
96
99
|
if not q:
|
|
97
100
|
return []
|
|
@@ -124,6 +127,7 @@ class FuzzySearch:
|
|
|
124
127
|
out.append({
|
|
125
128
|
"trace_id": row["trace_id"],
|
|
126
129
|
"content": row["content"],
|
|
130
|
+
"content_hash": row["content_hash"],
|
|
127
131
|
"score": round(float(score), 4),
|
|
128
132
|
"priority": w.get("priority"),
|
|
129
133
|
"anchor": bool(w.get("anchor")),
|
|
@@ -228,17 +232,18 @@ class Memory:
|
|
|
228
232
|
trace = self.engine.get_trace(trace_id)
|
|
229
233
|
if trace is None or trace.get("scope_id") != scope_id:
|
|
230
234
|
raise ValueError("trace_not_found")
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
235
|
+
# One outer transaction covers evidence, summaries AND the person card.
|
|
236
|
+
# Nested Engine transactions use savepoints.
|
|
237
|
+
with self.engine._tx():
|
|
238
|
+
already_erased = bool(trace.get("erased_at"))
|
|
239
|
+
result = {"state_keys": []} if already_erased else self.engine.erase_trace(trace_id)
|
|
240
|
+
from .profile import ProfileStore
|
|
241
|
+
profiles = ProfileStore(self.engine)
|
|
242
|
+
accounts = self.engine._conn().execute(
|
|
243
|
+
"SELECT account_id FROM person_profiles WHERE scope_id=?", (scope_id,)
|
|
244
|
+
).fetchall()
|
|
245
|
+
for account in accounts:
|
|
246
|
+
profiles.derive_from_memory(scope_id, account["account_id"])
|
|
242
247
|
return {"trace_id": trace_id, "erased": True, "already_erased": already_erased,
|
|
243
248
|
"state_keys": result["state_keys"]}
|
|
244
249
|
|
|
@@ -273,5 +278,6 @@ class Memory:
|
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
# ------------------------------------------------------------ 搜索
|
|
276
|
-
def search(self, scope_id: str, query: str, *, limit: int = 5
|
|
277
|
-
|
|
281
|
+
def search(self, scope_id: str, query: str, *, limit: int = 5,
|
|
282
|
+
candidates: int | None = 800) -> list[dict]:
|
|
283
|
+
return self.fuzzy.search(scope_id, query, limit=limit, candidates=candidates)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Removal of active projections that depend on explicitly erased evidence.
|
|
2
|
+
|
|
3
|
+
This is logical application erasure, not forensic disk erasure: SQLite WAL,
|
|
4
|
+
external exports/backups and the DSH conversation log are outside this API.
|
|
5
|
+
Audit identifiers, timestamps and hashes are retained, not replacement facts.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def repair_legacy(engine, profiles) -> None:
|
|
13
|
+
"""Once-only sweep for projections left by 0.6's incomplete forgetting."""
|
|
14
|
+
with engine._tx() as conn:
|
|
15
|
+
if conn.execute(
|
|
16
|
+
"SELECT 1 FROM memory_maintenance WHERE name='privacy_repair_070'"
|
|
17
|
+
).fetchone():
|
|
18
|
+
return
|
|
19
|
+
scopes = set()
|
|
20
|
+
for row in conn.execute(
|
|
21
|
+
"SELECT trace_id,scope_id FROM traces WHERE erased_at IS NOT NULL"
|
|
22
|
+
).fetchall():
|
|
23
|
+
keys = [r[0] for r in conn.execute(
|
|
24
|
+
"SELECT DISTINCT e.state_key FROM events e JOIN event_evidence v"
|
|
25
|
+
" ON e.event_id=v.event_id WHERE v.trace_id=?", (row["trace_id"],)
|
|
26
|
+
).fetchall()]
|
|
27
|
+
purge_derived(engine, conn, row["trace_id"], keys)
|
|
28
|
+
scopes.add(row["scope_id"])
|
|
29
|
+
for scope in scopes:
|
|
30
|
+
for profile in profiles.profiles(scope):
|
|
31
|
+
profiles.derive_from_memory(scope, profile["account_id"])
|
|
32
|
+
conn.execute(
|
|
33
|
+
"INSERT INTO memory_maintenance(name,completed_at) VALUES (?,?)",
|
|
34
|
+
("privacy_repair_070", engine.now()),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def purge_derived(engine, conn, trace_id: str, state_keys: list[str]) -> None:
|
|
39
|
+
trace = conn.execute(
|
|
40
|
+
"SELECT scope_id FROM traces WHERE trace_id=?", (trace_id,)
|
|
41
|
+
).fetchone()
|
|
42
|
+
if trace is None:
|
|
43
|
+
return
|
|
44
|
+
scope = trace["scope_id"]
|
|
45
|
+
# Merged summaries retain the original trace ids in input_refs_json too.
|
|
46
|
+
# Clear content, not merely the active flag; otherwise it survives in detail.
|
|
47
|
+
for row in conn.execute(
|
|
48
|
+
"SELECT summary_id,input_refs_json FROM summaries WHERE scope_id=?", (scope,)
|
|
49
|
+
).fetchall():
|
|
50
|
+
try:
|
|
51
|
+
refs = json.loads(row["input_refs_json"])
|
|
52
|
+
except (TypeError, ValueError):
|
|
53
|
+
# An unreadable dependency cannot safely remain model-visible.
|
|
54
|
+
refs = [trace_id]
|
|
55
|
+
if trace_id in refs:
|
|
56
|
+
conn.execute(
|
|
57
|
+
"UPDATE summaries SET text='',lifecycle_state='archived',"
|
|
58
|
+
"coverage_json='{}',omissions_json='[]' WHERE summary_id=?",
|
|
59
|
+
(row["summary_id"],),
|
|
60
|
+
)
|
|
61
|
+
conn.execute(
|
|
62
|
+
"UPDATE clarifications SET status='dismissed',detail_json='{}'"
|
|
63
|
+
" WHERE scope_id=? AND EXISTS ("
|
|
64
|
+
" SELECT 1 FROM json_each(clarifications.interpretation_ids_json) j"
|
|
65
|
+
" JOIN interpretations i ON i.interpretation_id=j.value"
|
|
66
|
+
" WHERE i.trace_id=?)", (scope, trace_id),
|
|
67
|
+
)
|
|
68
|
+
conn.execute(
|
|
69
|
+
"UPDATE encodings SET payload_json='{}' WHERE trace_id=?", (trace_id,)
|
|
70
|
+
)
|
|
71
|
+
conn.execute(
|
|
72
|
+
"UPDATE interpretations SET slots_json='{}',signals_json='{}' WHERE trace_id=?",
|
|
73
|
+
(trace_id,),
|
|
74
|
+
)
|
|
75
|
+
conn.execute(
|
|
76
|
+
"UPDATE events SET payload_json='{}' WHERE void=1 AND event_id IN"
|
|
77
|
+
" (SELECT event_id FROM event_evidence WHERE trace_id=?)", (trace_id,),
|
|
78
|
+
)
|
|
79
|
+
conn.execute(
|
|
80
|
+
"UPDATE person_tags SET value='',status='expired' WHERE scope_id=?"
|
|
81
|
+
" AND evidence_trace_id=?", (scope, trace_id),
|
|
82
|
+
)
|
|
83
|
+
conn.execute(
|
|
84
|
+
"UPDATE profile_alerts SET signals_json='{}',status='dismissed',resolved_at=?"
|
|
85
|
+
" WHERE scope_id=? AND evidence_trace_id=?", (engine.now(), scope, trace_id),
|
|
86
|
+
)
|
|
87
|
+
for table in ("emotion_events", "relationship_events"):
|
|
88
|
+
conn.execute(
|
|
89
|
+
f"DELETE FROM {table} WHERE scope_id=? AND evidence_trace_id=?",
|
|
90
|
+
(scope, trace_id),
|
|
91
|
+
)
|
|
92
|
+
conn.execute(
|
|
93
|
+
"DELETE FROM grievances WHERE scope_id=? AND evidence_trace_id=?",
|
|
94
|
+
(scope, trace_id),
|
|
95
|
+
)
|
|
96
|
+
conn.execute(
|
|
97
|
+
"UPDATE lifecycle SET anchor=0,level='archived'"
|
|
98
|
+
" WHERE scope_id=? AND ref_type='trace' AND ref_id=?", (scope, trace_id),
|
|
99
|
+
)
|
|
100
|
+
for key in state_keys:
|
|
101
|
+
conn.execute("DELETE FROM state_snapshots WHERE state_key=?", (key,))
|
|
102
|
+
conn.execute(
|
|
103
|
+
"UPDATE states SET last_known_json=NULL WHERE state_key=?"
|
|
104
|
+
" AND status NOT IN ('known','stale')", (key,),
|
|
105
|
+
)
|