@rubytech/create-realagent 1.0.678 → 1.0.681
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/dist/index.js +232 -39
- package/package.json +1 -1
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +38 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +130 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/index.js +201 -45
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +78 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.js +194 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
- package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +157 -0
- package/payload/platform/lib/graph-mcp/src/index.ts +247 -47
- package/payload/platform/lib/graph-mcp/src/schema-cache.ts +212 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-trash/dist/index.js +109 -14
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-trash/src/index.ts +136 -21
- package/payload/platform/plugins/docs/references/deployment.md +4 -2
- package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +20 -0
- package/payload/platform/plugins/memory/PLUGIN.md +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
- package/payload/platform/scripts/vnc.sh +12 -409
- package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
- package/payload/platform/templates/dotfiles/.tmux.conf +1 -0
- package/payload/platform/templates/systemd/maxy-ttyd.service +25 -0
- package/payload/server/chunk-3RBKKDHC.js +783 -0
- package/payload/server/maxy-edge.js +377 -8
- package/payload/server/public/assets/admin-CIkyOur7.js +362 -0
- package/payload/server/public/assets/admin-kHJ-D0s7.css +1 -0
- package/payload/server/public/index.html +2 -1
- package/payload/server/server.js +391 -412
- package/payload/server/public/assets/admin-BBL1no_g.js +0 -352
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process schema cache for the graph-mcp proxy (Task 654).
|
|
3
|
+
*
|
|
4
|
+
* Caches { labels, relationshipTypes } from the connected Neo4j instance so
|
|
5
|
+
* the cypher validator can look up tokens without a round-trip per call.
|
|
6
|
+
*
|
|
7
|
+
* Refresh triggers:
|
|
8
|
+
* - boot — async refresh kicked off by start(); validator fails
|
|
9
|
+
* OPEN until the first refresh resolves.
|
|
10
|
+
* - interval — every 60s (configurable) while the process is alive.
|
|
11
|
+
* - stale-miss — an unknown token with a near match (edit distance ≤ 2)
|
|
12
|
+
* triggers a debounced refresh, catching fresh migrations
|
|
13
|
+
* without requiring operators to wait out the interval.
|
|
14
|
+
*
|
|
15
|
+
* Failure posture:
|
|
16
|
+
* - A refresh that throws preserves the last good snapshot and leaves
|
|
17
|
+
* ready() as it was. `[schema-cache] refresh failure ...` is logged
|
|
18
|
+
* loudly so operators see persistent Neo4j unreachability.
|
|
19
|
+
* - A boot refresh that fails leaves ready()=false and the snapshot empty.
|
|
20
|
+
* The validator's own fail-open branch handles this (empty snapshot →
|
|
21
|
+
* ok=true, no rejections), so the graph-mcp proxy keeps working against
|
|
22
|
+
* a still-reachable Neo4j instance that's just slow to respond to the
|
|
23
|
+
* initial `db.labels()` call.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { SchemaSnapshot, UnknownToken } from "./cypher-validate.js";
|
|
27
|
+
|
|
28
|
+
export interface SchemaFetcher {
|
|
29
|
+
labels(): Promise<string[]>;
|
|
30
|
+
relationshipTypes(): Promise<string[]>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SchemaCacheOptions {
|
|
34
|
+
/** Interval between automatic refreshes. 0 disables the timer (tests). */
|
|
35
|
+
refreshIntervalMs?: number;
|
|
36
|
+
/** Minimum ms between stale-miss-triggered refreshes. Default 5000. */
|
|
37
|
+
staleMissDebounceMs?: number;
|
|
38
|
+
/** Emit a log line. Default: stderr. Tests inject to capture. */
|
|
39
|
+
emit?: (line: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 60_000;
|
|
43
|
+
const DEFAULT_STALE_MISS_DEBOUNCE_MS = 5_000;
|
|
44
|
+
const STALE_MISS_DISTANCE_CEILING = 2;
|
|
45
|
+
|
|
46
|
+
export class SchemaCache {
|
|
47
|
+
private _labels = new Set<string>();
|
|
48
|
+
private _rels = new Set<string>();
|
|
49
|
+
private _ready = false;
|
|
50
|
+
private _timer: ReturnType<typeof setInterval> | null = null;
|
|
51
|
+
private _refreshInFlight: Promise<boolean> | null = null;
|
|
52
|
+
private _lastStaleMissAt = 0;
|
|
53
|
+
|
|
54
|
+
constructor(
|
|
55
|
+
private readonly fetcher: SchemaFetcher,
|
|
56
|
+
private readonly options: SchemaCacheOptions = {},
|
|
57
|
+
) {}
|
|
58
|
+
|
|
59
|
+
snapshot(): SchemaSnapshot {
|
|
60
|
+
return { labels: this._labels, relationshipTypes: this._rels };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ready(): boolean {
|
|
64
|
+
return this._ready;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async start(): Promise<void> {
|
|
68
|
+
await this.refresh("boot");
|
|
69
|
+
const interval = this.options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
70
|
+
if (interval > 0) {
|
|
71
|
+
this._timer = setInterval(() => {
|
|
72
|
+
void this.refresh("interval");
|
|
73
|
+
}, interval);
|
|
74
|
+
// Allow the process to exit without waiting for the timer.
|
|
75
|
+
if (typeof this._timer === "object" && "unref" in this._timer) {
|
|
76
|
+
(this._timer as { unref?: () => void }).unref?.();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
stop(): void {
|
|
82
|
+
if (this._timer) {
|
|
83
|
+
clearInterval(this._timer);
|
|
84
|
+
this._timer = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Refresh the cache. Single-flight: concurrent calls await the same in-flight
|
|
90
|
+
* promise rather than triggering overlapping Neo4j reads. Returns true on
|
|
91
|
+
* successful update, false on failure (last good snapshot preserved).
|
|
92
|
+
*/
|
|
93
|
+
async refresh(reason: string): Promise<boolean> {
|
|
94
|
+
if (this._refreshInFlight) return this._refreshInFlight;
|
|
95
|
+
const run = async (): Promise<boolean> => {
|
|
96
|
+
const started = Date.now();
|
|
97
|
+
try {
|
|
98
|
+
const [labels, rels] = await Promise.all([
|
|
99
|
+
this.fetcher.labels(),
|
|
100
|
+
this.fetcher.relationshipTypes(),
|
|
101
|
+
]);
|
|
102
|
+
this._labels = new Set(labels);
|
|
103
|
+
this._rels = new Set(rels);
|
|
104
|
+
this._ready = true;
|
|
105
|
+
this.emit(
|
|
106
|
+
`[schema-cache] refresh reason=${reason} labels=${this._labels.size} relationshipTypes=${this._rels.size} ms=${Date.now() - started}`,
|
|
107
|
+
);
|
|
108
|
+
return true;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
111
|
+
this.emit(
|
|
112
|
+
`[schema-cache] refresh failure reason=${reason} ms=${Date.now() - started} error="${msg.replace(/"/g, "'")}"`,
|
|
113
|
+
);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
this._refreshInFlight = run();
|
|
118
|
+
try {
|
|
119
|
+
return await this._refreshInFlight;
|
|
120
|
+
} finally {
|
|
121
|
+
this._refreshInFlight = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* If the validator returned unknown tokens whose nearest known token is
|
|
127
|
+
* within STALE_MISS_DISTANCE_CEILING edits, trigger an out-of-band refresh
|
|
128
|
+
* (debounced). The heuristic: a near-miss is likely a fresh migration
|
|
129
|
+
* (renamed / newly added label/edge), where a far-miss is almost certainly
|
|
130
|
+
* a typo and a refresh would not change the outcome. Returns true iff a
|
|
131
|
+
* refresh was actually run this call.
|
|
132
|
+
*/
|
|
133
|
+
async maybeRebuildOnStaleMiss(unknown: UnknownToken[]): Promise<boolean> {
|
|
134
|
+
if (unknown.length === 0) return false;
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
const debounceMs =
|
|
137
|
+
this.options.staleMissDebounceMs ?? DEFAULT_STALE_MISS_DEBOUNCE_MS;
|
|
138
|
+
if (now - this._lastStaleMissAt < debounceMs) return false;
|
|
139
|
+
const anyNearMiss = unknown.some((u) => {
|
|
140
|
+
if (u.nearest.length === 0) return false;
|
|
141
|
+
return editDistance(u.token, u.nearest[0]) <= STALE_MISS_DISTANCE_CEILING;
|
|
142
|
+
});
|
|
143
|
+
if (!anyNearMiss) return false;
|
|
144
|
+
this._lastStaleMissAt = now;
|
|
145
|
+
await this.refresh("stale-miss");
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private emit(line: string): void {
|
|
150
|
+
if (this.options.emit) {
|
|
151
|
+
this.options.emit(line);
|
|
152
|
+
} else {
|
|
153
|
+
process.stderr.write(`${line}\n`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build a SchemaFetcher over a long-lived neo4j-driver Driver. Each call
|
|
160
|
+
* opens a fresh session (cheap, sub-ms) and closes it in finally so the
|
|
161
|
+
* driver's connection pool stays small. Consumer owns the driver lifecycle.
|
|
162
|
+
*
|
|
163
|
+
* Import is deferred to the factory call so that test files can exercise
|
|
164
|
+
* the SchemaCache class without pulling neo4j-driver into the module graph.
|
|
165
|
+
*/
|
|
166
|
+
export async function neo4jSchemaFetcher(
|
|
167
|
+
uri: string,
|
|
168
|
+
user: string,
|
|
169
|
+
password: string,
|
|
170
|
+
): Promise<SchemaFetcher> {
|
|
171
|
+
const neo4j = await import("neo4j-driver");
|
|
172
|
+
const driver = neo4j.default.driver(uri, neo4j.default.auth.basic(user, password));
|
|
173
|
+
const query = async (cypher: string): Promise<string[]> => {
|
|
174
|
+
const session = driver.session();
|
|
175
|
+
try {
|
|
176
|
+
const result = await session.run(cypher);
|
|
177
|
+
return result.records
|
|
178
|
+
.map((r) => r.get(0))
|
|
179
|
+
.filter((v): v is string => typeof v === "string");
|
|
180
|
+
} finally {
|
|
181
|
+
await session.close();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
async labels() {
|
|
186
|
+
return query("CALL db.labels() YIELD label RETURN label");
|
|
187
|
+
},
|
|
188
|
+
async relationshipTypes() {
|
|
189
|
+
return query(
|
|
190
|
+
"CALL db.relationshipTypes() YIELD relationshipType RETURN relationshipType",
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function editDistance(a: string, b: string): number {
|
|
197
|
+
if (a === b) return 0;
|
|
198
|
+
if (a.length === 0) return b.length;
|
|
199
|
+
if (b.length === 0) return a.length;
|
|
200
|
+
let prev = new Array<number>(b.length + 1);
|
|
201
|
+
let curr = new Array<number>(b.length + 1);
|
|
202
|
+
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
|
203
|
+
for (let i = 1; i <= a.length; i++) {
|
|
204
|
+
curr[0] = i;
|
|
205
|
+
for (let j = 1; j <= b.length; j++) {
|
|
206
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
207
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
208
|
+
}
|
|
209
|
+
[prev, curr] = [curr, prev];
|
|
210
|
+
}
|
|
211
|
+
return prev[b.length];
|
|
212
|
+
}
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
* are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
|
|
14
14
|
* MERGE against the same key won't collide. `restoreNode` writes them back
|
|
15
15
|
* and fails loudly when an active node already occupies the slot.
|
|
16
|
+
*
|
|
17
|
+
* Label-aware cascade (Task 655): when `trashNode` trashes a `:Conversation`,
|
|
18
|
+
* it also trashes every `(m:Message)-[:PART_OF]->(c)` Message in the same
|
|
19
|
+
* managed transaction. This makes `MATCH (m:Trashed)` a correct audit
|
|
20
|
+
* primitive again — pre-Task-655 a Conversation trash left attached Messages
|
|
21
|
+
* un-labelled, so audits that filtered `m:Trashed` silently under-reported
|
|
22
|
+
* the blast radius of the 2026-04-22 incident by a factor of 10+.
|
|
23
|
+
* `restoreNode` reverses both sides.
|
|
16
24
|
*/
|
|
17
25
|
import type { Session } from "neo4j-driver";
|
|
18
26
|
export interface TrashParams {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyIzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA6G/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0EpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAI9D;AAED,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAqB,CAAC"}
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
* are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
|
|
15
15
|
* MERGE against the same key won't collide. `restoreNode` writes them back
|
|
16
16
|
* and fails loudly when an active node already occupies the slot.
|
|
17
|
+
*
|
|
18
|
+
* Label-aware cascade (Task 655): when `trashNode` trashes a `:Conversation`,
|
|
19
|
+
* it also trashes every `(m:Message)-[:PART_OF]->(c)` Message in the same
|
|
20
|
+
* managed transaction. This makes `MATCH (m:Trashed)` a correct audit
|
|
21
|
+
* primitive again — pre-Task-655 a Conversation trash left attached Messages
|
|
22
|
+
* un-labelled, so audits that filtered `m:Trashed` silently under-reported
|
|
23
|
+
* the blast radius of the 2026-04-22 incident by a factor of 10+.
|
|
24
|
+
* `restoreNode` reverses both sides.
|
|
17
25
|
*/
|
|
18
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
27
|
exports.TRASH_METADATA_PROPS = void 0;
|
|
@@ -92,19 +100,72 @@ async function trashNode(params) {
|
|
|
92
100
|
.join(", ");
|
|
93
101
|
const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
|
|
94
102
|
const trashedAt = new Date().toISOString();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
const isConversation = baseLabels.includes("Conversation");
|
|
104
|
+
// Message unique-key list computed once — matches UNIQUE_KEYS_BY_LABEL["Message"].
|
|
105
|
+
const messageUniqueKeys = UNIQUE_KEYS_BY_LABEL["Message"] ?? [];
|
|
106
|
+
// Root trash + Conversation→Message cascade in one managed transaction so a
|
|
107
|
+
// DB error mid-sweep can't leave a Conversation :Trashed with some Messages
|
|
108
|
+
// un-trashed (the 2026-04-22 audit correctness gap). For non-Conversation
|
|
109
|
+
// labels, the cascade branch is a no-op — same one-write shape as before.
|
|
110
|
+
let cascadedMessageCount = 0;
|
|
111
|
+
await session.executeWrite(async (tx) => {
|
|
112
|
+
await tx.run(`MATCH (n) WHERE elementId(n) = $eid
|
|
113
|
+
SET n:Trashed,
|
|
114
|
+
n.trashedAt = datetime($trashedAt),
|
|
115
|
+
n.trashedBy = $by,
|
|
116
|
+
n.trashReason = $reason,
|
|
117
|
+
n._trashedKeys = $trashedKeysJson${setNullSuffix}`, {
|
|
118
|
+
eid: elementId,
|
|
119
|
+
trashedAt,
|
|
120
|
+
by,
|
|
121
|
+
reason: reason ?? null,
|
|
122
|
+
trashedKeysJson: JSON.stringify(originalKeys),
|
|
123
|
+
});
|
|
124
|
+
if (isConversation) {
|
|
125
|
+
// Two-step cascade: collect Message elementIds and their unique-key
|
|
126
|
+
// values first, then apply :Trashed + snapshot + null in one SET per
|
|
127
|
+
// message using parameter-passed snapshot JSON. This keeps JSON
|
|
128
|
+
// construction in the Node process (where JSON.stringify is trivially
|
|
129
|
+
// correct) rather than building escaped JSON inside Cypher.
|
|
130
|
+
const collectKeys = messageUniqueKeys.length > 0
|
|
131
|
+
? messageUniqueKeys.map((k) => `\`${k}\`: m.\`${k}\``).join(", ")
|
|
132
|
+
: "";
|
|
133
|
+
const collectMsgProps = collectKeys ? `, {${collectKeys}}` : ", {}";
|
|
134
|
+
const collected = await tx.run(`MATCH (c) WHERE elementId(c) = $eid
|
|
135
|
+
MATCH (m:Message)-[:PART_OF]->(c)
|
|
136
|
+
WHERE NOT m:Trashed
|
|
137
|
+
RETURN elementId(m) AS meid${collectMsgProps} AS keys`, { eid: elementId });
|
|
138
|
+
for (const rec of collected.records) {
|
|
139
|
+
const meid = rec.get("meid");
|
|
140
|
+
const keys = rec.get("keys");
|
|
141
|
+
const liveKeys = {};
|
|
142
|
+
for (const k of messageUniqueKeys) {
|
|
143
|
+
if (keys[k] !== undefined && keys[k] !== null)
|
|
144
|
+
liveKeys[k] = keys[k];
|
|
145
|
+
}
|
|
146
|
+
const msgSetNulls = Object.keys(liveKeys).length > 0
|
|
147
|
+
? ", " + Object.keys(liveKeys).map((k) => `m.\`${k}\` = null`).join(", ")
|
|
148
|
+
: "";
|
|
149
|
+
await tx.run(`MATCH (m) WHERE elementId(m) = $meid
|
|
150
|
+
SET m:Trashed,
|
|
151
|
+
m.trashedAt = datetime($trashedAt),
|
|
152
|
+
m.trashedBy = $by,
|
|
153
|
+
m.trashReason = $reason,
|
|
154
|
+
m._trashedKeys = $trashedKeysJson${msgSetNulls}`, {
|
|
155
|
+
meid,
|
|
156
|
+
trashedAt,
|
|
157
|
+
by: `${by}:cascade-from-conversation`,
|
|
158
|
+
reason: reason ?? `cascade from Conversation ${elementId}`,
|
|
159
|
+
trashedKeysJson: JSON.stringify(liveKeys),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
cascadedMessageCount = collected.records.length;
|
|
163
|
+
}
|
|
106
164
|
});
|
|
107
165
|
process.stderr.write(`[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}\n`);
|
|
166
|
+
if (isConversation) {
|
|
167
|
+
process.stderr.write(`[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}\n`);
|
|
168
|
+
}
|
|
108
169
|
return {
|
|
109
170
|
trashed: true,
|
|
110
171
|
alreadyTrashed: false,
|
|
@@ -150,10 +211,44 @@ async function restoreNode(params) {
|
|
|
150
211
|
const setParams = { eid: elementId };
|
|
151
212
|
for (const [k, v] of Object.entries(originalKeys))
|
|
152
213
|
setParams[`val_${k}`] = v;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
214
|
+
const isConversation = baseLabels.includes("Conversation");
|
|
215
|
+
let cascadedMessageCount = 0;
|
|
216
|
+
await session.executeWrite(async (tx) => {
|
|
217
|
+
await tx.run(`MATCH (n:Trashed) WHERE elementId(n) = $eid
|
|
218
|
+
REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
|
|
219
|
+
SET n.restoredAt = datetime()${setSuffix}`, setParams);
|
|
220
|
+
if (isConversation) {
|
|
221
|
+
// Reverse of the trashNode cascade: re-label and restore unique-key
|
|
222
|
+
// snapshots for every `(m:Message)-[:PART_OF]->(c)` that was trashed
|
|
223
|
+
// by the same cascade chain (i.e. m._trashedKeys exists + m was
|
|
224
|
+
// trashed by a `*:cascade-from-conversation` provenance). We scope by
|
|
225
|
+
// the second condition so a user-initiated single-Message trash that
|
|
226
|
+
// happens to sit inside this Conversation isn't accidentally undone.
|
|
227
|
+
const collected = await tx.run(`MATCH (c) WHERE elementId(c) = $eid
|
|
228
|
+
MATCH (m:Trashed:Message)-[:PART_OF]->(c)
|
|
229
|
+
WHERE m.trashedBy ENDS WITH ':cascade-from-conversation'
|
|
230
|
+
RETURN elementId(m) AS meid, m._trashedKeys AS keysJson`, { eid: elementId });
|
|
231
|
+
for (const rec of collected.records) {
|
|
232
|
+
const meid = rec.get("meid");
|
|
233
|
+
const keysJson = rec.get("keysJson");
|
|
234
|
+
const keys = keysJson ? JSON.parse(keysJson) : {};
|
|
235
|
+
const setClause = Object.keys(keys).length > 0
|
|
236
|
+
? ", " + Object.keys(keys).map((k) => `m.\`${k}\` = $val_${k}`).join(", ")
|
|
237
|
+
: "";
|
|
238
|
+
const msgParams = { meid };
|
|
239
|
+
for (const [k, v] of Object.entries(keys))
|
|
240
|
+
msgParams[`val_${k}`] = v;
|
|
241
|
+
await tx.run(`MATCH (m) WHERE elementId(m) = $meid
|
|
242
|
+
REMOVE m:Trashed, m.trashedAt, m.trashedBy, m.trashReason, m._trashedKeys
|
|
243
|
+
SET m.restoredAt = datetime()${setClause}`, msgParams);
|
|
244
|
+
}
|
|
245
|
+
cascadedMessageCount = collected.records.length;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
156
248
|
process.stderr.write(`[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}\n`);
|
|
249
|
+
if (isConversation) {
|
|
250
|
+
process.stderr.write(`[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}\n`);
|
|
251
|
+
}
|
|
157
252
|
return {
|
|
158
253
|
restored: true,
|
|
159
254
|
nodeId: elementId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AA4DH,8BAyEC;AAeD,kCAkEC;AA8BD,gCA0EC;AAcD,gCAEC;AAGD,kDAIC;AAjVD;;;;;;GAMG;AACH,MAAM,oBAAoB,GAA6B;IACrD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,OAAO,EAAE,CAAC,WAAW,CAAC;IACtB,aAAa,EAAE,CAAC,WAAW,CAAC;IAC5B,IAAI,EAAE,CAAC,QAAQ,CAAC;IAChB,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,iBAAiB,EAAE,CAAC,cAAc,CAAC;IACnC,eAAe,EAAE,CAAC,cAAc,CAAC;IACjC,YAAY,EAAE,CAAC,gBAAgB,EAAE,YAAY,CAAC;IAC9C,OAAO,EAAE,CAAC,WAAW,CAAC;IACtB,eAAe,EAAE,CAAC,WAAW,CAAC;IAC9B,QAAQ,EAAE,CAAC,YAAY,CAAC;IACxB,YAAY,EAAE,CAAC,QAAQ,CAAC;IACxB,WAAW,EAAE,CAAC,OAAO,CAAC;IACtB,UAAU,EAAE,CAAC,cAAc,CAAC;IAC5B,KAAK,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC;IAC/B,SAAS,EAAE,CAAC,QAAQ,CAAC;IACrB,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,8DAA8D;IAC9D,WAAW,EAAE,CAAC,cAAc,CAAC,EAAM,iDAAiD;IACpF,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAY,gCAAgC;CACpE,CAAC;AAEF,MAAM,gBAAgB,GAAG;IACvB,WAAW;IACX,WAAW;IACX,aAAa;IACb,cAAc;CACN,CAAC;AAqBJ,KAAK,UAAU,SAAS,CAAC,MAAmB;IACjD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE7D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;wDACoD,EACpD,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,CAC9B,CAAC;IACF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,wCAAwC,SAAS,cAAc,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAa,CAAC;IAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAA4B,CAAC;IACxE,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAE5D,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAClC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,cAAc,EAAE,IAAI;YACpB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC;YACxC,YAAY,EAAE,EAAE;SACjB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,YAAY,GAA4B,EAAE,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,YAAY,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;SAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;SAC/B,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAElE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,OAAO,CAAC,GAAG,CACf;;;;;4CAKwC,aAAa,EAAE,EACvD;QACE,GAAG,EAAE,SAAS;QACd,SAAS;QACT,EAAE;QACF,MAAM,EAAE,MAAM,IAAI,IAAI;QACtB,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;KAC9C,CACF,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4BAA4B,SAAS,cAAc,SAAS,WAAW,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,MAAM,IAAI,MAAM,IAAI,CACpI,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAI;QACb,cAAc,EAAE,KAAK;QACrB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,UAAU;QAClB,SAAS;QACT,YAAY;KACb,CAAC;AACJ,CAAC;AAeM,KAAK,UAAU,WAAW,CAAC,MAAqB;IACrD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAEjD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;4DACwD,EACxD,EAAE,GAAG,EAAE,SAAS,EAAE,CACnB,CAAC;IACF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,kDAAkD,SAAS,GAAG,CAC/D,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAa,CAAC;IAC9D,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAkB,CAAC;IACpE,MAAM,YAAY,GAA4B,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnF,6EAA6E;IAC7E,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACrD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;gBAAE,SAAS;YAC5C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,kBAAkB,KAAK;;;yBAGN,CAAC;oDAC0B,EAC5C,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,CAC3B,CAAC;YACF,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAW,CAAC;gBAC7D,MAAM,IAAI,KAAK,CACb,+BAA+B,KAAK,cAAc,SAAS,4BAA4B,OAAO,kBAAkB,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CACzI,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;SACzC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;SACpC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,MAAM,SAAS,GAA4B,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;IAC9D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC;QAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAE7E,MAAM,OAAO,CAAC,GAAG,CACf;;oCAEgC,SAAS,EAAE,EAC3C,SAAS,CACV,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8BAA8B,SAAS,cAAc,SAAS,WAAW,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAClG,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,UAAU;QAClB,YAAY,EAAE,YAAY;KAC3B,CAAC;AACJ,CAAC;AA8BM,KAAK,UAAU,UAAU,CAAC,MAAwB;IACvD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,EAAE,EAAE,MAAM,GAAG,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAEpG,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAEpF,MAAM,WAAW,GAAG,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QACvD,CAAC,CAAC,iDAAiD;QACnD,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,GAAG,CACxC;;OAEG,WAAW;;;;;;8BAMY,EAC1B,EAAE,MAAM,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CACpD,CAAC;IAEF,MAAM,UAAU,GAAqB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACtE,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,UAAU,CAAkB,CAAC;QACpD,OAAO;YACL,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAW;YACjC,MAAM,EAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;YACpE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACrC,SAAS,EAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAmB,IAAI,IAAI;YACxD,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;SAClD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+BAA+B,SAAS,cAAc,SAAS,WAAW,MAAM,eAAe,UAAU,CAAC,MAAM,IAAI,CACrH,CAAC;IAEF,IAAI,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,4CAA4C,SAAS,WAAW,MAAM,cAAc,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CACpJ,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8DAA8D,CAAC,CAAC,SAAS,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACnI,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,OAAO,CAAC,GAAG,CACf,qDAAqD,EACrD,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,EAAE,CACrB,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QACnG,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6BAA6B,SAAS,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,SAAS,YAAY,OAAO,IAAI,CAC7I,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,gCAAgC,OAAO,cAAc,SAAS,WAAW,MAAM,cAAc,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAC7J,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,OAAO,UAAU,KAAK,oBAAoB,KAAK,uBAAuB,CAAC;AACzE,CAAC;AAED,qFAAqF;AACrF,SAAgB,mBAAmB,CAAC,MAAgB;IAClD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,KAAK,MAAM,CAAC,IAAI,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC;AAED,2FAA2F;AAC9E,QAAA,oBAAoB,GAAsB,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
* are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
|
|
14
14
|
* MERGE against the same key won't collide. `restoreNode` writes them back
|
|
15
15
|
* and fails loudly when an active node already occupies the slot.
|
|
16
|
+
*
|
|
17
|
+
* Label-aware cascade (Task 655): when `trashNode` trashes a `:Conversation`,
|
|
18
|
+
* it also trashes every `(m:Message)-[:PART_OF]->(c)` Message in the same
|
|
19
|
+
* managed transaction. This makes `MATCH (m:Trashed)` a correct audit
|
|
20
|
+
* primitive again — pre-Task-655 a Conversation trash left attached Messages
|
|
21
|
+
* un-labelled, so audits that filtered `m:Trashed` silently under-reported
|
|
22
|
+
* the blast radius of the 2026-04-22 incident by a factor of 10+.
|
|
23
|
+
* `restoreNode` reverses both sides.
|
|
16
24
|
*/
|
|
17
25
|
|
|
18
26
|
import type { Session } from "neo4j-driver";
|
|
@@ -118,25 +126,89 @@ export async function trashNode(params: TrashParams): Promise<TrashResult> {
|
|
|
118
126
|
const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
|
|
119
127
|
|
|
120
128
|
const trashedAt = new Date().toISOString();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
const isConversation = baseLabels.includes("Conversation");
|
|
130
|
+
// Message unique-key list computed once — matches UNIQUE_KEYS_BY_LABEL["Message"].
|
|
131
|
+
const messageUniqueKeys = UNIQUE_KEYS_BY_LABEL["Message"] ?? [];
|
|
132
|
+
|
|
133
|
+
// Root trash + Conversation→Message cascade in one managed transaction so a
|
|
134
|
+
// DB error mid-sweep can't leave a Conversation :Trashed with some Messages
|
|
135
|
+
// un-trashed (the 2026-04-22 audit correctness gap). For non-Conversation
|
|
136
|
+
// labels, the cascade branch is a no-op — same one-write shape as before.
|
|
137
|
+
let cascadedMessageCount = 0;
|
|
138
|
+
await session.executeWrite(async (tx) => {
|
|
139
|
+
await tx.run(
|
|
140
|
+
`MATCH (n) WHERE elementId(n) = $eid
|
|
141
|
+
SET n:Trashed,
|
|
142
|
+
n.trashedAt = datetime($trashedAt),
|
|
143
|
+
n.trashedBy = $by,
|
|
144
|
+
n.trashReason = $reason,
|
|
145
|
+
n._trashedKeys = $trashedKeysJson${setNullSuffix}`,
|
|
146
|
+
{
|
|
147
|
+
eid: elementId,
|
|
148
|
+
trashedAt,
|
|
149
|
+
by,
|
|
150
|
+
reason: reason ?? null,
|
|
151
|
+
trashedKeysJson: JSON.stringify(originalKeys),
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (isConversation) {
|
|
156
|
+
// Two-step cascade: collect Message elementIds and their unique-key
|
|
157
|
+
// values first, then apply :Trashed + snapshot + null in one SET per
|
|
158
|
+
// message using parameter-passed snapshot JSON. This keeps JSON
|
|
159
|
+
// construction in the Node process (where JSON.stringify is trivially
|
|
160
|
+
// correct) rather than building escaped JSON inside Cypher.
|
|
161
|
+
const collectKeys = messageUniqueKeys.length > 0
|
|
162
|
+
? messageUniqueKeys.map((k) => `\`${k}\`: m.\`${k}\``).join(", ")
|
|
163
|
+
: "";
|
|
164
|
+
const collectMsgProps = collectKeys ? `, {${collectKeys}}` : ", {}";
|
|
165
|
+
|
|
166
|
+
const collected = await tx.run(
|
|
167
|
+
`MATCH (c) WHERE elementId(c) = $eid
|
|
168
|
+
MATCH (m:Message)-[:PART_OF]->(c)
|
|
169
|
+
WHERE NOT m:Trashed
|
|
170
|
+
RETURN elementId(m) AS meid${collectMsgProps} AS keys`,
|
|
171
|
+
{ eid: elementId },
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
for (const rec of collected.records) {
|
|
175
|
+
const meid = rec.get("meid") as string;
|
|
176
|
+
const keys = rec.get("keys") as Record<string, unknown>;
|
|
177
|
+
const liveKeys: Record<string, unknown> = {};
|
|
178
|
+
for (const k of messageUniqueKeys) {
|
|
179
|
+
if (keys[k] !== undefined && keys[k] !== null) liveKeys[k] = keys[k];
|
|
180
|
+
}
|
|
181
|
+
const msgSetNulls = Object.keys(liveKeys).length > 0
|
|
182
|
+
? ", " + Object.keys(liveKeys).map((k) => `m.\`${k}\` = null`).join(", ")
|
|
183
|
+
: "";
|
|
184
|
+
await tx.run(
|
|
185
|
+
`MATCH (m) WHERE elementId(m) = $meid
|
|
186
|
+
SET m:Trashed,
|
|
187
|
+
m.trashedAt = datetime($trashedAt),
|
|
188
|
+
m.trashedBy = $by,
|
|
189
|
+
m.trashReason = $reason,
|
|
190
|
+
m._trashedKeys = $trashedKeysJson${msgSetNulls}`,
|
|
191
|
+
{
|
|
192
|
+
meid,
|
|
193
|
+
trashedAt,
|
|
194
|
+
by: `${by}:cascade-from-conversation`,
|
|
195
|
+
reason: reason ?? `cascade from Conversation ${elementId}`,
|
|
196
|
+
trashedKeysJson: JSON.stringify(liveKeys),
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
cascadedMessageCount = collected.records.length;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
136
203
|
|
|
137
204
|
process.stderr.write(
|
|
138
205
|
`[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}\n`,
|
|
139
206
|
);
|
|
207
|
+
if (isConversation) {
|
|
208
|
+
process.stderr.write(
|
|
209
|
+
`[trash:cascaded] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount} by=${by}\n`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
140
212
|
|
|
141
213
|
return {
|
|
142
214
|
trashed: true,
|
|
@@ -210,16 +282,59 @@ export async function restoreNode(params: RestoreParams): Promise<RestoreResult>
|
|
|
210
282
|
const setParams: Record<string, unknown> = { eid: elementId };
|
|
211
283
|
for (const [k, v] of Object.entries(originalKeys)) setParams[`val_${k}`] = v;
|
|
212
284
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
285
|
+
const isConversation = baseLabels.includes("Conversation");
|
|
286
|
+
let cascadedMessageCount = 0;
|
|
287
|
+
|
|
288
|
+
await session.executeWrite(async (tx) => {
|
|
289
|
+
await tx.run(
|
|
290
|
+
`MATCH (n:Trashed) WHERE elementId(n) = $eid
|
|
291
|
+
REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
|
|
292
|
+
SET n.restoredAt = datetime()${setSuffix}`,
|
|
293
|
+
setParams,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (isConversation) {
|
|
297
|
+
// Reverse of the trashNode cascade: re-label and restore unique-key
|
|
298
|
+
// snapshots for every `(m:Message)-[:PART_OF]->(c)` that was trashed
|
|
299
|
+
// by the same cascade chain (i.e. m._trashedKeys exists + m was
|
|
300
|
+
// trashed by a `*:cascade-from-conversation` provenance). We scope by
|
|
301
|
+
// the second condition so a user-initiated single-Message trash that
|
|
302
|
+
// happens to sit inside this Conversation isn't accidentally undone.
|
|
303
|
+
const collected = await tx.run(
|
|
304
|
+
`MATCH (c) WHERE elementId(c) = $eid
|
|
305
|
+
MATCH (m:Trashed:Message)-[:PART_OF]->(c)
|
|
306
|
+
WHERE m.trashedBy ENDS WITH ':cascade-from-conversation'
|
|
307
|
+
RETURN elementId(m) AS meid, m._trashedKeys AS keysJson`,
|
|
308
|
+
{ eid: elementId },
|
|
309
|
+
);
|
|
310
|
+
for (const rec of collected.records) {
|
|
311
|
+
const meid = rec.get("meid") as string;
|
|
312
|
+
const keysJson = rec.get("keysJson") as string | null;
|
|
313
|
+
const keys: Record<string, unknown> = keysJson ? JSON.parse(keysJson) : {};
|
|
314
|
+
const setClause = Object.keys(keys).length > 0
|
|
315
|
+
? ", " + Object.keys(keys).map((k) => `m.\`${k}\` = $val_${k}`).join(", ")
|
|
316
|
+
: "";
|
|
317
|
+
const msgParams: Record<string, unknown> = { meid };
|
|
318
|
+
for (const [k, v] of Object.entries(keys)) msgParams[`val_${k}`] = v;
|
|
319
|
+
await tx.run(
|
|
320
|
+
`MATCH (m) WHERE elementId(m) = $meid
|
|
321
|
+
REMOVE m:Trashed, m.trashedAt, m.trashedBy, m.trashReason, m._trashedKeys
|
|
322
|
+
SET m.restoredAt = datetime()${setClause}`,
|
|
323
|
+
msgParams,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
cascadedMessageCount = collected.records.length;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
219
329
|
|
|
220
330
|
process.stderr.write(
|
|
221
331
|
`[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}\n`,
|
|
222
332
|
);
|
|
333
|
+
if (isConversation) {
|
|
334
|
+
process.stderr.write(
|
|
335
|
+
`[trash:cascade-restored] accountId=${accountId} conversationElementId=${elementId} messageCount=${cascadedMessageCount}\n`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
223
338
|
|
|
224
339
|
return {
|
|
225
340
|
restored: true,
|
|
@@ -70,9 +70,11 @@ The logs will show which service failed to start and why. Common causes:
|
|
|
70
70
|
|
|
71
71
|
Each Maxy device runs one `--user` systemd unit:
|
|
72
72
|
|
|
73
|
-
- `maxy-ui.service` — the admin + public HTTP server
|
|
73
|
+
- `maxy-ui.service` — the admin + public HTTP server on `127.0.0.1:19199`. Restarted by the upgrade flow; short downtime is expected during steps 8→12 of an upgrade.
|
|
74
|
+
- `maxy-edge.service` — the always-on public listener on the configured port (default 19200). Reverse-proxies HTTP to `maxy-ui`, handles `/websockify` (VNC) and `/ttyd` (admin terminal) WebSocket upgrades locally. Does NOT restart during an upgrade — the browser WebSocket stays connected by construction.
|
|
75
|
+
- `maxy-ttyd.service` — `ttyd` bound to `127.0.0.1:7681`, running `tmux new-session -A -s maxy-pty`. Owns the byte-stream admin terminal rendered by xterm.js in the header overlay and the Software Update modal (Task 657). Independent of `maxy-ui` and `maxy-edge`; outlives service restarts so scrollback is preserved.
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
If the admin terminal fails to open, check `sudo tail -n 50 ~/.maxy/logs/edge-boot.log` — the `ttyd-ws-upgrade` / `ttyd-proxy-open` / `ttyd-proxy-close` lines carry a `corrId` that ties the full session lifecycle together. For unit health, `systemctl --user status maxy-ttyd` + `journalctl --user -u maxy-ttyd`.
|
|
76
78
|
|
|
77
79
|
## Upgrading
|
|
78
80
|
|
|
@@ -90,10 +90,14 @@ Conversations and Messages carry role/channel sublabels so you can read the chat
|
|
|
90
90
|
|
|
91
91
|
**Save a default view:** once you have the rows you want, click **Set default view** in the filter menu. Next time you open **Graph**, those rows are pre-selected and your data renders immediately. The default is per-admin, per-account — each admin on each account has their own.
|
|
92
92
|
|
|
93
|
-
**Delete a node:** drag it to the trash icon top-right of the canvas. No confirmation — deletes are reversible for 30 days. To restore, toggle **Show trashed** inside the filter menu and click **Restore** on the node, or ask Maxy in chat ("restore the <label> I just deleted").
|
|
93
|
+
**Delete a node:** drag it to the trash icon top-right of the canvas. No confirmation — deletes are reversible for 30 days. To restore, toggle **Show trashed** inside the filter menu and click **Restore** on the node, or ask Maxy in chat ("restore the <label> I just deleted"). Deleting a conversation also trashes its messages in the same step, so they reappear together on restore.
|
|
94
|
+
|
|
95
|
+
**Bulk cleanup of conversations in chat:** when you ask Maxy to clean up conversations in bulk ("trash all empty conversations," "clean up the single-assistant tests"), the agent uses a deterministic selector with a fixed set of filter names — it cannot author custom delete queries. The server re-runs the same filter on every candidate before it trashes, so a stale list can't destroy something the filter wouldn't match now. If the filter matches nothing, Maxy reports "no candidates" and nothing happens.
|
|
94
96
|
|
|
95
97
|
The page reads only your own brand's Neo4j — a Maxy device and a Real Agent device share no graph state even when on the same laptop. No credentials are required; the view inherits your admin session.
|
|
96
98
|
|
|
99
|
+
**Typo-proof cypher.** When Maxy runs direct Cypher to answer a relational question, the query is checked against your Neo4j's live label and relationship-type taxonomy before it executes. Cypher that references an unknown name (an edge or label that does not exist in your graph) is rejected for writes and flagged with a warnings header for reads, so Maxy never silently acts on a query that targeted the wrong set of nodes. You should not see this — it runs invisibly — but it is the safety net that stops a fabricated edge name from producing "empty" results that are really just unreachable. Before acting on a bulk operation Maxy surfaces the result count and a sample; if it ever describes a cypher rejection, that means its first attempt was malformed and it corrected itself.
|
|
100
|
+
|
|
97
101
|
## Privacy
|
|
98
102
|
|
|
99
103
|
All memory is stored on your local Raspberry Pi. The Neo4j database never leaves your network. Maxy does not sync memory to any cloud service or third party.
|