@rubytech/create-realagent 1.0.832 → 1.0.834

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.
Files changed (97) hide show
  1. package/dist/index.js +131 -9
  2. package/package.json +1 -1
  3. package/payload/platform/lib/admins-write/dist/index.d.ts +87 -0
  4. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/admins-write/dist/index.js +248 -0
  6. package/payload/platform/lib/admins-write/dist/index.js.map +1 -0
  7. package/payload/platform/lib/admins-write/src/index.ts +311 -0
  8. package/payload/platform/lib/admins-write/tsconfig.json +8 -0
  9. package/payload/platform/neo4j/migrations/009-conversation-archive-title.ts +197 -0
  10. package/payload/platform/neo4j/schema.cypher +1 -1
  11. package/payload/platform/package.json +2 -2
  12. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  13. package/payload/platform/plugins/admin/mcp/dist/index.js +37 -44
  14. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/docs/references/internals.md +4 -3
  16. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +215 -43
  17. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +7 -2
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +75 -0
  19. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +16 -10
  21. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
  22. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +155 -100
  23. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
  24. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +13 -5
  25. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +53 -59
  27. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -1
  28. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +9 -0
  29. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +24 -7
  31. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +47 -11
  33. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  34. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +45 -8
  35. package/payload/platform/scripts/lib/resolve-account-dir.sh +3 -1
  36. package/payload/platform/scripts/migrate-import.sh +3 -1
  37. package/payload/platform/scripts/seed-neo4j.sh +13 -3
  38. package/payload/server/chunk-CRAIGEXY.js +654 -0
  39. package/payload/server/chunk-GK4WHM3H.js +9961 -0
  40. package/payload/server/chunk-I2NOLBQA.js +2123 -0
  41. package/payload/server/chunk-IVTESKFR.js +9961 -0
  42. package/payload/server/chunk-KD3XP4IK.js +1116 -0
  43. package/payload/server/chunk-KKGGT5RH.js +654 -0
  44. package/payload/server/chunk-MRJGG6CS.js +2124 -0
  45. package/payload/server/chunk-OJZPS4BL.js +367 -0
  46. package/payload/server/chunk-ZVW5XKPU.js +1116 -0
  47. package/payload/server/client-pool-FM3YJWV5.js +32 -0
  48. package/payload/server/client-pool-J5BCVVI2.js +32 -0
  49. package/payload/server/cloudflare-task-tracker-FSPEJOTH.js +19 -0
  50. package/payload/server/cloudflare-task-tracker-XCUO4N74.js +19 -0
  51. package/payload/server/maxy-edge.js +6 -5
  52. package/payload/server/neo4j-migrations-5AN2U3YO.js +664 -0
  53. package/payload/server/neo4j-migrations-XP7XDVPX.js +664 -0
  54. package/payload/server/public/assets/{Checkbox-CTGhpDKq.js → Checkbox-Bq6ORjz2.js} +1 -1
  55. package/payload/server/public/assets/admin-CstEkw-G.js +352 -0
  56. package/payload/server/public/assets/data-DwZZ7qbH.js +1 -0
  57. package/payload/server/public/assets/graph-DceEv42K.js +1 -0
  58. package/payload/server/public/assets/{jsx-runtime-D4WovFYk.css → jsx-runtime-DidQeNoZ.css} +1 -1
  59. package/payload/server/public/assets/page-Bpi_jPw6.js +50 -0
  60. package/payload/server/public/assets/{page-DkBfWy4C.js → page-CFWoVkgV.js} +1 -1
  61. package/payload/server/public/assets/{public-BdVIVpv8.js → public-BWMwq5Jj.js} +1 -1
  62. package/payload/server/public/assets/{useAdminFetch-DmHu0oCx.js → useAdminFetch-B93ig7ef.js} +1 -1
  63. package/payload/server/public/assets/{useVoiceRecorder-CSc_hxjV.js → useVoiceRecorder-Cb0nAtOo.js} +1 -1
  64. package/payload/server/public/data.html +5 -5
  65. package/payload/server/public/graph.html +6 -6
  66. package/payload/server/public/index.html +8 -8
  67. package/payload/server/public/public.html +5 -5
  68. package/payload/server/server.js +376 -167
  69. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts +0 -31
  70. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +0 -1
  71. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +0 -666
  72. package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +0 -1
  73. package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.d.ts +0 -61
  74. package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.d.ts.map +0 -1
  75. package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.js +0 -266
  76. package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.js.map +0 -1
  77. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts +0 -27
  78. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts.map +0 -1
  79. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js +0 -477
  80. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js.map +0 -1
  81. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.d.ts +0 -27
  82. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.d.ts.map +0 -1
  83. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.js +0 -160
  84. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.js.map +0 -1
  85. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +0 -10
  86. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +0 -1
  87. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js +0 -29
  88. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +0 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts +0 -28
  90. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts.map +0 -1
  91. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js +0 -34
  92. package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js.map +0 -1
  93. package/payload/server/public/assets/admin-BNwPsMhJ.js +0 -352
  94. package/payload/server/public/assets/data-Y77FLKjs.js +0 -1
  95. package/payload/server/public/assets/graph-N_Bw-8oT.js +0 -1
  96. package/payload/server/public/assets/page-BKLGP-th.js +0 -50
  97. /package/payload/server/public/assets/{jsx-runtime-DkaAusaX.js → jsx-runtime-DH5S-MwB.js} +0 -0
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
3
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, statSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
4
4
  import { resolve, join, dirname } from "node:path";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { resolveInstallPortFromFs, buildMaxyUnitFile } from "./port-resolution.js";
