@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
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enumerate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/enumerate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const index_js_1 = require("../index.js");
|
|
12
|
+
function makeFixture() {
|
|
13
|
+
const root = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "acct-enum-"));
|
|
14
|
+
// (a) non-UUID dir → excluded.
|
|
15
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, "not-a-uuid"));
|
|
16
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "not-a-uuid", "account.json"), "{}");
|
|
17
|
+
// (b) UUID dir, no account.json → excluded.
|
|
18
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, "11111111-2222-3333-4444-555555555555"));
|
|
19
|
+
// (c) UUID dir, unparseable account.json → excluded (over-prune doctrine).
|
|
20
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"));
|
|
21
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "account.json"), "{ broken json");
|
|
22
|
+
// (d) UUID dir, parseable account.json → included.
|
|
23
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, "12345678-9abc-def0-1234-56789abcdef0"));
|
|
24
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "12345678-9abc-def0-1234-56789abcdef0", "account.json"), JSON.stringify({ id: "12345678-9abc-def0-1234-56789abcdef0" }));
|
|
25
|
+
return root;
|
|
26
|
+
}
|
|
27
|
+
(0, node_test_1.default)("enumerateValidAccountIds returns only UUID dirs with parseable account.json", () => {
|
|
28
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
29
|
+
const root = makeFixture();
|
|
30
|
+
try {
|
|
31
|
+
const result = (0, index_js_1.enumerateValidAccountIds)(root);
|
|
32
|
+
strict_1.default.deepEqual(result, ["12345678-9abc-def0-1234-56789abcdef0"]);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
(0, node_test_1.default)("enumerateValidAccountIds returns [] when accountsDir does not exist", () => {
|
|
39
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
40
|
+
const ghost = (0, node_path_1.join)((0, node_os_1.tmpdir)(), "acct-enum-does-not-exist-" + Date.now());
|
|
41
|
+
strict_1.default.deepEqual((0, index_js_1.enumerateValidAccountIds)(ghost), []);
|
|
42
|
+
});
|
|
43
|
+
(0, node_test_1.default)("enumerateValidAccountIds caches per accountsDir; reset re-reads", () => {
|
|
44
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
45
|
+
const root = makeFixture();
|
|
46
|
+
try {
|
|
47
|
+
const first = (0, index_js_1.enumerateValidAccountIds)(root);
|
|
48
|
+
// Add a second valid dir AFTER first call; cached call must NOT see it.
|
|
49
|
+
const newId = "99999999-aaaa-bbbb-cccc-dddddddddddd";
|
|
50
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, newId));
|
|
51
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, newId, "account.json"), "{}");
|
|
52
|
+
const cached = (0, index_js_1.enumerateValidAccountIds)(root);
|
|
53
|
+
strict_1.default.deepEqual(cached, first, "cache should hide the newly-added dir");
|
|
54
|
+
(0, index_js_1._resetEnumerationCache)();
|
|
55
|
+
const fresh = (0, index_js_1.enumerateValidAccountIds)(root);
|
|
56
|
+
strict_1.default.equal(fresh.length, 2);
|
|
57
|
+
strict_1.default.ok(fresh.includes(newId));
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
(0, node_fs_1.rmSync)(root, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
(0, node_test_1.default)("getAccountsDirFromEnv throws when MAXY_PLATFORM_ROOT is unset", () => {
|
|
64
|
+
const saved = process.env.MAXY_PLATFORM_ROOT;
|
|
65
|
+
delete process.env.MAXY_PLATFORM_ROOT;
|
|
66
|
+
try {
|
|
67
|
+
strict_1.default.throws(() => (0, index_js_1.getAccountsDirFromEnv)(), /MAXY_PLATFORM_ROOT not set/);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
if (saved !== undefined)
|
|
71
|
+
process.env.MAXY_PLATFORM_ROOT = saved;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
(0, node_test_1.default)("getAccountsDirFromEnv resolves to ${root}/../data/accounts when set", () => {
|
|
75
|
+
const saved = process.env.MAXY_PLATFORM_ROOT;
|
|
76
|
+
process.env.MAXY_PLATFORM_ROOT = "/tmp/fake-root/platform";
|
|
77
|
+
try {
|
|
78
|
+
const result = (0, index_js_1.getAccountsDirFromEnv)();
|
|
79
|
+
strict_1.default.equal(result, (0, node_path_1.resolve)("/tmp/fake-root/platform", "..", "data/accounts"));
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (saved !== undefined)
|
|
83
|
+
process.env.MAXY_PLATFORM_ROOT = saved;
|
|
84
|
+
else
|
|
85
|
+
delete process.env.MAXY_PLATFORM_ROOT;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=enumerate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enumerate.test.js","sourceRoot":"","sources":["../../src/__tests__/enumerate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the list of valid account UUIDs found under `accountsDir`. A dir is
|
|
3
|
+
* "valid" iff its name is a UUID AND it contains a parseable `account.json`.
|
|
4
|
+
* Corruption-discipline: a present-but-unparseable account.json EXCLUDES the
|
|
5
|
+
* dir (over-prune one suspect account beats under-prune the leak it might
|
|
6
|
+
* be hiding).
|
|
7
|
+
*/
|
|
8
|
+
export declare function enumerateValidAccountIds(accountsDir: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the canonical accounts dir from `MAXY_PLATFORM_ROOT`. Mirrors the
|
|
11
|
+
* pattern at platform/ui/app/lib/claude-agent/account.ts:9
|
|
12
|
+
* (`ACCOUNTS_DIR = resolve(PLATFORM_ROOT, "..", "data/accounts")`).
|
|
13
|
+
*
|
|
14
|
+
* Loud-throws when the env var is unset — graph-write's gate must not fall
|
|
15
|
+
* open silently.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getAccountsDirFromEnv(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Test-only cache reset. Production callers must not invoke this — the cache
|
|
20
|
+
* is a deliberate boot-time invariant (see module doc).
|
|
21
|
+
*/
|
|
22
|
+
export declare function _resetEnumerationCache(): void;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAgCtE;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAS9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enumerateValidAccountIds = enumerateValidAccountIds;
|
|
4
|
+
exports.getAccountsDirFromEnv = getAccountsDirFromEnv;
|
|
5
|
+
exports._resetEnumerationCache = _resetEnumerationCache;
|
|
6
|
+
/**
|
|
7
|
+
* account-enumeration — single source of truth for "which accountIds are
|
|
8
|
+
* provisioned on disk for this install?".
|
|
9
|
+
*
|
|
10
|
+
* Doctrine — `.docs/neo4j.md` "Account isolation invariant": every writer
|
|
11
|
+
* that stamps `n.accountId` must verify the value against
|
|
12
|
+
* `${DATA_ROOT}/accounts/<id>/account.json` before write. Read-side scoping
|
|
13
|
+
* (`WHERE n.accountId = $accountId`) silently hides leaks from every UI;
|
|
14
|
+
* an unenforced writer can leak indefinitely with zero downstream symptom.
|
|
15
|
+
*
|
|
16
|
+
* Two consumers:
|
|
17
|
+
* 1. `writeNodeWithEdges` (platform/lib/graph-write/) — central write-time
|
|
18
|
+
* floor; rejects any write whose `props.accountId` is not in the set
|
|
19
|
+
* returned here, unless `createdBy.agent === 'system'` (bootstrap).
|
|
20
|
+
* 2. `resolvePlatformAccountId` (platform/ui/app/lib/whatsapp/platform-account-id.ts)
|
|
21
|
+
* — WhatsApp writer-side helper; loud-throws on zero or multi accounts.
|
|
22
|
+
*
|
|
23
|
+
* Per-process cache keyed on `accountsDir` — Phase 0 invariant has accounts
|
|
24
|
+
* created at install time, so adding/removing accounts requires a service
|
|
25
|
+
* restart anyway.
|
|
26
|
+
*/
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
+
const cache = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* Returns the list of valid account UUIDs found under `accountsDir`. A dir is
|
|
33
|
+
* "valid" iff its name is a UUID AND it contains a parseable `account.json`.
|
|
34
|
+
* Corruption-discipline: a present-but-unparseable account.json EXCLUDES the
|
|
35
|
+
* dir (over-prune one suspect account beats under-prune the leak it might
|
|
36
|
+
* be hiding).
|
|
37
|
+
*/
|
|
38
|
+
function enumerateValidAccountIds(accountsDir) {
|
|
39
|
+
const cached = cache.get(accountsDir);
|
|
40
|
+
if (cached !== undefined)
|
|
41
|
+
return cached;
|
|
42
|
+
let names;
|
|
43
|
+
try {
|
|
44
|
+
names = (0, node_fs_1.readdirSync)(accountsDir);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err.code === "ENOENT") {
|
|
48
|
+
cache.set(accountsDir, []);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
const valid = [];
|
|
54
|
+
for (const name of names) {
|
|
55
|
+
if (!UUID_RE.test(name))
|
|
56
|
+
continue;
|
|
57
|
+
const configPath = (0, node_path_1.resolve)(accountsDir, name, "account.json");
|
|
58
|
+
try {
|
|
59
|
+
JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
60
|
+
valid.push(name);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const code = err.code;
|
|
64
|
+
if (code === "ENOENT")
|
|
65
|
+
continue;
|
|
66
|
+
// SyntaxError (parse failure) and other read errors fall through:
|
|
67
|
+
// exclude this dir, try the next.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
cache.set(accountsDir, valid);
|
|
71
|
+
return valid;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolves the canonical accounts dir from `MAXY_PLATFORM_ROOT`. Mirrors the
|
|
75
|
+
* pattern at platform/ui/app/lib/claude-agent/account.ts:9
|
|
76
|
+
* (`ACCOUNTS_DIR = resolve(PLATFORM_ROOT, "..", "data/accounts")`).
|
|
77
|
+
*
|
|
78
|
+
* Loud-throws when the env var is unset — graph-write's gate must not fall
|
|
79
|
+
* open silently.
|
|
80
|
+
*/
|
|
81
|
+
function getAccountsDirFromEnv() {
|
|
82
|
+
const root = process.env.MAXY_PLATFORM_ROOT;
|
|
83
|
+
if (!root) {
|
|
84
|
+
throw new Error("[graph-write] MAXY_PLATFORM_ROOT not set — cannot enforce accountId gate. " +
|
|
85
|
+
"Set MAXY_PLATFORM_ROOT in the spawning process or pass `accountsDir` explicitly.");
|
|
86
|
+
}
|
|
87
|
+
return (0, node_path_1.resolve)(root, "..", "data/accounts");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Test-only cache reset. Production callers must not invoke this — the cache
|
|
91
|
+
* is a deliberate boot-time invariant (see module doc).
|
|
92
|
+
*/
|
|
93
|
+
function _resetEnumerationCache() {
|
|
94
|
+
cache.clear();
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAoCA,4DAgCC;AAUD,sDASC;AAMD,wDAEC;AA/FD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qCAAoD;AACpD,yCAAoC;AAEpC,MAAM,OAAO,GACX,iEAAiE,CAAC;AAEpE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;AAE1C;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,WAAmB;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,IAAA,qBAAW,EAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,UAAU,GAAG,IAAA,mBAAO,EAAC,WAAW,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,QAAQ;gBAAE,SAAS;YAChC,kEAAkE;YAClE,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,qBAAqB;IACnC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,4EAA4E;YAC1E,kFAAkF,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,IAAA,mBAAO,EAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB;IACpC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
enumerateValidAccountIds,
|
|
8
|
+
getAccountsDirFromEnv,
|
|
9
|
+
_resetEnumerationCache,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
|
|
12
|
+
function makeFixture(): string {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "acct-enum-"));
|
|
14
|
+
// (a) non-UUID dir → excluded.
|
|
15
|
+
mkdirSync(join(root, "not-a-uuid"));
|
|
16
|
+
writeFileSync(join(root, "not-a-uuid", "account.json"), "{}");
|
|
17
|
+
// (b) UUID dir, no account.json → excluded.
|
|
18
|
+
mkdirSync(join(root, "11111111-2222-3333-4444-555555555555"));
|
|
19
|
+
// (c) UUID dir, unparseable account.json → excluded (over-prune doctrine).
|
|
20
|
+
mkdirSync(join(root, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"));
|
|
21
|
+
writeFileSync(
|
|
22
|
+
join(root, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "account.json"),
|
|
23
|
+
"{ broken json",
|
|
24
|
+
);
|
|
25
|
+
// (d) UUID dir, parseable account.json → included.
|
|
26
|
+
mkdirSync(join(root, "12345678-9abc-def0-1234-56789abcdef0"));
|
|
27
|
+
writeFileSync(
|
|
28
|
+
join(root, "12345678-9abc-def0-1234-56789abcdef0", "account.json"),
|
|
29
|
+
JSON.stringify({ id: "12345678-9abc-def0-1234-56789abcdef0" }),
|
|
30
|
+
);
|
|
31
|
+
return root;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test("enumerateValidAccountIds returns only UUID dirs with parseable account.json", () => {
|
|
35
|
+
_resetEnumerationCache();
|
|
36
|
+
const root = makeFixture();
|
|
37
|
+
try {
|
|
38
|
+
const result = enumerateValidAccountIds(root);
|
|
39
|
+
assert.deepEqual(result, ["12345678-9abc-def0-1234-56789abcdef0"]);
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(root, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("enumerateValidAccountIds returns [] when accountsDir does not exist", () => {
|
|
46
|
+
_resetEnumerationCache();
|
|
47
|
+
const ghost = join(tmpdir(), "acct-enum-does-not-exist-" + Date.now());
|
|
48
|
+
assert.deepEqual(enumerateValidAccountIds(ghost), []);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("enumerateValidAccountIds caches per accountsDir; reset re-reads", () => {
|
|
52
|
+
_resetEnumerationCache();
|
|
53
|
+
const root = makeFixture();
|
|
54
|
+
try {
|
|
55
|
+
const first = enumerateValidAccountIds(root);
|
|
56
|
+
// Add a second valid dir AFTER first call; cached call must NOT see it.
|
|
57
|
+
const newId = "99999999-aaaa-bbbb-cccc-dddddddddddd";
|
|
58
|
+
mkdirSync(join(root, newId));
|
|
59
|
+
writeFileSync(join(root, newId, "account.json"), "{}");
|
|
60
|
+
const cached = enumerateValidAccountIds(root);
|
|
61
|
+
assert.deepEqual(cached, first, "cache should hide the newly-added dir");
|
|
62
|
+
_resetEnumerationCache();
|
|
63
|
+
const fresh = enumerateValidAccountIds(root);
|
|
64
|
+
assert.equal(fresh.length, 2);
|
|
65
|
+
assert.ok(fresh.includes(newId));
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(root, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("getAccountsDirFromEnv throws when MAXY_PLATFORM_ROOT is unset", () => {
|
|
72
|
+
const saved = process.env.MAXY_PLATFORM_ROOT;
|
|
73
|
+
delete process.env.MAXY_PLATFORM_ROOT;
|
|
74
|
+
try {
|
|
75
|
+
assert.throws(
|
|
76
|
+
() => getAccountsDirFromEnv(),
|
|
77
|
+
/MAXY_PLATFORM_ROOT not set/,
|
|
78
|
+
);
|
|
79
|
+
} finally {
|
|
80
|
+
if (saved !== undefined) process.env.MAXY_PLATFORM_ROOT = saved;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("getAccountsDirFromEnv resolves to ${root}/../data/accounts when set", () => {
|
|
85
|
+
const saved = process.env.MAXY_PLATFORM_ROOT;
|
|
86
|
+
process.env.MAXY_PLATFORM_ROOT = "/tmp/fake-root/platform";
|
|
87
|
+
try {
|
|
88
|
+
const result = getAccountsDirFromEnv();
|
|
89
|
+
assert.equal(result, resolve("/tmp/fake-root/platform", "..", "data/accounts"));
|
|
90
|
+
} finally {
|
|
91
|
+
if (saved !== undefined) process.env.MAXY_PLATFORM_ROOT = saved;
|
|
92
|
+
else delete process.env.MAXY_PLATFORM_ROOT;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* account-enumeration — single source of truth for "which accountIds are
|
|
3
|
+
* provisioned on disk for this install?".
|
|
4
|
+
*
|
|
5
|
+
* Doctrine — `.docs/neo4j.md` "Account isolation invariant": every writer
|
|
6
|
+
* that stamps `n.accountId` must verify the value against
|
|
7
|
+
* `${DATA_ROOT}/accounts/<id>/account.json` before write. Read-side scoping
|
|
8
|
+
* (`WHERE n.accountId = $accountId`) silently hides leaks from every UI;
|
|
9
|
+
* an unenforced writer can leak indefinitely with zero downstream symptom.
|
|
10
|
+
*
|
|
11
|
+
* Two consumers:
|
|
12
|
+
* 1. `writeNodeWithEdges` (platform/lib/graph-write/) — central write-time
|
|
13
|
+
* floor; rejects any write whose `props.accountId` is not in the set
|
|
14
|
+
* returned here, unless `createdBy.agent === 'system'` (bootstrap).
|
|
15
|
+
* 2. `resolvePlatformAccountId` (platform/ui/app/lib/whatsapp/platform-account-id.ts)
|
|
16
|
+
* — WhatsApp writer-side helper; loud-throws on zero or multi accounts.
|
|
17
|
+
*
|
|
18
|
+
* Per-process cache keyed on `accountsDir` — Phase 0 invariant has accounts
|
|
19
|
+
* created at install time, so adding/removing accounts requires a service
|
|
20
|
+
* restart anyway.
|
|
21
|
+
*/
|
|
22
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
23
|
+
import { resolve } from "node:path";
|
|
24
|
+
|
|
25
|
+
const UUID_RE =
|
|
26
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27
|
+
|
|
28
|
+
const cache = new Map<string, string[]>();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns the list of valid account UUIDs found under `accountsDir`. A dir is
|
|
32
|
+
* "valid" iff its name is a UUID AND it contains a parseable `account.json`.
|
|
33
|
+
* Corruption-discipline: a present-but-unparseable account.json EXCLUDES the
|
|
34
|
+
* dir (over-prune one suspect account beats under-prune the leak it might
|
|
35
|
+
* be hiding).
|
|
36
|
+
*/
|
|
37
|
+
export function enumerateValidAccountIds(accountsDir: string): string[] {
|
|
38
|
+
const cached = cache.get(accountsDir);
|
|
39
|
+
if (cached !== undefined) return cached;
|
|
40
|
+
|
|
41
|
+
let names: string[];
|
|
42
|
+
try {
|
|
43
|
+
names = readdirSync(accountsDir);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
46
|
+
cache.set(accountsDir, []);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const valid: string[] = [];
|
|
53
|
+
for (const name of names) {
|
|
54
|
+
if (!UUID_RE.test(name)) continue;
|
|
55
|
+
const configPath = resolve(accountsDir, name, "account.json");
|
|
56
|
+
try {
|
|
57
|
+
JSON.parse(readFileSync(configPath, "utf-8"));
|
|
58
|
+
valid.push(name);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
61
|
+
if (code === "ENOENT") continue;
|
|
62
|
+
// SyntaxError (parse failure) and other read errors fall through:
|
|
63
|
+
// exclude this dir, try the next.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cache.set(accountsDir, valid);
|
|
68
|
+
return valid;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the canonical accounts dir from `MAXY_PLATFORM_ROOT`. Mirrors the
|
|
73
|
+
* pattern at platform/ui/app/lib/claude-agent/account.ts:9
|
|
74
|
+
* (`ACCOUNTS_DIR = resolve(PLATFORM_ROOT, "..", "data/accounts")`).
|
|
75
|
+
*
|
|
76
|
+
* Loud-throws when the env var is unset — graph-write's gate must not fall
|
|
77
|
+
* open silently.
|
|
78
|
+
*/
|
|
79
|
+
export function getAccountsDirFromEnv(): string {
|
|
80
|
+
const root = process.env.MAXY_PLATFORM_ROOT;
|
|
81
|
+
if (!root) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"[graph-write] MAXY_PLATFORM_ROOT not set — cannot enforce accountId gate. " +
|
|
84
|
+
"Set MAXY_PLATFORM_ROOT in the spawning process or pass `accountsDir` explicitly.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return resolve(root, "..", "data/accounts");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Test-only cache reset. Production callers must not invoke this — the cache
|
|
92
|
+
* is a deliberate boot-time invariant (see module doc).
|
|
93
|
+
*/
|
|
94
|
+
export function _resetEnumerationCache(): void {
|
|
95
|
+
cache.clear();
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-id-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/account-id-gate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
7
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
8
|
+
const index_js_1 = require("../index.js");
|
|
9
|
+
function makeStubSession(targetId) {
|
|
10
|
+
const tx = {
|
|
11
|
+
async run(cypher, params = {}) {
|
|
12
|
+
if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
|
|
13
|
+
const ids = params.ids ?? [];
|
|
14
|
+
const records = ids
|
|
15
|
+
.filter((id) => id === targetId)
|
|
16
|
+
.map((id) => ({
|
|
17
|
+
get(field) {
|
|
18
|
+
if (field === "id")
|
|
19
|
+
return id;
|
|
20
|
+
if (field === "labels")
|
|
21
|
+
return ["Account"];
|
|
22
|
+
throw new Error(`unknown field ${field}`);
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
return { records };
|
|
26
|
+
}
|
|
27
|
+
if (cypher.includes("CREATE (n:")) {
|
|
28
|
+
return {
|
|
29
|
+
records: [
|
|
30
|
+
{
|
|
31
|
+
get(field) {
|
|
32
|
+
if (field === "nodeId")
|
|
33
|
+
return "new-element-id";
|
|
34
|
+
if (field === "nodeLabels")
|
|
35
|
+
return ["Conversation"];
|
|
36
|
+
throw new Error(`unknown field ${field}`);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
|
|
43
|
+
return {
|
|
44
|
+
records: [],
|
|
45
|
+
summary: {
|
|
46
|
+
counters: {
|
|
47
|
+
updates: () => ({ relationshipsCreated: 1 }),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { records: [] };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
async executeWrite(work) {
|
|
57
|
+
return await work(tx);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const VALID_UUID = "12345678-9abc-def0-1234-56789abcdef0";
|
|
62
|
+
const TARGET_ID = "target-element-id";
|
|
63
|
+
const REL = [
|
|
64
|
+
{ type: "PART_OF", direction: "outgoing", targetNodeId: TARGET_ID },
|
|
65
|
+
];
|
|
66
|
+
function captureStderr() {
|
|
67
|
+
const original = process.stderr.write.bind(process.stderr);
|
|
68
|
+
let buf = "";
|
|
69
|
+
process.stderr.write =
|
|
70
|
+
(chunk) => {
|
|
71
|
+
buf += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
restore: () => {
|
|
76
|
+
process.stderr.write = original;
|
|
77
|
+
},
|
|
78
|
+
output: () => buf,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
(0, node_test_1.default)("rejects null accountId from non-system writer", async () => {
|
|
82
|
+
const cap = captureStderr();
|
|
83
|
+
try {
|
|
84
|
+
await strict_1.default.rejects(() => (0, index_js_1.writeNodeWithEdges)({
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
session: makeStubSession(TARGET_ID),
|
|
87
|
+
labels: ["Conversation"],
|
|
88
|
+
props: { accountId: null },
|
|
89
|
+
relationships: REL,
|
|
90
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
91
|
+
expectedAccountId: VALID_UUID,
|
|
92
|
+
}), /invalid-account-id/);
|
|
93
|
+
cap.restore();
|
|
94
|
+
strict_1.default.match(cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=missing writer=test-agent\b/);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
cap.restore();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
(0, node_test_1.default)("rejects non-UUID accountId from non-system writer", async () => {
|
|
101
|
+
const cap = captureStderr();
|
|
102
|
+
try {
|
|
103
|
+
await strict_1.default.rejects(() => (0, index_js_1.writeNodeWithEdges)({
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
session: makeStubSession(TARGET_ID),
|
|
106
|
+
labels: ["Conversation"],
|
|
107
|
+
props: { accountId: "evil-leak" },
|
|
108
|
+
relationships: REL,
|
|
109
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
110
|
+
expectedAccountId: VALID_UUID,
|
|
111
|
+
}), /invalid-account-id/);
|
|
112
|
+
cap.restore();
|
|
113
|
+
strict_1.default.match(cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=evil-lea writer=test-agent\b/);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
cap.restore();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
(0, node_test_1.default)("rejects UUID-shape accountId that does not match the expected one", async () => {
|
|
120
|
+
const stranger = "ffffffff-1111-2222-3333-444444444444";
|
|
121
|
+
const cap = captureStderr();
|
|
122
|
+
try {
|
|
123
|
+
await strict_1.default.rejects(() => (0, index_js_1.writeNodeWithEdges)({
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
125
|
+
session: makeStubSession(TARGET_ID),
|
|
126
|
+
labels: ["Conversation"],
|
|
127
|
+
props: { accountId: stranger },
|
|
128
|
+
relationships: REL,
|
|
129
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
130
|
+
expectedAccountId: VALID_UUID,
|
|
131
|
+
}), /invalid-account-id/);
|
|
132
|
+
cap.restore();
|
|
133
|
+
strict_1.default.match(cap.output(), /\[graph-write\] reject reason=invalid-account-id accountId=ffffffff writer=test-agent\b/);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
cap.restore();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
(0, node_test_1.default)("accepts matching accountId from non-system writer", async () => {
|
|
140
|
+
const result = await (0, index_js_1.writeNodeWithEdges)({
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
|
+
session: makeStubSession(TARGET_ID),
|
|
143
|
+
labels: ["Conversation"],
|
|
144
|
+
props: { accountId: VALID_UUID },
|
|
145
|
+
relationships: REL,
|
|
146
|
+
createdBy: { agent: "test-agent", session: "s" },
|
|
147
|
+
expectedAccountId: VALID_UUID,
|
|
148
|
+
});
|
|
149
|
+
strict_1.default.equal(result.nodeId, "new-element-id");
|
|
150
|
+
strict_1.default.equal(result.edgesCreated, 1);
|
|
151
|
+
});
|
|
152
|
+
(0, node_test_1.default)("system bootstrap exemption: bad accountId still proceeds", async () => {
|
|
153
|
+
const result = await (0, index_js_1.writeNodeWithEdges)({
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
session: makeStubSession(TARGET_ID),
|
|
156
|
+
labels: ["Conversation"],
|
|
157
|
+
// accountId is intentionally wrong; system bootstrap exempts it.
|
|
158
|
+
props: { accountId: "not-a-uuid" },
|
|
159
|
+
relationships: REL,
|
|
160
|
+
createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
|
|
161
|
+
expectedAccountId: VALID_UUID,
|
|
162
|
+
});
|
|
163
|
+
strict_1.default.equal(result.nodeId, "new-element-id");
|
|
164
|
+
});
|
|
165
|
+
//# sourceMappingURL=account-id-gate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-id-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/account-id-gate.test.ts"],"names":[],"mappings":"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"}
|