@rubytech/create-realagent 1.0.840 → 1.0.842
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/package.json +1 -1
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts +2 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js +88 -0
- package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts +23 -0
- package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/account-enumeration/dist/index.js +96 -0
- package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -0
- package/payload/platform/lib/account-enumeration/src/__tests__/enumerate.test.ts +94 -0
- package/payload/platform/lib/account-enumeration/src/index.ts +96 -0
- package/payload/platform/lib/account-enumeration/tsconfig.json +8 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts +2 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js +165 -0
- package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +15 -5
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.d.ts +12 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +25 -0
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/__tests__/account-id-gate.test.ts +189 -0
- package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +16 -5
- package/payload/platform/lib/graph-write/src/index.ts +45 -1
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/docs/references/attachments.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -0
- package/payload/server/adminuser-self-heal-RY4NFCI7.js +45 -0
- package/payload/server/chunk-2YG3AYAH.js +1508 -0
- package/payload/server/chunk-7DFOKDNM.js +2098 -0
- package/payload/server/chunk-DTWW35TK.js +667 -0
- package/payload/server/chunk-HTYXRFT6.js +727 -0
- package/payload/server/chunk-NPVEOM3D.js +1508 -0
- package/payload/server/chunk-QGM4M3NI.js +37 -0
- package/payload/server/chunk-S27QCBFQ.js +10071 -0
- package/payload/server/chunk-TS6CKCGU.js +727 -0
- package/payload/server/chunk-XECKT3YB.js +10071 -0
- package/payload/server/client-pool-2WQ2Q3TF.js +32 -0
- package/payload/server/client-pool-SMWCZMZG.js +32 -0
- package/payload/server/cloudflare-task-tracker-NQK7A2EQ.js +20 -0
- package/payload/server/cloudflare-task-tracker-O4ZA4TAS.js +20 -0
- package/payload/server/cloudflare-task-tracker-XFGXO7ZV.js +20 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{admin-D678VwpH.js → admin-CvwOOG4D.js} +1 -1
- package/payload/server/public/assets/{public-C7mCgRX0.js → public-Br9YjNs_.js} +2 -2
- package/payload/server/public/index.html +1 -1
- package/payload/server/public/public.html +1 -1
- package/payload/server/server.js +65 -41
|
@@ -6,6 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_test_1 = __importDefault(require("node:test"));
|
|
7
7
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
8
|
const index_js_1 = require("../index.js");
|
|
9
|
+
// Task 917: writeNodeWithEdges now rejects writes whose accountId does not
|
|
10
|
+
// match the spawning process's ACCOUNT_ID. These provenance-gate tests
|
|
11
|
+
// predate that gate; they pass a literal `expectedAccountId` so they
|
|
12
|
+
// exercise only the provenance gate they were written to test.
|
|
13
|
+
const TEST_ACCOUNT_ID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
9
14
|
function makeStubSession(opts) {
|
|
10
15
|
const calls = [];
|
|
11
16
|
const tx = {
|
|
@@ -87,9 +92,10 @@ function makeStubSession(opts) {
|
|
|
87
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
93
|
session: session,
|
|
89
94
|
labels: ["Person"],
|
|
90
|
-
props: { accountId:
|
|
95
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
91
96
|
relationships,
|
|
92
97
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
98
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
93
99
|
}), /missing-action-provenance|Process provenance/i);
|
|
94
100
|
});
|
|
95
101
|
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED-from-Task edge", async () => {
|
|
@@ -105,9 +111,10 @@ function makeStubSession(opts) {
|
|
|
105
111
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
112
|
session: session,
|
|
107
113
|
labels: ["Person"],
|
|
108
|
-
props: { accountId:
|
|
114
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
109
115
|
relationships,
|
|
110
116
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
117
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
111
118
|
});
|
|
112
119
|
strict_1.default.equal(res.edgesCreated, 1);
|
|
113
120
|
});
|
|
@@ -122,9 +129,10 @@ function makeStubSession(opts) {
|
|
|
122
129
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
130
|
session: session,
|
|
124
131
|
labels: ["Person"],
|
|
125
|
-
props: { accountId:
|
|
132
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
126
133
|
relationships,
|
|
127
134
|
createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
|
|
135
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
128
136
|
});
|
|
129
137
|
strict_1.default.equal(res.edgesCreated, 1);
|
|
130
138
|
});
|
|
@@ -143,9 +151,10 @@ function makeStubSession(opts) {
|
|
|
143
151
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
152
|
session: session,
|
|
145
153
|
labels: ["Person"],
|
|
146
|
-
props: { accountId:
|
|
154
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
147
155
|
relationships,
|
|
148
156
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
157
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
149
158
|
}), /missing-action-provenance|Process provenance/i);
|
|
150
159
|
});
|
|
151
160
|
(0, node_test_1.default)("writeNodeWithEdges accepts non-action-provenance label writes without Task edge", async () => {
|
|
@@ -159,9 +168,10 @@ function makeStubSession(opts) {
|
|
|
159
168
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
169
|
session: session,
|
|
161
170
|
labels: ["Question"], // not in ACTION_PROVENANCE_LABELS
|
|
162
|
-
props: { accountId:
|
|
171
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
163
172
|
relationships,
|
|
164
173
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
174
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
165
175
|
});
|
|
166
176
|
strict_1.default.equal(res.edgesCreated, 1);
|
|
167
177
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-provenance-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/action-provenance-gate.test.ts"],"names":[],"mappings":";;;;;AAAA,0DAA6B;AAC7B,gEAAwC;AACxC,0CAIqB;
|
|
1
|
+
{"version":3,"file":"action-provenance-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/action-provenance-gate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,6 +72,18 @@ export interface WriteNodeWithEdgesParams {
|
|
|
72
72
|
/** At least one relationship is required — zero-rel writes are rejected. */
|
|
73
73
|
relationships: GraphRelationship[];
|
|
74
74
|
createdBy: CreatedBy;
|
|
75
|
+
/**
|
|
76
|
+
* Override for the accountId gate (Task 917). Production callers leave
|
|
77
|
+
* this unset — the gate compares `props.accountId` against
|
|
78
|
+
* `process.env.ACCOUNT_ID`, which the MCP server / UI server validates at
|
|
79
|
+
* boot time. Tests pass an explicit value to avoid env coupling.
|
|
80
|
+
* Reading from env var (instead of an on-disk dir enumeration) keeps
|
|
81
|
+
* graph-write out of the `node:fs` import chain — the lib is bundled
|
|
82
|
+
* transitively into the ESM payload via cloudflare-task-tracker.ts, and
|
|
83
|
+
* any CJS-side fs require shims to a runtime-fatal `__require("fs")`
|
|
84
|
+
* after esbuild's CJS-to-ESM conversion strips the `node:` prefix.
|
|
85
|
+
*/
|
|
86
|
+
expectedAccountId?: string;
|
|
75
87
|
}
|
|
76
88
|
export interface WriteNodeResult {
|
|
77
89
|
nodeId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAQvD,CAAC;AAiBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAQvD,CAAC;AAiBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAqM1B"}
|
|
@@ -109,6 +109,31 @@ async function writeNodeWithEdges(params) {
|
|
|
109
109
|
const { session, labels, props, relationships, createdBy } = params;
|
|
110
110
|
const agentLabel = createdBy.agent ?? createdBy.source ?? "unknown";
|
|
111
111
|
const labelCsv = labels.join(",");
|
|
112
|
+
// Task 917: accountId floor. Every write whose author is not the bootstrap
|
|
113
|
+
// system MUST stamp `props.accountId` with the value of `ACCOUNT_ID`,
|
|
114
|
+
// which the spawning process (MCP server / UI server) validated at boot
|
|
115
|
+
// against the on-disk `${DATA_ROOT}/accounts/<id>/account.json` set. Read-
|
|
116
|
+
// side scoping silently hides leaks, so this gate is the live floor that
|
|
117
|
+
// Task 914's deletion of `pruneAlienAccounts` left to the writer surface.
|
|
118
|
+
// The on-disk enumeration happens once per process at boot (in the
|
|
119
|
+
// platform/lib/account-enumeration consumers — the WhatsApp helper and
|
|
120
|
+
// the boot-side `[graph-health] account-enumeration` log). The gate here
|
|
121
|
+
// verifies match against that pre-validated identity, keeping graph-write
|
|
122
|
+
// out of the `node:fs` import chain so the ESM payload bundle does not
|
|
123
|
+
// emit `__require("fs")` shims.
|
|
124
|
+
// Doctrine: `.docs/neo4j.md` "Account isolation invariant".
|
|
125
|
+
const isSystemBootstrap = (createdBy.agent ?? "") === "system";
|
|
126
|
+
if (!isSystemBootstrap) {
|
|
127
|
+
const accountId = props.accountId;
|
|
128
|
+
const expectedAccountId = params.expectedAccountId ?? process.env.ACCOUNT_ID;
|
|
129
|
+
if (typeof accountId !== "string" ||
|
|
130
|
+
!expectedAccountId ||
|
|
131
|
+
accountId !== expectedAccountId) {
|
|
132
|
+
const slice = typeof accountId === "string" ? accountId.slice(0, 8) : "missing";
|
|
133
|
+
process.stderr.write(`[graph-write] reject reason=invalid-account-id accountId=${slice} writer=${agentLabel}\n`);
|
|
134
|
+
throw new Error(`Write doctrine violated: invalid-account-id (${slice}) — accountId must equal ACCOUNT_ID set by the spawning process. See .docs/neo4j.md "Account isolation invariant".`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
112
137
|
// Task 884: review-detector / review-digest feature is removed entirely.
|
|
113
138
|
// Reject any re-introduction via the doctrine surface. The actionTool check
|
|
114
139
|
// is property-keyed (not label-keyed) so a future scheduled-Event writer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { writeNodeWithEdges, type GraphRelationship } from "../index.js";
|
|
4
|
+
|
|
5
|
+
interface StubTx {
|
|
6
|
+
run(cypher: string, params?: Record<string, unknown>): Promise<{
|
|
7
|
+
records: { get(field: string): unknown }[];
|
|
8
|
+
summary?: { counters: { updates(): { relationshipsCreated: number } } };
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function makeStubSession(targetId: string) {
|
|
13
|
+
const tx: StubTx = {
|
|
14
|
+
async run(cypher: string, params: Record<string, unknown> = {}) {
|
|
15
|
+
if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
|
|
16
|
+
const ids = (params.ids as string[]) ?? [];
|
|
17
|
+
const records = ids
|
|
18
|
+
.filter((id) => id === targetId)
|
|
19
|
+
.map((id) => ({
|
|
20
|
+
get(field: string) {
|
|
21
|
+
if (field === "id") return id;
|
|
22
|
+
if (field === "labels") return ["Account"];
|
|
23
|
+
throw new Error(`unknown field ${field}`);
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
return { records };
|
|
27
|
+
}
|
|
28
|
+
if (cypher.includes("CREATE (n:")) {
|
|
29
|
+
return {
|
|
30
|
+
records: [
|
|
31
|
+
{
|
|
32
|
+
get(field: string) {
|
|
33
|
+
if (field === "nodeId") return "new-element-id";
|
|
34
|
+
if (field === "nodeLabels") return ["Conversation"];
|
|
35
|
+
throw new Error(`unknown field ${field}`);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
|
|
42
|
+
return {
|
|
43
|
+
records: [],
|
|
44
|
+
summary: {
|
|
45
|
+
counters: {
|
|
46
|
+
updates: () => ({ relationshipsCreated: 1 }),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { records: [] };
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
async executeWrite<T>(work: (tx: StubTx) => Promise<T>): Promise<T> {
|
|
56
|
+
return await work(tx);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const VALID_UUID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
62
|
+
const TARGET_ID = "target-element-id";
|
|
63
|
+
|
|
64
|
+
const REL: GraphRelationship[] = [
|
|
65
|
+
{ type: "PART_OF", direction: "outgoing", targetNodeId: TARGET_ID },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function captureStderr(): { restore: () => void; output: () => string } {
|
|
69
|
+
const original = process.stderr.write.bind(process.stderr);
|
|
70
|
+
let buf = "";
|
|
71
|
+
(process.stderr as { write: (chunk: string | Uint8Array) => boolean }).write =
|
|
72
|
+
(chunk: string | Uint8Array): boolean => {
|
|
73
|
+
buf += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
restore: () => {
|
|
78
|
+
process.stderr.write = original;
|
|
79
|
+
},
|
|
80
|
+
output: () => buf,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("rejects null accountId from non-system writer", async () => {
|
|
85
|
+
const cap = captureStderr();
|
|
86
|
+
try {
|
|
87
|
+
await assert.rejects(
|
|
88
|
+
() =>
|
|
89
|
+
writeNodeWithEdges({
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
|
+
session: makeStubSession(TARGET_ID) as any,
|
|
92
|
+
labels: ["Conversation"],
|
|
93
|
+
props: { accountId: null },
|
|
94
|
+
relationships: REL,
|
|
95
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
96
|
+
expectedAccountId: VALID_UUID,
|
|
97
|
+
}),
|
|
98
|
+
/invalid-account-id/,
|
|
99
|
+
);
|
|
100
|
+
cap.restore();
|
|
101
|
+
assert.match(
|
|
102
|
+
cap.output(),
|
|
103
|
+
/\[graph-write\] reject reason=invalid-account-id accountId=missing writer=test-agent\b/,
|
|
104
|
+
);
|
|
105
|
+
} finally {
|
|
106
|
+
cap.restore();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("rejects non-UUID accountId from non-system writer", async () => {
|
|
111
|
+
const cap = captureStderr();
|
|
112
|
+
try {
|
|
113
|
+
await assert.rejects(
|
|
114
|
+
() =>
|
|
115
|
+
writeNodeWithEdges({
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
session: makeStubSession(TARGET_ID) as any,
|
|
118
|
+
labels: ["Conversation"],
|
|
119
|
+
props: { accountId: "evil-leak" },
|
|
120
|
+
relationships: REL,
|
|
121
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
122
|
+
expectedAccountId: VALID_UUID,
|
|
123
|
+
}),
|
|
124
|
+
/invalid-account-id/,
|
|
125
|
+
);
|
|
126
|
+
cap.restore();
|
|
127
|
+
assert.match(
|
|
128
|
+
cap.output(),
|
|
129
|
+
/\[graph-write\] reject reason=invalid-account-id accountId=evil-lea writer=test-agent\b/,
|
|
130
|
+
);
|
|
131
|
+
} finally {
|
|
132
|
+
cap.restore();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("rejects UUID-shape accountId that does not match the expected one", async () => {
|
|
137
|
+
const stranger = "ffffffff-1111-2222-3333-444444444444";
|
|
138
|
+
const cap = captureStderr();
|
|
139
|
+
try {
|
|
140
|
+
await assert.rejects(
|
|
141
|
+
() =>
|
|
142
|
+
writeNodeWithEdges({
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
+
session: makeStubSession(TARGET_ID) as any,
|
|
145
|
+
labels: ["Conversation"],
|
|
146
|
+
props: { accountId: stranger },
|
|
147
|
+
relationships: REL,
|
|
148
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
149
|
+
expectedAccountId: VALID_UUID,
|
|
150
|
+
}),
|
|
151
|
+
/invalid-account-id/,
|
|
152
|
+
);
|
|
153
|
+
cap.restore();
|
|
154
|
+
assert.match(
|
|
155
|
+
cap.output(),
|
|
156
|
+
/\[graph-write\] reject reason=invalid-account-id accountId=ffffffff writer=test-agent\b/,
|
|
157
|
+
);
|
|
158
|
+
} finally {
|
|
159
|
+
cap.restore();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("accepts matching accountId from non-system writer", async () => {
|
|
164
|
+
const result = await writeNodeWithEdges({
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
+
session: makeStubSession(TARGET_ID) as any,
|
|
167
|
+
labels: ["Conversation"],
|
|
168
|
+
props: { accountId: VALID_UUID },
|
|
169
|
+
relationships: REL,
|
|
170
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
171
|
+
expectedAccountId: VALID_UUID,
|
|
172
|
+
});
|
|
173
|
+
assert.equal(result.nodeId, "new-element-id");
|
|
174
|
+
assert.equal(result.edgesCreated, 1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("system bootstrap exemption: bad accountId still proceeds", async () => {
|
|
178
|
+
const result = await writeNodeWithEdges({
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
session: makeStubSession(TARGET_ID) as any,
|
|
181
|
+
labels: ["Conversation"],
|
|
182
|
+
// accountId is intentionally wrong; system bootstrap exempts it.
|
|
183
|
+
props: { accountId: "not-a-uuid" },
|
|
184
|
+
relationships: REL,
|
|
185
|
+
createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
|
|
186
|
+
expectedAccountId: VALID_UUID,
|
|
187
|
+
});
|
|
188
|
+
assert.equal(result.nodeId, "new-element-id");
|
|
189
|
+
});
|
|
@@ -6,6 +6,12 @@ import {
|
|
|
6
6
|
type GraphRelationship,
|
|
7
7
|
} from "../index.js";
|
|
8
8
|
|
|
9
|
+
// Task 917: writeNodeWithEdges now rejects writes whose accountId does not
|
|
10
|
+
// match the spawning process's ACCOUNT_ID. These provenance-gate tests
|
|
11
|
+
// predate that gate; they pass a literal `expectedAccountId` so they
|
|
12
|
+
// exercise only the provenance gate they were written to test.
|
|
13
|
+
const TEST_ACCOUNT_ID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
14
|
+
|
|
9
15
|
// Stub session/tx — captures Cypher calls and returns canned results.
|
|
10
16
|
interface StubTx {
|
|
11
17
|
run(cypher: string, params?: Record<string, unknown>): Promise<{
|
|
@@ -101,9 +107,10 @@ test("writeNodeWithEdges rejects :Person write missing PRODUCED-from-Task edge",
|
|
|
101
107
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
108
|
session: session as any,
|
|
103
109
|
labels: ["Person"],
|
|
104
|
-
props: { accountId:
|
|
110
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
105
111
|
relationships,
|
|
106
112
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
113
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
107
114
|
}),
|
|
108
115
|
/missing-action-provenance|Process provenance/i,
|
|
109
116
|
);
|
|
@@ -122,9 +129,10 @@ test("writeNodeWithEdges accepts :Person write with PRODUCED-from-Task edge", as
|
|
|
122
129
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
130
|
session: session as any,
|
|
124
131
|
labels: ["Person"],
|
|
125
|
-
props: { accountId:
|
|
132
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
126
133
|
relationships,
|
|
127
134
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
135
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
128
136
|
});
|
|
129
137
|
assert.equal(res.edgesCreated, 1);
|
|
130
138
|
});
|
|
@@ -140,9 +148,10 @@ test("writeNodeWithEdges accepts :Person write with no Task edge when agent='sys
|
|
|
140
148
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
149
|
session: session as any,
|
|
142
150
|
labels: ["Person"],
|
|
143
|
-
props: { accountId:
|
|
151
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
144
152
|
relationships,
|
|
145
153
|
createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
|
|
154
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
146
155
|
});
|
|
147
156
|
assert.equal(res.edgesCreated, 1);
|
|
148
157
|
});
|
|
@@ -164,9 +173,10 @@ test("writeNodeWithEdges rejects :Person write whose 'PRODUCED-incoming' source
|
|
|
164
173
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
174
|
session: session as any,
|
|
166
175
|
labels: ["Person"],
|
|
167
|
-
props: { accountId:
|
|
176
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
168
177
|
relationships,
|
|
169
178
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
179
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
170
180
|
}),
|
|
171
181
|
/missing-action-provenance|Process provenance/i,
|
|
172
182
|
);
|
|
@@ -183,9 +193,10 @@ test("writeNodeWithEdges accepts non-action-provenance label writes without Task
|
|
|
183
193
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
194
|
session: session as any,
|
|
185
195
|
labels: ["Question"], // not in ACTION_PROVENANCE_LABELS
|
|
186
|
-
props: { accountId:
|
|
196
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
187
197
|
relationships,
|
|
188
198
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
199
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
189
200
|
});
|
|
190
201
|
assert.equal(res.edgesCreated, 1);
|
|
191
202
|
});
|
|
@@ -79,7 +79,6 @@ function findProducedFromTaskCandidates(
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
import type { Session } from "neo4j-driver";
|
|
82
|
-
|
|
83
82
|
export interface GraphRelationship {
|
|
84
83
|
type: string;
|
|
85
84
|
direction: "outgoing" | "incoming";
|
|
@@ -104,6 +103,18 @@ export interface WriteNodeWithEdgesParams {
|
|
|
104
103
|
/** At least one relationship is required — zero-rel writes are rejected. */
|
|
105
104
|
relationships: GraphRelationship[];
|
|
106
105
|
createdBy: CreatedBy;
|
|
106
|
+
/**
|
|
107
|
+
* Override for the accountId gate (Task 917). Production callers leave
|
|
108
|
+
* this unset — the gate compares `props.accountId` against
|
|
109
|
+
* `process.env.ACCOUNT_ID`, which the MCP server / UI server validates at
|
|
110
|
+
* boot time. Tests pass an explicit value to avoid env coupling.
|
|
111
|
+
* Reading from env var (instead of an on-disk dir enumeration) keeps
|
|
112
|
+
* graph-write out of the `node:fs` import chain — the lib is bundled
|
|
113
|
+
* transitively into the ESM payload via cloudflare-task-tracker.ts, and
|
|
114
|
+
* any CJS-side fs require shims to a runtime-fatal `__require("fs")`
|
|
115
|
+
* after esbuild's CJS-to-ESM conversion strips the `node:` prefix.
|
|
116
|
+
*/
|
|
117
|
+
expectedAccountId?: string;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
export interface WriteNodeResult {
|
|
@@ -140,6 +151,39 @@ export async function writeNodeWithEdges(
|
|
|
140
151
|
const agentLabel = createdBy.agent ?? createdBy.source ?? "unknown";
|
|
141
152
|
const labelCsv = labels.join(",");
|
|
142
153
|
|
|
154
|
+
// Task 917: accountId floor. Every write whose author is not the bootstrap
|
|
155
|
+
// system MUST stamp `props.accountId` with the value of `ACCOUNT_ID`,
|
|
156
|
+
// which the spawning process (MCP server / UI server) validated at boot
|
|
157
|
+
// against the on-disk `${DATA_ROOT}/accounts/<id>/account.json` set. Read-
|
|
158
|
+
// side scoping silently hides leaks, so this gate is the live floor that
|
|
159
|
+
// Task 914's deletion of `pruneAlienAccounts` left to the writer surface.
|
|
160
|
+
// The on-disk enumeration happens once per process at boot (in the
|
|
161
|
+
// platform/lib/account-enumeration consumers — the WhatsApp helper and
|
|
162
|
+
// the boot-side `[graph-health] account-enumeration` log). The gate here
|
|
163
|
+
// verifies match against that pre-validated identity, keeping graph-write
|
|
164
|
+
// out of the `node:fs` import chain so the ESM payload bundle does not
|
|
165
|
+
// emit `__require("fs")` shims.
|
|
166
|
+
// Doctrine: `.docs/neo4j.md` "Account isolation invariant".
|
|
167
|
+
const isSystemBootstrap = (createdBy.agent ?? "") === "system";
|
|
168
|
+
if (!isSystemBootstrap) {
|
|
169
|
+
const accountId = props.accountId;
|
|
170
|
+
const expectedAccountId = params.expectedAccountId ?? process.env.ACCOUNT_ID;
|
|
171
|
+
if (
|
|
172
|
+
typeof accountId !== "string" ||
|
|
173
|
+
!expectedAccountId ||
|
|
174
|
+
accountId !== expectedAccountId
|
|
175
|
+
) {
|
|
176
|
+
const slice =
|
|
177
|
+
typeof accountId === "string" ? accountId.slice(0, 8) : "missing";
|
|
178
|
+
process.stderr.write(
|
|
179
|
+
`[graph-write] reject reason=invalid-account-id accountId=${slice} writer=${agentLabel}\n`,
|
|
180
|
+
);
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Write doctrine violated: invalid-account-id (${slice}) — accountId must equal ACCOUNT_ID set by the spawning process. See .docs/neo4j.md "Account isolation invariant".`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
143
187
|
// Task 884: review-detector / review-digest feature is removed entirely.
|
|
144
188
|
// Reject any re-introduction via the doctrine surface. The actionTool check
|
|
145
189
|
// is property-keyed (not label-keyed) so a future scheduled-Event writer
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"plugins/*/mcp"
|
|
7
7
|
],
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
-
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json",
|
|
9
|
+
"build": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
|
|
10
|
+
"build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/anthropic-key/tsconfig.json && tsc -p lib/oauth-llm/tsconfig.json && tsc -p lib/mcp-stderr-tee/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/screening-patterns/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json",
|
|
11
11
|
"build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
|
|
12
12
|
"build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
|
|
13
13
|
"build:telegram": "tsc -p plugins/telegram/mcp/tsconfig.json",
|
|
@@ -17,7 +17,7 @@ Anything else is refused at upload time with a message naming the type.
|
|
|
17
17
|
|
|
18
18
|
## Size caps
|
|
19
19
|
|
|
20
|
-
- **Per file:**
|
|
20
|
+
- **Per file:** 50 MB. Enforced at the upload endpoint — files over this limit never reach disk.
|
|
21
21
|
- **Per message:** up to 5 files.
|
|
22
22
|
- **Uncompressed contents of a single zip:** 100 MB. A zip whose declared uncompressed total is over this limit is refused before any byte is extracted (decompression-bomb guard).
|
|
23
23
|
|
|
@@ -37,7 +37,7 @@ Nothing is ingested, sent, or acted on automatically. The extraction is local an
|
|
|
37
37
|
- `tar`, `tar.gz`, `7z`, `rar` — zip only. If you have one of these, unzip/convert locally and upload the zip (or the extracted files directly).
|
|
38
38
|
- Nested archives — a zip-inside-a-zip is extracted one level; you can ask the agent to unpack the inner one afterwards.
|
|
39
39
|
- Password-protected zips — the agent will tell you to unlock locally and re-upload.
|
|
40
|
-
- Uploads larger than
|
|
40
|
+
- Uploads larger than 50 MB — split the archive, or upload the individual files.
|
|
41
41
|
|
|
42
42
|
## Where the files live
|
|
43
43
|
|
|
@@ -140,7 +140,7 @@ WHERE node.accountId = $accountId
|
|
|
140
140
|
|
|
141
141
|
Multi-tenancy boundary. Every query is scoped to the requesting account. The `ACCOUNT_ID` environment variable is set at MCP server startup — it is not a tool parameter and cannot be overridden by the agent.
|
|
142
142
|
|
|
143
|
-
The read filter alone is not sufficient — it correctly *hides* alien-account nodes from every UI but does not prevent them existing. A writer that misresolves `accountId` (literal, undefined, or inferred-from-the-wrong-context) leaks nodes into the graph with no downstream symptom; the read filter then keeps them invisible indefinitely. The write-side doctrine is documented in `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write.
|
|
143
|
+
The read filter alone is not sufficient — it correctly *hides* alien-account nodes from every UI but does not prevent them existing. A writer that misresolves `accountId` (literal, undefined, or inferred-from-the-wrong-context) leaks nodes into the graph with no downstream symptom; the read filter then keeps them invisible indefinitely. The write-side doctrine is documented in `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The live floor is `writeNodeWithEdges` — every doctrine-primitive write is gated by an `accountId == process.env.ACCOUNT_ID` check (the spawning process validates `ACCOUNT_ID` at boot against the on-disk account set via the `account-enumeration` lib), with `[graph-write] reject reason=invalid-account-id …` as the rejection signal.
|
|
144
144
|
|
|
145
145
|
---
|
|
146
146
|
|
|
@@ -58,7 +58,7 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
58
58
|
- Platform process has stopped — restart it
|
|
59
59
|
- Network issue if accessing remotely — check your Cloudflare tunnel is running
|
|
60
60
|
|
|
61
|
-
**If the chat shows a single `[agent-loop-stop] same error twice — aborting` line and stops:** {{productName}} hit the same structured tool failure twice in a row inside one turn (e.g. a permission gate refused the same write twice). The runtime aborted the turn after the second occurrence to save tokens instead of running until the SDK turn budget exhausted. The blocker text names the tool and the first line of the error. Resolve the underlying cause (re-run the named skill, fix the missing prerequisite, etc.) and send your next message — the next turn cold-starts with a fresh client. To see the diagnostic, ask {{productName}}: "Show me the most recent agent-loop-stop log line."
|
|
61
|
+
**If the chat shows a single `[agent-loop-stop] same error twice — aborting` line and stops:** {{productName}} hit the same structured tool failure twice in a row inside one turn (e.g. a permission gate refused the same write twice, or two `Read` calls hit the same missing file). The runtime aborted the turn after the second occurrence to save tokens instead of running until the SDK turn budget exhausted. The blocker text names the tool and the first line of the error. Resolve the underlying cause (re-run the named skill, fix the missing prerequisite, etc.) and send your next message — the next turn cold-starts with a fresh client AND carries a recovery briefing so {{productName}} picks up where it aborted, instead of cold-querying its own session list. To see the diagnostic, ask {{productName}}: "Show me the most recent agent-loop-stop log line." Greppable post-deploy invariants: `[agent-loop-stop] reason=identical-tool-failure tool=<name> errorSignature=<sha8> toolInputDigest=<sha8>` (the `toolInputDigest` discriminator means two `Read` calls on different `file_path` no longer collapse to one signature) followed by paired `[recovery-handoff] generated reason=agent-loop-stop` and `[recovery-handoff] consumed reason=agent-loop-stop`. A `[recovery-handoff] WARN missing-on-cold-create` line means the recovery briefing wasn't persisted — surface to support.
|
|
62
62
|
|
|
63
63
|
**Agent searches the filesystem after uploading a zip.** If you uploaded a zip and the agent burns several turns running `find` / `Glob` instead of unzipping, that is the symptom of the recovery-retry attachment-context regression (now closed by the recovery context preservation contract in `.docs/agents.md`). Greppable confirmation is the `[context-overflow-recovery] retry … attachmentsCarried=<n>` line in the conversation stream log. If you see `[context-overflow-recovery] WARN attachment-context-lost`, the regression has returned — surface to support.
|
|
64
64
|
|
|
@@ -53,7 +53,7 @@ When per-group activation is `mention`, the agent fires only if the inbound mess
|
|
|
53
53
|
|
|
54
54
|
Every `messages.upsert` event (both `notify` and `append`, both `fromMe` directions) writes a `:Message:WhatsAppMessage` row to Neo4j attached to the sessionKey-keyed `:Conversation`. A single capture site at `platform/ui/app/lib/whatsapp/manager.ts` covers inbound, outbound (Baileys echoes agent-sent messages back through `messages.upsert` with `fromMe=true`), and owner-mirror — without touching `outbound/send.ts`. `messageId` namespace is `whatsapp-live:<waName>:<remoteJid>:<msg.key.id>` where `<waName>` is the Baileys credential dirname (e.g. `default`); distinct from the `:Section:Conversation` chunks written by the source-agnostic `conversation-archive` skill — live and archive live in disjoint label spaces. Persist failures are loud (`[whatsapp-persist] FAIL …`) and never block dispatch — silent loss is the worse failure mode.
|
|
55
55
|
|
|
56
|
-
**`accountId` contract.** `n.accountId` on every `:Conversation`, `:Person`, and `:Message:WhatsAppMessage` row stamped by this plugin is the **platform-side UUID** resolved by [`resolvePlatformAccountId()`](../../ui/app/lib/whatsapp/platform-account-id.ts) from `data/accounts/<uuid>/account.json` — NOT the Baileys credential dirname (which is only used as the `messageId`/`sessionKey` namespace token). The boot-time line `[whatsapp-persist] resolved-account-id waname=<dir> uuid=<uuid>` records the resolution. Doctrine: see `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The helper loud-throws on zero or multi accounts (Phase 0 single-account invariant), aborting the WhatsApp connection start before any write can occur.
|
|
56
|
+
**`accountId` contract.** `n.accountId` on every `:Conversation`, `:Person`, and `:Message:WhatsAppMessage` row stamped by this plugin is the **platform-side UUID** resolved by [`resolvePlatformAccountId()`](../../ui/app/lib/whatsapp/platform-account-id.ts) from `data/accounts/<uuid>/account.json` — NOT the Baileys credential dirname (which is only used as the `messageId`/`sessionKey` namespace token). The boot-time line `[whatsapp-persist] resolved-account-id waname=<dir> uuid=<uuid>` records the resolution. Doctrine: see `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The helper loud-throws on zero or multi accounts (Phase 0 single-account invariant), aborting the WhatsApp connection start before any write can occur. The same boot-validated identity (`process.env.ACCOUNT_ID`) backs the central live floor at [`writeNodeWithEdges`](../../lib/graph-write/src/index.ts) — any write whose `accountId` differs from the spawning process's `ACCOUNT_ID` is rejected by the gate; the WhatsApp helper is the writer-side discipline, the gate is the universal floor.
|
|
57
57
|
|
|
58
58
|
## Skills
|
|
59
59
|
|
|
@@ -121,6 +121,8 @@ Operationalises the CONCISE prerogative for clarification. Three rules:
|
|
|
121
121
|
|
|
122
122
|
## Tool Routing
|
|
123
123
|
|
|
124
|
+
**Bundled-SKILL access contract.** Plugin skills and references live in the bundled npm package, not on disk under `<accountDir>/agents/admin/plugins/`. Load them with `mcp__admin__plugin-read` only — the `<plugin-manifest>` `Skills:` and `References:` lines name the exact `pluginName` and file path to pass. `Read`, `Glob`, and `Bash` against those paths will fail with `File does not exist`, and the runtime agent-loop-stop interceptor cannot distinguish a generic-error retry from a tool-routing mistake. Subagents inherit this contract via the parent system prompt — never dispatch an `Agent` subagent with a `Read` directive against a bundled SKILL path.
|
|
125
|
+
|
|
124
126
|
Plugins provide domain-specific tools that query their own data stores directly. `memory-search` is a general-purpose semantic search across the entire knowledge graph — it finds nodes by vector similarity, which means results are ranked by semantic closeness to the query, not by domain relevance. A query containing the word "email" will surface product documentation *about* email features before it surfaces actual Email nodes whose content is unrelated to the query wording.
|
|
125
127
|
|
|
126
128
|
When the user's intent maps to a specific plugin's domain, use that plugin's tools — not `memory-search`. The `<plugin-manifest>` groups tools by plugin and describes each plugin's purpose and retrieval paths. The `<specialist-domains>` block within it lists every specialist-owned tool by name. Match user intent to a tool in these registries first; fall back to `memory-search` only when the query genuinely spans multiple domains or no tool in the manifest matches the intent.
|
|
@@ -245,6 +247,8 @@ When `<previous-context>` is present:
|
|
|
245
247
|
|
|
246
248
|
When `<previous-context>` is absent, Neo4j was unreachable or no prior context exists — proceed normally, using tool calls to establish state.
|
|
247
249
|
|
|
250
|
+
A separate `<recovery-context>` block on the user-message side appears only when the previous turn was aborted by `agent-loop-stop` or `main-stream-stalled` and the platform persisted a continuation briefing. Treat it as the authoritative description of what failed and what was incomplete — do not re-execute the failed work, do not call `session-list` to figure out what was happening, and do not re-research the blocker. The block coexists with `<previous-context>` (system-prompt session summary) on the recovery turn; the two are not duplicates — `<previous-context>` orients you to the session, `<recovery-context>` orients you to the specific failed turn.
|
|
251
|
+
|
|
248
252
|
In managed context mode, conversation history is provided within `<conversation-history>` tags. Use `session-compact-status` to retrieve older archived context if needed.
|
|
249
253
|
|
|
250
254
|
## Tasks
|