@@ -1334,6 +1334,90 @@ function provisionRemoteSessionSecret() {
1334
1334
  writeFileSync(secretFile, randomBytes(32).toString("hex"), { mode: 0o600 });
1335
1335
  console.log(` [install] remote-session-secret provisioned path=${secretFile}`);
1336
1336
  }
1337
+ // Task 904 — install-time admin-auth invariant. Walks every account.json
1338
+ // under accountsDir and compares its admins[] to the persistent users.json,
1339
+ // emitting one [install-invariant] line per divergence and one summary line.
1340
+ // Log-only (no install abort) so pre-Task-904 devices with already-divergent
1341
+ // state still upgrade; future sprint can promote to refuse-or-degrade once
1342
+ // the deployed fleet is audited clean. Mirror of
1343
+ // checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts.
1344
+ function runInstallInvariantCheck(usersFile, accountsDir) {
1345
+ const TAG = "[install-invariant]";
1346
+ const usersUserIds = new Set();
1347
+ if (existsSync(usersFile)) {
1348
+ try {
1349
+ const raw = readFileSync(usersFile, "utf-8").trim();
1350
+ if (raw) {
1351
+ const users = JSON.parse(raw);
1352
+ for (const u of users) {
1353
+ if (typeof u.userId === "string")
1354
+ usersUserIds.add(u.userId);
1355
+ }
1356
+ }
1357
+ }
1358
+ catch (err) {
1359
+ const msg = err instanceof Error ? err.message : String(err);
1360
+ console.log(` ${TAG} users.json unreadable usersFile=${usersFile} error=${msg}`);
1361
+ }
1362
+ }
1363
+ const accountUserIds = new Set();
1364
+ let divergences = 0;
1365
+ if (existsSync(accountsDir)) {
1366
+ let entries = [];
1367
+ try {
1368
+ entries = readdirSync(accountsDir);
1369
+ }
1370
+ catch (err) {
1371
+ const msg = err instanceof Error ? err.message : String(err);
1372
+ console.log(` ${TAG} accounts-dir unreadable accountsDir=${accountsDir} error=${msg}`);
1373
+ console.log(` ${TAG} check complete divergences=0 (accounts-dir unreadable)`);
1374
+ return;
1375
+ }
1376
+ for (const entry of entries) {
1377
+ if (entry.startsWith("."))
1378
+ continue;
1379
+ const accountDir = join(accountsDir, entry);
1380
+ try {
1381
+ if (!statSync(accountDir).isDirectory())
1382
+ continue;
1383
+ }
1384
+ catch {
1385
+ continue;
1386
+ }
1387
+ const accountJsonPath = join(accountDir, "account.json");
1388
+ if (!existsSync(accountJsonPath))
1389
+ continue;
1390
+ let admins = [];
1391
+ try {
1392
+ const config = JSON.parse(readFileSync(accountJsonPath, "utf-8"));
1393
+ admins = (config.admins ?? []);
1394
+ }
1395
+ catch (err) {
1396
+ const msg = err instanceof Error ? err.message : String(err);
1397
+ console.log(` ${TAG} account.json unreadable source=${accountJsonPath} error=${msg}`);
1398
+ continue;
1399
+ }
1400
+ for (const a of admins) {
1401
+ if (typeof a.userId !== "string")
1402
+ continue;
1403
+ accountUserIds.add(a.userId);
1404
+ if (!usersUserIds.has(a.userId)) {
1405
+ const userIdShort = a.userId.slice(0, 8);
1406
+ console.log(` ${TAG} direction=account-without-users userId=${userIdShort} source=${accountJsonPath}`);
1407
+ divergences += 1;
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+ for (const uid of usersUserIds) {
1413
+ if (!accountUserIds.has(uid)) {
1414
+ const userIdShort = uid.slice(0, 8);
1415
+ console.log(` ${TAG} direction=users-without-account userId=${userIdShort} source=${usersFile}`);
1416
+ divergences += 1;
1417
+ }
1418
+ }
1419
+ console.log(` ${TAG} check complete divergences=${divergences}`);
1420
+ }
1337
1421
  function deployPayload() {
1338
1422
  log("8", TOTAL, `Deploying ${BRAND.productName}...`);
1339
1423
  if (!existsSync(PAYLOAD_DIR)) {
@@ -1354,14 +1438,24 @@ function deployPayload() {
1354
1438
  mkdirSync(persistentDir, { recursive: true });
1355
1439
  cpSync(oldAccountsDir, persistentAccountsDir, { recursive: true });
1356
1440
  }
1357
- // Migrate users.json to persistent dir — userId must survive across installs.
1358
- // Without this, every reinstall generates a new UUID and orphans all prior conversations.
1441
+ // Task 904 — users.json lives at <persistentDir>/users.json. Pre-Task-904
1442
+ // installs wrote to <INSTALL_DIR>/platform/config/users.json (the wipe zone)
1443
+ // and relied on a one-shot copy-up at first install plus a copy-back after
1444
+ // every wipe. The copy-back overwrote LIVE with the FROZEN-AT-FIRST-INSTALL
1445
+ // backup, dropping every admin row added since first install. Writers (paths.ts,
1446
+ // admin-add, set-pin, seed-neo4j.sh, migrate-import.sh) now target the
1447
+ // persistent file directly — see comment in platform/ui/app/lib/paths.ts.
1448
+ //
1449
+ // Legacy pickup: if a pre-Task-904 install left users.json inside the wipe
1450
+ // zone, copy it up once. Idempotent — guarded by !existsSync(persistentUsersFile).
1451
+ // Safe to delete this block one release after Task 904 has been deployed
1452
+ // everywhere; until then it absorbs upgrades from any pre-Task-904 version.
1359
1453
  const persistentUsersFile = join(persistentDir, "users.json");
1360
1454
  const oldUsersFile = join(INSTALL_DIR, "platform/config/users.json");
1361
1455
  if (existsSync(oldUsersFile) && !existsSync(persistentUsersFile)) {
1362
1456
  mkdirSync(persistentDir, { recursive: true });
1363
1457
  cpSync(oldUsersFile, persistentUsersFile);
1364
- console.log(" Migrated users.json to persistent storage.");
1458
+ console.log(" Migrated pre-Task-904 users.json to persistent storage.");
1365
1459
  }
1366
1460
  // Brand isolation: installer does not read ~/.maxy/, ~/.cloudflared/, or
1367
1461
  // ~/.cloudflare/ on non-default brands. These are peer-brand or shared-singleton
@@ -1440,13 +1534,41 @@ function deployPayload() {
1440
1534
  cpSync(persistentPasswordFile, join(configDir, ".neo4j-password"));
1441
1535
  console.log(" Restored Neo4j password.");
1442
1536
  }
1537
+ // Task 904 — users.json is read directly from persistentDir by both
1538
+ // platform/ui/app/lib/paths.ts (USERS_FILE = MAXY_DIR/users.json) and the
1539
+ // admin MCP plugin (USERS_FILE = CONFIG_DIR/users.json). No copy into the
1540
+ // wipe zone — the pre-Task-904 cpSync to platform/config/users.json was the
1541
+ // copy that overwrote new admin rows on every install. The line below
1542
+ // observes the row count + userId prefixes so a future regression is grep-
1543
+ // detectable in the install log without any runtime change (singular
1544
+ // "userId preserved" hid the multi-row failure mode).
1443
1545
  if (existsSync(persistentUsersFile)) {
1444
- cpSync(persistentUsersFile, join(configDir, "users.json"));
1445
- console.log(" Restored users.json userId preserved across install.");
1546
+ try {
1547
+ const raw = readFileSync(persistentUsersFile, "utf-8").trim();
1548
+ const rows = raw ? JSON.parse(raw) : [];
1549
+ const ids = rows
1550
+ .map(r => (typeof r.userId === "string" ? r.userId.slice(0, 8) : "?"))
1551
+ .join(",");
1552
+ console.log(` [install] users.json preserved: rows=${rows.length} userIds=${ids}`);
1553
+ }
1554
+ catch (err) {
1555
+ const msg = err instanceof Error ? err.message : String(err);
1556
+ console.log(` [install] users.json preserved: rows=? parse-failed error=${msg}`);
1557
+ }
1446
1558
  }
1447
- // Account data no longer needs restoring — it lives at {installDir}/data/accounts/
1448
- // which is outside the wipe zone. The migration block above handles the one-time move
1449
- // from the old platform/config/accounts/ location.
1559
+ else {
1560
+ console.log(" [install] users.json: no persistent file (fresh install set-pin will create it)");
1561
+ }
1562
+ // Task 904 item 3 — install-time admin-auth invariant check. Walks every
1563
+ // data/accounts/*/account.json admins[] and compares to the persistent
1564
+ // users.json. Log-only (does NOT refuse install) so pre-Task-904 devices
1565
+ // with already-divergent state still upgrade; the [install-invariant]
1566
+ // line surfaces the bug to the operator without bricking the device.
1567
+ // Inline rather than imported from platform/lib/admins-write because the
1568
+ // installer is its own npm package and doesn't bundle the platform lib.
1569
+ // Mirrors checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts;
1570
+ // future divergence between the two should be caught by the test suite.
1571
+ runInstallInvariantCheck(persistentUsersFile, join(INSTALL_DIR, "data", "accounts"));
1450
1572
  // Write version marker so the running platform knows which create-maxy produced this deployment
1451
1573
  writeFileSync(join(configDir, `.${BRAND.hostname}-version`), PKG_VERSION, "utf-8");
1452
1574
  console.log(` Deployed to ${INSTALL_DIR}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.832",
3
+ "version": "1.0.834",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -0,0 +1,87 @@
1
+ export interface UserEntry {
2
+ userId: string;
3
+ pin: string;
4
+ name?: string;
5
+ }
6
+ export interface AdminEntry {
7
+ userId: string;
8
+ role: "owner" | "admin";
9
+ }
10
+ export interface AdminWriteInput {
11
+ userId: string;
12
+ pin: string;
13
+ role: "owner" | "admin";
14
+ usersFile: string;
15
+ accountDir: string;
16
+ caller: string;
17
+ }
18
+ export interface AdminWriteResult {
19
+ usersJsonResult: "ok" | "fail" | "noop";
20
+ accountJsonResult: "ok" | "fail" | "noop";
21
+ usersError?: string;
22
+ accountError?: string;
23
+ }
24
+ /**
25
+ * Write a single admin entry across both auth stores (users.json + account.json admins[]).
26
+ *
27
+ * Idempotent on userId: if the userId already exists in users.json, the PIN
28
+ * field is updated in place rather than duplicated; admins[] is checked for
29
+ * presence before pushing.
30
+ *
31
+ * Always emits exactly one `[admins-write] caller=… userId=<prefix> usersJsonResult=… accountJsonResult=…`
32
+ * line — success and failure paths both fire it. Operators grep for this single
33
+ * predicate when reconstructing a partial-write incident.
34
+ */
35
+ export declare function writeAdminEntry(input: AdminWriteInput): AdminWriteResult;
36
+ export interface AdminRemoveInput {
37
+ userId: string;
38
+ accountDir: string;
39
+ caller: string;
40
+ }
41
+ export interface AdminRemoveResult {
42
+ accountJsonResult: "ok" | "fail" | "noop";
43
+ accountError?: string;
44
+ }
45
+ /**
46
+ * Remove a single admin entry from account.json admins[] — does NOT touch
47
+ * users.json (the device-level entry is preserved because the user may admin
48
+ * other accounts). Single-file operation; the [admins-write] line records
49
+ * `usersJsonResult=skip` so the grep-by-helper-call still picks it up.
50
+ */
51
+ export declare function removeAdminFromAccount(input: AdminRemoveInput): AdminRemoveResult;
52
+ export interface InvariantCheckInput {
53
+ /** Absolute path to users.json (persistent location). */
54
+ usersFile: string;
55
+ /** Absolute path to the accounts root (e.g. <INSTALL_DIR>/data/accounts). */
56
+ accountsDir: string;
57
+ /** Tag for the log prefix — `install-invariant` or `admin-invariant`. */
58
+ tag: "install-invariant" | "admin-invariant";
59
+ }
60
+ export interface InvariantCheckResult {
61
+ divergences: number;
62
+ /** Userids in account.json admins[] that have no row in users.json. */
63
+ accountWithoutUsers: Array<{
64
+ userId: string;
65
+ source: string;
66
+ }>;
67
+ /** Userids in users.json that no account.json admins[] references. */
68
+ usersWithoutAccount: Array<{
69
+ userId: string;
70
+ }>;
71
+ }
72
+ /**
73
+ * Walk every account.json under accountsDir and compare its admins[] to
74
+ * users.json. Emits one log line per divergence and one summary line.
75
+ *
76
+ * Behaviour: log-only — does NOT throw, does NOT refuse boot or install.
77
+ * Pre-Task-904 installs that already diverged would otherwise brick on the
78
+ * first boot of a Task-904 device. The summary line `divergences=<n>` is
79
+ * always emitted (wired-up assertion); per-divergence lines fire only when
80
+ * divergences > 0. Operators grep `[install-invariant]` or `[admin-invariant]`
81
+ * to detect the regression class without reading every install log.
82
+ *
83
+ * Promotion to refuse-or-degrade is a future sprint; capture as a TODO once
84
+ * the deployed fleet has been audited clean.
85
+ */
86
+ export declare function checkAdminAuthInvariant(input: InvariantCheckInput): InvariantCheckResult;
87
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IAGZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,iBAAiB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAmBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CA4DxE;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CA+BjF;AAED,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,GAAG,EAAE,mBAAmB,GAAG,iBAAiB,CAAC;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,mBAAmB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,sEAAsE;IACtE,mBAAmB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAsFxF"}
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ // admins-write — single chokepoint for the dual-file admin write
3
+ // (users.json device-level PIN auth + account.json account-level role).
4
+ //
5
+ // Task 904 background: pre-Task-904 the two writers — `admin-add`
6
+ // (platform/plugins/admin/mcp/src/index.ts) and `set-pin` POST
7
+ // (platform/ui/server/routes/onboarding.ts) — each performed the dual write
8
+ // inline. They drifted: admin-add returned `is_error: true` with a per-leg
9
+ // `[admin-auth-store]` line on partial failure; set-pin logged on stderr but
10
+ // otherwise rolled forward. This module collapses both into one routed write
11
+ // so the static check `grep -rnE 'admins\.push|config\.admins\s*=' platform/`
12
+ // returns 0 outside this file (excluding tests, scripts, and the reader paths
13
+ // which never push).
14
+ //
15
+ // Atomicity contract — single file: write-temp + rename, durable.
16
+ // Atomicity contract — across files: NOT atomic. POSIX has no two-file
17
+ // transaction. The helper writes users.json first, then account.json. On
18
+ // account.json failure with users.json already written, the result names the
19
+ // partial state. Callers MUST surface partial state to the operator (admin-add
20
+ // returns is_error: true with the [admins-write] line; set-pin returns 500
21
+ // with the same). Same trust model as Task 850's [admin-auth-store] pair.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.writeAdminEntry = writeAdminEntry;
24
+ exports.removeAdminFromAccount = removeAdminFromAccount;
25
+ exports.checkAdminAuthInvariant = checkAdminAuthInvariant;
26
+ const node_fs_1 = require("node:fs");
27
+ const node_path_1 = require("node:path");
28
+ function logLine(input, result) {
29
+ const userIdShort = input.userId.slice(0, 8);
30
+ console.error(`[admins-write] caller=${input.caller} userId=${userIdShort} ` +
31
+ `usersJsonResult=${result.usersJsonResult} accountJsonResult=${result.accountJsonResult}` +
32
+ (result.usersError ? ` usersError=${result.usersError}` : "") +
33
+ (result.accountError ? ` accountError=${result.accountError}` : ""));
34
+ }
35
+ function writeFileAtomic(filePath, contents, mode) {
36
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(filePath), { recursive: true });
37
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
38
+ (0, node_fs_1.writeFileSync)(tempPath, contents, mode !== undefined ? { mode } : undefined);
39
+ (0, node_fs_1.renameSync)(tempPath, filePath);
40
+ }
41
+ /**
42
+ * Write a single admin entry across both auth stores (users.json + account.json admins[]).
43
+ *
44
+ * Idempotent on userId: if the userId already exists in users.json, the PIN
45
+ * field is updated in place rather than duplicated; admins[] is checked for
46
+ * presence before pushing.
47
+ *
48
+ * Always emits exactly one `[admins-write] caller=… userId=<prefix> usersJsonResult=… accountJsonResult=…`
49
+ * line — success and failure paths both fire it. Operators grep for this single
50
+ * predicate when reconstructing a partial-write incident.
51
+ */
52
+ function writeAdminEntry(input) {
53
+ const result = { usersJsonResult: "noop", accountJsonResult: "noop" };
54
+ // 1. users.json — read-modify-write atomically.
55
+ try {
56
+ let users = [];
57
+ if ((0, node_fs_1.existsSync)(input.usersFile)) {
58
+ const raw = (0, node_fs_1.readFileSync)(input.usersFile, "utf-8").trim();
59
+ if (raw) {
60
+ const parsed = JSON.parse(raw);
61
+ if (!Array.isArray(parsed)) {
62
+ throw new Error("users.json is not an array");
63
+ }
64
+ users = parsed;
65
+ }
66
+ }
67
+ const existing = users.findIndex(u => u.userId === input.userId);
68
+ if (existing === -1) {
69
+ users.push({ userId: input.userId, pin: input.pin });
70
+ }
71
+ else {
72
+ // Preserve any sibling fields that other writers may have set, but
73
+ // overwrite pin. Strip the deprecated `name` field on touch (Task 829).
74
+ const merged = { ...users[existing], pin: input.pin };
75
+ delete merged.name;
76
+ users[existing] = merged;
77
+ }
78
+ writeFileAtomic(input.usersFile, JSON.stringify(users, null, 2) + "\n", 0o600);
79
+ result.usersJsonResult = "ok";
80
+ }
81
+ catch (err) {
82
+ result.usersJsonResult = "fail";
83
+ result.usersError = err instanceof Error ? err.message : String(err);
84
+ logLine(input, result);
85
+ return result;
86
+ }
87
+ // 2. account.json — read-modify-write atomically.
88
+ try {
89
+ const accountJsonPath = (0, node_path_1.join)(input.accountDir, "account.json");
90
+ if (!(0, node_fs_1.existsSync)(accountJsonPath)) {
91
+ throw new Error(`account.json not found at ${accountJsonPath}`);
92
+ }
93
+ const config = JSON.parse((0, node_fs_1.readFileSync)(accountJsonPath, "utf-8"));
94
+ const admins = (config.admins ?? []);
95
+ if (admins.some(a => a.userId === input.userId)) {
96
+ result.accountJsonResult = "noop";
97
+ }
98
+ else {
99
+ admins.push({ userId: input.userId, role: input.role });
100
+ config.admins = admins;
101
+ writeFileAtomic(accountJsonPath, JSON.stringify(config, null, 2) + "\n");
102
+ result.accountJsonResult = "ok";
103
+ }
104
+ }
105
+ catch (err) {
106
+ result.accountJsonResult = "fail";
107
+ result.accountError = err instanceof Error ? err.message : String(err);
108
+ }
109
+ logLine(input, result);
110
+ return result;
111
+ }
112
+ /**
113
+ * Remove a single admin entry from account.json admins[] — does NOT touch
114
+ * users.json (the device-level entry is preserved because the user may admin
115
+ * other accounts). Single-file operation; the [admins-write] line records
116
+ * `usersJsonResult=skip` so the grep-by-helper-call still picks it up.
117
+ */
118
+ function removeAdminFromAccount(input) {
119
+ const result = { accountJsonResult: "noop" };
120
+ const userIdShort = input.userId.slice(0, 8);
121
+ try {
122
+ const accountJsonPath = (0, node_path_1.join)(input.accountDir, "account.json");
123
+ if (!(0, node_fs_1.existsSync)(accountJsonPath)) {
124
+ throw new Error(`account.json not found at ${accountJsonPath}`);
125
+ }
126
+ const config = JSON.parse((0, node_fs_1.readFileSync)(accountJsonPath, "utf-8"));
127
+ const admins = (config.admins ?? []);
128
+ const targetIndex = admins.findIndex(a => a.userId === input.userId);
129
+ if (targetIndex === -1) {
130
+ result.accountJsonResult = "noop";
131
+ }
132
+ else {
133
+ admins.splice(targetIndex, 1);
134
+ config.admins = admins;
135
+ writeFileAtomic(accountJsonPath, JSON.stringify(config, null, 2) + "\n");
136
+ result.accountJsonResult = "ok";
137
+ }
138
+ }
139
+ catch (err) {
140
+ result.accountJsonResult = "fail";
141
+ result.accountError = err instanceof Error ? err.message : String(err);
142
+ }
143
+ console.error(`[admins-write] caller=${input.caller} userId=${userIdShort} ` +
144
+ `usersJsonResult=skip accountJsonResult=${result.accountJsonResult}` +
145
+ (result.accountError ? ` accountError=${result.accountError}` : ""));
146
+ return result;
147
+ }
148
+ /**
149
+ * Walk every account.json under accountsDir and compare its admins[] to
150
+ * users.json. Emits one log line per divergence and one summary line.
151
+ *
152
+ * Behaviour: log-only — does NOT throw, does NOT refuse boot or install.
153
+ * Pre-Task-904 installs that already diverged would otherwise brick on the
154
+ * first boot of a Task-904 device. The summary line `divergences=<n>` is
155
+ * always emitted (wired-up assertion); per-divergence lines fire only when
156
+ * divergences > 0. Operators grep `[install-invariant]` or `[admin-invariant]`
157
+ * to detect the regression class without reading every install log.
158
+ *
159
+ * Promotion to refuse-or-degrade is a future sprint; capture as a TODO once
160
+ * the deployed fleet has been audited clean.
161
+ */
162
+ function checkAdminAuthInvariant(input) {
163
+ const result = {
164
+ divergences: 0,
165
+ accountWithoutUsers: [],
166
+ usersWithoutAccount: [],
167
+ };
168
+ // Read users.json userIds (persistent file). Absent file == empty set; do
169
+ // not abort — first-boot before set-pin has run is a legitimate state.
170
+ const usersUserIds = new Set();
171
+ if ((0, node_fs_1.existsSync)(input.usersFile)) {
172
+ try {
173
+ const raw = (0, node_fs_1.readFileSync)(input.usersFile, "utf-8").trim();
174
+ if (raw) {
175
+ const users = JSON.parse(raw);
176
+ for (const u of users) {
177
+ if (typeof u.userId === "string")
178
+ usersUserIds.add(u.userId);
179
+ }
180
+ }
181
+ }
182
+ catch (err) {
183
+ const msg = err instanceof Error ? err.message : String(err);
184
+ console.error(`[${input.tag}] users.json unreadable usersFile=${input.usersFile} error=${msg}`);
185
+ }
186
+ }
187
+ // Walk account dirs. Skip dotfiles (.trash/) and missing account.json.
188
+ const accountUserIds = new Set();
189
+ if ((0, node_fs_1.existsSync)(input.accountsDir)) {
190
+ let entries;
191
+ try {
192
+ entries = (0, node_fs_1.readdirSync)(input.accountsDir);
193
+ }
194
+ catch (err) {
195
+ const msg = err instanceof Error ? err.message : String(err);
196
+ console.error(`[${input.tag}] accounts-dir unreadable accountsDir=${input.accountsDir} error=${msg}`);
197
+ console.error(`[${input.tag}] check complete divergences=0 (accounts-dir unreadable)`);
198
+ return result;
199
+ }
200
+ for (const entry of entries) {
201
+ if (entry.startsWith("."))
202
+ continue;
203
+ const accountDir = (0, node_path_1.join)(input.accountsDir, entry);
204
+ try {
205
+ if (!(0, node_fs_1.statSync)(accountDir).isDirectory())
206
+ continue;
207
+ }
208
+ catch {
209
+ continue;
210
+ }
211
+ const accountJsonPath = (0, node_path_1.join)(accountDir, "account.json");
212
+ if (!(0, node_fs_1.existsSync)(accountJsonPath))
213
+ continue;
214
+ let admins = [];
215
+ try {
216
+ const config = JSON.parse((0, node_fs_1.readFileSync)(accountJsonPath, "utf-8"));
217
+ admins = (config.admins ?? []);
218
+ }
219
+ catch (err) {
220
+ const msg = err instanceof Error ? err.message : String(err);
221
+ console.error(`[${input.tag}] account.json unreadable source=${accountJsonPath} error=${msg}`);
222
+ continue;
223
+ }
224
+ for (const a of admins) {
225
+ if (typeof a.userId !== "string")
226
+ continue;
227
+ accountUserIds.add(a.userId);
228
+ if (!usersUserIds.has(a.userId)) {
229
+ const userIdShort = a.userId.slice(0, 8);
230
+ console.error(`[${input.tag}] direction=account-without-users userId=${userIdShort} source=${accountJsonPath}`);
231
+ result.accountWithoutUsers.push({ userId: a.userId, source: accountJsonPath });
232
+ result.divergences += 1;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ for (const uid of usersUserIds) {
238
+ if (!accountUserIds.has(uid)) {
239
+ const userIdShort = uid.slice(0, 8);
240
+ console.error(`[${input.tag}] direction=users-without-account userId=${userIdShort} source=${input.usersFile}`);
241
+ result.usersWithoutAccount.push({ userId: uid });
242
+ result.divergences += 1;
243
+ }
244
+ }
245
+ console.error(`[${input.tag}] check complete divergences=${result.divergences}`);
246
+ return result;
247
+ }
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}