@rubytech/create-realagent 1.0.650 → 1.0.652

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 (76) hide show
  1. package/dist/index.js +115 -6
  2. package/dist/pinned-binaries.js +43 -0
  3. package/package.json +1 -1
  4. package/payload/platform/lib/graph-trash/dist/index.d.ts +91 -0
  5. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -0
  6. package/payload/platform/lib/graph-trash/dist/index.js +238 -0
  7. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -0
  8. package/payload/platform/lib/graph-trash/src/index.ts +360 -0
  9. package/payload/platform/lib/graph-trash/tsconfig.json +8 -0
  10. package/payload/platform/neo4j/schema.cypher +19 -0
  11. package/payload/platform/package.json +2 -2
  12. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +42 -5
  13. package/payload/platform/plugins/contacts/mcp/dist/index.js +9 -4
  14. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts +16 -1
  16. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts.map +1 -1
  17. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js +23 -10
  18. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js.map +1 -1
  19. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -1
  20. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +2 -1
  21. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -1
  22. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  23. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +8 -4
  24. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  25. package/payload/platform/plugins/docs/references/deployment.md +1 -1
  26. package/payload/platform/plugins/docs/references/troubleshooting.md +27 -0
  27. package/payload/platform/plugins/memory/PLUGIN.md +4 -2
  28. package/payload/platform/plugins/memory/mcp/dist/index.js +92 -28
  29. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +30 -15
  31. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +46 -84
  33. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  34. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -0
  35. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +36 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -0
  38. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +16 -0
  40. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts +2 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts.map +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js +42 -3
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts +24 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts.map +1 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +40 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  50. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +35 -10
  51. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  52. package/payload/platform/plugins/memory/references/graph-primitives.md +49 -3
  53. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +4 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  55. package/payload/platform/scripts/lib/resolve-account-dir.sh +166 -0
  56. package/payload/platform/scripts/seed-neo4j.sh +7 -11
  57. package/payload/platform/templates/systemd/maxy-ttyd.service +6 -1
  58. package/payload/server/public/assets/{admin-S2KHPNe4.js → admin-DQxieG3v.js} +1 -1
  59. package/payload/server/public/assets/data-CAKMrPTQ.js +1 -0
  60. package/payload/server/public/assets/{file-CRrDfnO1.js → file-dBmvpAuH.js} +1 -1
  61. package/payload/server/public/assets/{graph-CSBMZGGe.js → graph-3snSy3WW.js} +2 -2
  62. package/payload/server/public/assets/{house-D1CBraxB.js → house-IMEjNkQf.js} +1 -1
  63. package/payload/server/public/assets/{jsx-runtime-CZtLX8NN.css → jsx-runtime-BJhXEiL3.css} +1 -1
  64. package/payload/server/public/assets/{public-ukz9-gSe.js → public-b43rEAhq.js} +1 -1
  65. package/payload/server/public/assets/{share-2-DkF8neDM.js → share-2-ARoCxH5K.js} +1 -1
  66. package/payload/server/public/assets/{trash-2-Dde58AAR.js → trash-2-D_Rm8z21.js} +1 -1
  67. package/payload/server/public/assets/{useVoiceRecorder-Hz9X6luB.js → useVoiceRecorder-Cw8gxj1L.js} +1 -1
  68. package/payload/server/public/assets/x-CFPIrGuL.js +1 -0
  69. package/payload/server/public/data.html +7 -7
  70. package/payload/server/public/graph.html +6 -6
  71. package/payload/server/public/index.html +8 -8
  72. package/payload/server/public/public.html +5 -5
  73. package/payload/server/server.js +19 -0
  74. package/payload/server/public/assets/data-BajF3t1i.js +0 -1
  75. package/payload/server/public/assets/x-D52qhSya.js +0 -1
  76. /package/payload/server/public/assets/{jsx-runtime-Cb-WunFZ.js → jsx-runtime-CXoJCO3U.js} +0 -0
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
4
4
  import { resolve, join, dirname } from "node:path";
5
- import { randomBytes } from "node:crypto";
5
+ import { randomBytes, createHash } from "node:crypto";
6
+ import { TTYD_VERSION, TTYD_SHA256_BY_ARCH, mapUnameToTtydArch, ttydDownloadUrl, } from "./pinned-binaries.js";
6
7
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
7
8
  // Brand manifest — read from payload to derive all brand-specific installation values.
8
9
  // The bundler stamps brand.json into the payload at build time.
@@ -220,11 +221,10 @@ function installSystemDeps() {
220
221
  shell("apt-get", ["install", "-y", "curl", "git", "unzip", "jq", "avahi-daemon", "avahi-utils", "poppler-utils", "ffmpeg"], { sudo: true });
221
222
  shell("apt-get", ["install", "-y", "tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium"], { sudo: true });
222
223
  shell("apt-get", ["install", "-y", "hostapd", "dnsmasq"], { sudo: true });
223
- // ttyd + tmux power the admin terminal surface (Task 591) — ttyd serves a
224
- // WebSocket PTY on 127.0.0.1:7681, tmux provides the persistent named
225
- // session that outlives admin-server restarts. Both are in Debian/Raspbian
226
- // repos; no third-party source needed.
227
- shell("apt-get", ["install", "-y", "ttyd", "tmux"], { sudo: true });
224
+ // tmux backs the admin terminal's persistent named session (Task 591).
225
+ // ttyd is NOT in Debian Bookworm's apt repo (Task 602) — it ships as a
226
+ // pinned upstream binary installed by provisionTtydBinary() in step 11.
227
+ shell("apt-get", ["install", "-y", "tmux"], { sudo: true });
228
228
  }
229
229
  else {
230
230
  console.log(" Skipping apt-get (sudo unavailable non-interactively — deps assumed present from prior install)");
@@ -1504,6 +1504,103 @@ function installCrons() {
1504
1504
  logFile(` crontab write failed: ${write.stderr}`);
1505
1505
  }
1506
1506
  }
1507
+ const TTYD_INSTALL_PATH = "/usr/local/bin/ttyd";
1508
+ function sha256File(path) {
1509
+ const hash = createHash("sha256");
1510
+ hash.update(readFileSync(path));
1511
+ return hash.digest("hex");
1512
+ }
1513
+ // Provision the upstream ttyd binary into /usr/local/bin/ttyd. Degrades with
1514
+ // a loud warning and a copy-pasteable remediation command on any failure —
1515
+ // never throws. Contract: the caller (installTerminalService) uses the
1516
+ // presence of TTYD_INSTALL_PATH after return to decide whether to enable the
1517
+ // maxy-ttyd.service systemd unit. ttyd is NOT in Debian Bookworm apt, so we
1518
+ // own the full download / verify / install flow here.
1519
+ function provisionTtydBinary() {
1520
+ const unameRaw = spawnSync("uname", ["-m"], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
1521
+ const uname = (unameRaw.stdout || "").trim();
1522
+ const arch = mapUnameToTtydArch(uname);
1523
+ if (arch === null) {
1524
+ console.error(` WARNING: ttyd — unsupported architecture 'uname -m'='${uname}'. Admin terminal will be unavailable.`);
1525
+ console.error(` Remediate: install ttyd ${TTYD_VERSION} manually for your platform and place it at ${TTYD_INSTALL_PATH}, then 'sudo chmod +x ${TTYD_INSTALL_PATH}'.`);
1526
+ return false;
1527
+ }
1528
+ const pinnedDigest = TTYD_SHA256_BY_ARCH[arch];
1529
+ const url = ttydDownloadUrl(arch);
1530
+ const remediation = `curl -L -o /tmp/ttyd.${arch} '${url}' && sudo mv /tmp/ttyd.${arch} ${TTYD_INSTALL_PATH} && sudo chmod +x ${TTYD_INSTALL_PATH}`;
1531
+ // Idempotency: existing binary with matching pinned digest → skip download.
1532
+ if (existsSync(TTYD_INSTALL_PATH)) {
1533
+ try {
1534
+ const existingDigest = sha256File(TTYD_INSTALL_PATH);
1535
+ if (existingDigest === pinnedDigest) {
1536
+ console.log(` ttyd ${TTYD_VERSION} already installed at ${TTYD_INSTALL_PATH} (SHA256 match — skipping download)`);
1537
+ return true;
1538
+ }
1539
+ console.log(` ttyd at ${TTYD_INSTALL_PATH} has different digest — replacing with pinned ${TTYD_VERSION}`);
1540
+ }
1541
+ catch (err) {
1542
+ console.error(` WARNING: could not read existing ${TTYD_INSTALL_PATH}: ${err instanceof Error ? err.message : String(err)} — will overwrite`);
1543
+ }
1544
+ }
1545
+ if (!canSudo()) {
1546
+ console.error(` WARNING: ttyd — sudo unavailable non-interactively, cannot write ${TTYD_INSTALL_PATH}. Admin terminal will be unavailable.`);
1547
+ console.error(` Remediate: ${remediation}`);
1548
+ return false;
1549
+ }
1550
+ const tmpPath = `/tmp/ttyd.${arch}`;
1551
+ try {
1552
+ console.log(` Downloading ttyd ${TTYD_VERSION} for ${arch} from ${url}`);
1553
+ shellRetry("curl", ["-fL", "--retry", "3", "--retry-delay", "5", "-o", tmpPath, url], { timeout: 60_000 });
1554
+ }
1555
+ catch (err) {
1556
+ console.error(` WARNING: ttyd download failed: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1557
+ console.error(` Remediate: ${remediation}`);
1558
+ try {
1559
+ unlinkSync(tmpPath);
1560
+ }
1561
+ catch { /* nothing to clean */ }
1562
+ return false;
1563
+ }
1564
+ let actualDigest;
1565
+ try {
1566
+ actualDigest = sha256File(tmpPath);
1567
+ }
1568
+ catch (err) {
1569
+ console.error(` WARNING: ttyd — could not read downloaded file ${tmpPath}: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1570
+ try {
1571
+ unlinkSync(tmpPath);
1572
+ }
1573
+ catch { /* nothing to clean */ }
1574
+ return false;
1575
+ }
1576
+ if (actualDigest !== pinnedDigest) {
1577
+ console.error(` WARNING: ttyd SHA256 mismatch — refusing to install unverified binary.`);
1578
+ console.error(` expected: ${pinnedDigest}`);
1579
+ console.error(` actual: ${actualDigest}`);
1580
+ console.error(` Admin terminal will be unavailable. A later installer version may pin a newer digest.`);
1581
+ try {
1582
+ unlinkSync(tmpPath);
1583
+ }
1584
+ catch { /* nothing to clean */ }
1585
+ return false;
1586
+ }
1587
+ console.log(` ttyd ${TTYD_VERSION} SHA256 verified (${actualDigest.slice(0, 12)}…)`);
1588
+ try {
1589
+ shell("mv", [tmpPath, TTYD_INSTALL_PATH], { sudo: true });
1590
+ shell("chmod", ["+x", TTYD_INSTALL_PATH], { sudo: true });
1591
+ }
1592
+ catch (err) {
1593
+ console.error(` WARNING: ttyd — could not install to ${TTYD_INSTALL_PATH}: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1594
+ console.error(` Remediate: ${remediation}`);
1595
+ try {
1596
+ unlinkSync(tmpPath);
1597
+ }
1598
+ catch { /* already moved or cleaned */ }
1599
+ return false;
1600
+ }
1601
+ console.log(` ttyd ${TTYD_VERSION} installed at ${TTYD_INSTALL_PATH}`);
1602
+ return true;
1603
+ }
1507
1604
  function installTerminalService() {
1508
1605
  log("11", TOTAL, "Installing admin terminal service (ttyd + tmux)...");
1509
1606
  if (!isLinux()) {
@@ -1511,6 +1608,11 @@ function installTerminalService() {
1511
1608
  console.log(" brew install ttyd tmux && ttyd -p 7681 -i 127.0.0.1 -W tmux new-session -A -s maxy-pty");
1512
1609
  return;
1513
1610
  }
1611
+ // ttyd is provisioned from upstream GitHub releases (pinned + SHA256-verified)
1612
+ // because Debian Bookworm's apt does NOT carry a ttyd package (Task 602).
1613
+ // A failure here is loud but non-fatal — the rest of the install completes
1614
+ // and the admin UI degrades to "terminal unavailable" per Task 603.
1615
+ const ttydReady = provisionTtydBinary();
1514
1616
  // Default ~/.tmux.conf — only written if the operator doesn't already have
1515
1617
  // one. `history-limit 50000` is load-bearing: a closed-tab + reopen during
1516
1618
  // an upgrade must show every line the operator missed in scrollback.
@@ -1541,6 +1643,13 @@ function installTerminalService() {
1541
1643
  // unit and every subsequent install is a no-op (idempotent overwrite).
1542
1644
  const systemdUserDir = resolve(homeDir, ".config/systemd/user");
1543
1645
  mkdirSync(systemdUserDir, { recursive: true });
1646
+ // Skip systemd-unit install if the ttyd binary is not in place — enabling
1647
+ // a unit whose ExecStart points at a missing file just churns systemd with
1648
+ // restart failures.
1649
+ if (!ttydReady) {
1650
+ console.error(" Skipping maxy-ttyd.service install — ttyd binary not present. Admin terminal will be unavailable until remediated.");
1651
+ return;
1652
+ }
1544
1653
  const ttydUnitTemplate = resolve(INSTALL_DIR, "platform/templates/systemd/maxy-ttyd.service");
1545
1654
  const ttydUnitDest = join(systemdUserDir, "maxy-ttyd.service");
1546
1655
  try {
@@ -0,0 +1,43 @@
1
+ // Pinned upstream binaries installed by the Linux provisioner.
2
+ //
3
+ // Some binaries we depend on are not available in Debian Bookworm's apt repo
4
+ // (e.g. ttyd — entered Debian trixie and Ubuntu 22.04+ but NOT Bookworm, which
5
+ // is the base of current Raspberry Pi OS). For those, we download the upstream
6
+ // prebuilt static binary from GitHub releases, SHA256-verified, and place it
7
+ // under /usr/local/bin. This module is the single source of truth for every
8
+ // such dependency — version bumps are a one-file change.
9
+ //
10
+ // Verification rule: on SHA256 mismatch the installer aborts the download
11
+ // step with a loud error and never writes the binary. No silent-install of
12
+ // unverified bytes. See packages/create-maxy/src/index.ts → provisionTtydBinary.
13
+ export const TTYD_VERSION = '1.7.7';
14
+ // Asset file name as published on github.com/tsl0922/ttyd/releases.
15
+ export const TTYD_ASSET_BY_ARCH = {
16
+ aarch64: 'ttyd.aarch64',
17
+ arm: 'ttyd.arm',
18
+ x86_64: 'ttyd.x86_64',
19
+ };
20
+ // SHA256 digest of each asset for TTYD_VERSION. Computed by downloading the
21
+ // release asset directly from GitHub and running `sha256sum`. Bump together
22
+ // with TTYD_VERSION — never update one without the other.
23
+ export const TTYD_SHA256_BY_ARCH = {
24
+ aarch64: 'b38acadd89d1d396a0f5649aa52c539edbad07f4bc7348b27b4f4b7219dd4165',
25
+ arm: '05eac1223914f18c65898d72c8d14e76bbb5435f7762c6dc7f16f041994a8109',
26
+ x86_64: '8a217c968aba172e0dbf3f34447218dc015bc4d5e59bf51db2f2cd12b7be4f55',
27
+ };
28
+ // Map Linux kernel `uname -m` output to a TtydArch key. Returns null for
29
+ // architectures we have not pinned a binary for — the caller must treat this
30
+ // as a hard error (no silent fallback, no "try latest").
31
+ export function mapUnameToTtydArch(uname) {
32
+ const trimmed = uname.trim();
33
+ if (trimmed === 'aarch64' || trimmed === 'arm64')
34
+ return 'aarch64';
35
+ if (trimmed === 'armv7l' || trimmed === 'armv6l' || trimmed === 'arm')
36
+ return 'arm';
37
+ if (trimmed === 'x86_64' || trimmed === 'amd64')
38
+ return 'x86_64';
39
+ return null;
40
+ }
41
+ export function ttydDownloadUrl(arch) {
42
+ return `https://github.com/tsl0922/ttyd/releases/download/${TTYD_VERSION}/${TTYD_ASSET_BY_ARCH[arch]}`;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.650",
3
+ "version": "1.0.652",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Soft-delete primitive for the knowledge graph.
3
+ *
4
+ * MCP-tool callers must NEVER `DETACH DELETE` user-domain nodes. Use
5
+ * `trashNode` to mark a node `:Trashed`; `restoreNode` removes the label;
6
+ * `emptyTrash` hard-deletes nodes whose `trashedAt < now - graceDays`. The
7
+ * 2026-04-20 incident wiped 19 nodes via a single autonomous `DETACH DELETE`
8
+ * — Neo4j Community has no PITR, so properties were unrecoverable. This
9
+ * primitive contains the blast radius for every caller.
10
+ *
11
+ * Unique-constraint handling: when the trashed node's labels carry single-
12
+ * key UNIQUE constraints (e.g. `OnboardingState.accountId`), the live values
13
+ * are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
14
+ * MERGE against the same key won't collide. `restoreNode` writes them back
15
+ * and fails loudly when an active node already occupies the slot.
16
+ */
17
+ import type { Session } from "neo4j-driver";
18
+ export interface TrashParams {
19
+ session: Session;
20
+ accountId: string;
21
+ elementId: string;
22
+ /** Provenance marker — appears verbatim in `trashedBy` and `[trash:marked] by=`. */
23
+ by: string;
24
+ reason?: string;
25
+ }
26
+ export interface TrashResult {
27
+ trashed: boolean;
28
+ alreadyTrashed: boolean;
29
+ nodeId: string;
30
+ /** Labels excluding `:Trashed`. */
31
+ labels: string[];
32
+ trashedAt: string;
33
+ originalKeys: Record<string, unknown>;
34
+ }
35
+ export declare function trashNode(params: TrashParams): Promise<TrashResult>;
36
+ export interface RestoreParams {
37
+ session: Session;
38
+ accountId: string;
39
+ elementId: string;
40
+ }
41
+ export interface RestoreResult {
42
+ restored: boolean;
43
+ nodeId: string;
44
+ labels: string[];
45
+ restoredKeys: Record<string, unknown>;
46
+ }
47
+ export declare function restoreNode(params: RestoreParams): Promise<RestoreResult>;
48
+ export interface EmptyTrashParams {
49
+ session: Session;
50
+ accountId: string;
51
+ /** Default 30. */
52
+ graceDays?: number;
53
+ dryRun?: boolean;
54
+ /** Optional label whitelist — only nodes carrying any of these labels are eligible. */
55
+ labels?: string[];
56
+ /** Side-effect callback invoked per emptied node, e.g. to clean disk attachments. */
57
+ onEmpty?: (node: TrashCandidate) => Promise<void>;
58
+ }
59
+ export interface TrashCandidate {
60
+ elementId: string;
61
+ labels: string[];
62
+ trashedAt: string;
63
+ trashedBy: string | null;
64
+ /** Original unique-key snapshot — useful for KnowledgeDocument disk cleanup. */
65
+ trashedKeys: Record<string, unknown>;
66
+ }
67
+ export interface EmptyTrashResult {
68
+ graceDays: number;
69
+ dryRun: boolean;
70
+ candidates: TrashCandidate[];
71
+ emptied: number;
72
+ }
73
+ export declare function emptyTrash(params: EmptyTrashParams): Promise<EmptyTrashResult>;
74
+ /**
75
+ * Read-filter clause excluding trashed nodes for the given Cypher alias.
76
+ *
77
+ * Filters both the `:Trashed` label (Task 576 primitive) and the legacy
78
+ * `deletedAt` property (KnowledgeDocument soft-delete pre-Task 576). The
79
+ * `deletedAt` arm is a transitional belt-and-braces — production graphs may
80
+ * still hold pre-migration soft-deletes — and stays until those are confirmed
81
+ * empty, then is dropped.
82
+ *
83
+ * notTrashed("node") → "(NOT node:Trashed AND node.deletedAt IS NULL)"
84
+ * notTrashed("related") → "(NOT related:Trashed AND related.deletedAt IS NULL)"
85
+ */
86
+ export declare function notTrashed(alias: string): string;
87
+ /** Runtime accessor for the static unique-keys map (for tests + sibling tooling). */
88
+ export declare function uniqueKeysForLabels(labels: string[]): string[];
89
+ /** Property names trashNode writes — exposed so callers can re-MERGE without colliding. */
90
+ export declare const TRASH_METADATA_PROPS: readonly string[];
91
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyEzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAkE/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0EpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAI9D;AAED,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAqB,CAAC"}
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ /**
3
+ * Soft-delete primitive for the knowledge graph.
4
+ *
5
+ * MCP-tool callers must NEVER `DETACH DELETE` user-domain nodes. Use
6
+ * `trashNode` to mark a node `:Trashed`; `restoreNode` removes the label;
7
+ * `emptyTrash` hard-deletes nodes whose `trashedAt < now - graceDays`. The
8
+ * 2026-04-20 incident wiped 19 nodes via a single autonomous `DETACH DELETE`
9
+ * — Neo4j Community has no PITR, so properties were unrecoverable. This
10
+ * primitive contains the blast radius for every caller.
11
+ *
12
+ * Unique-constraint handling: when the trashed node's labels carry single-
13
+ * key UNIQUE constraints (e.g. `OnboardingState.accountId`), the live values
14
+ * are snapshotted into `_trashedKeys` (JSON) and nulled on the node, so
15
+ * MERGE against the same key won't collide. `restoreNode` writes them back
16
+ * and fails loudly when an active node already occupies the slot.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.TRASH_METADATA_PROPS = void 0;
20
+ exports.trashNode = trashNode;
21
+ exports.restoreNode = restoreNode;
22
+ exports.emptyTrash = emptyTrash;
23
+ exports.notTrashed = notTrashed;
24
+ exports.uniqueKeysForLabels = uniqueKeysForLabels;
25
+ /**
26
+ * Single-key UNIQUE properties per label, mirroring `platform/neo4j/schema.cypher`.
27
+ *
28
+ * Composite UNIQUEs (UserProfile, Email composite, AccessGrant) are
29
+ * represented by a single nullable component — Neo4j enforces a composite
30
+ * constraint only when every component is non-null, so nulling one frees it.
31
+ */
32
+ const UNIQUE_KEYS_BY_LABEL = {
33
+ Person: ["email", "telephone"],
34
+ Service: ["serviceId"],
35
+ LocalBusiness: ["accountId"],
36
+ Task: ["taskId"],
37
+ Event: ["eventId"],
38
+ KnowledgeDocument: ["attachmentId"],
39
+ DigitalDocument: ["attachmentId"],
40
+ Conversation: ["conversationId", "sessionKey"],
41
+ Message: ["messageId"],
42
+ OnboardingState: ["accountId"],
43
+ Workflow: ["workflowId"],
44
+ WorkflowStep: ["stepId"],
45
+ WorkflowRun: ["runId"],
46
+ Preference: ["preferenceId"],
47
+ Email: ["emailId", "messageId"],
48
+ AdminUser: ["userId"],
49
+ ToolCall: ["callId"],
50
+ // Composite component nulls — frees the composite constraint:
51
+ AccessGrant: ["contactValue"], // composite (contactValue, agentSlug, accountId)
52
+ UserProfile: ["userId"], // composite (accountId, userId)
53
+ };
54
+ const TRASH_PROP_NAMES = [
55
+ "trashedAt",
56
+ "trashedBy",
57
+ "trashReason",
58
+ "_trashedKeys",
59
+ ];
60
+ async function trashNode(params) {
61
+ const { session, accountId, elementId, by, reason } = params;
62
+ const lookup = await session.run(`MATCH (n) WHERE elementId(n) = $eid AND n.accountId = $accountId
63
+ RETURN labels(n) AS labels, properties(n) AS props`, { eid: elementId, accountId });
64
+ if (lookup.records.length === 0) {
65
+ throw new Error(`trashNode: node not found (elementId=${elementId} accountId=${accountId.slice(0, 8)}…)`);
66
+ }
67
+ const allLabels = lookup.records[0].get("labels");
68
+ const props = lookup.records[0].get("props");
69
+ const baseLabels = allLabels.filter((l) => l !== "Trashed");
70
+ if (allLabels.includes("Trashed")) {
71
+ return {
72
+ trashed: false,
73
+ alreadyTrashed: true,
74
+ nodeId: elementId,
75
+ labels: baseLabels,
76
+ trashedAt: String(props.trashedAt ?? ""),
77
+ originalKeys: {},
78
+ };
79
+ }
80
+ const uniqueKeys = new Set();
81
+ for (const label of baseLabels) {
82
+ for (const key of UNIQUE_KEYS_BY_LABEL[label] ?? [])
83
+ uniqueKeys.add(key);
84
+ }
85
+ const originalKeys = {};
86
+ for (const k of uniqueKeys) {
87
+ if (props[k] !== undefined && props[k] !== null)
88
+ originalKeys[k] = props[k];
89
+ }
90
+ const setNullClauses = Object.keys(originalKeys)
91
+ .map((k) => `n.\`${k}\` = null`)
92
+ .join(", ");
93
+ const setNullSuffix = setNullClauses ? `, ${setNullClauses}` : "";
94
+ const trashedAt = new Date().toISOString();
95
+ await session.run(`MATCH (n) WHERE elementId(n) = $eid
96
+ SET n:Trashed,
97
+ n.trashedAt = datetime($trashedAt),
98
+ n.trashedBy = $by,
99
+ n.trashReason = $reason,
100
+ n._trashedKeys = $trashedKeysJson${setNullSuffix}`, {
101
+ eid: elementId,
102
+ trashedAt,
103
+ by,
104
+ reason: reason ?? null,
105
+ trashedKeysJson: JSON.stringify(originalKeys),
106
+ });
107
+ process.stderr.write(`[trash:marked] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")} by=${by} reason=${reason ?? "null"}\n`);
108
+ return {
109
+ trashed: true,
110
+ alreadyTrashed: false,
111
+ nodeId: elementId,
112
+ labels: baseLabels,
113
+ trashedAt,
114
+ originalKeys,
115
+ };
116
+ }
117
+ async function restoreNode(params) {
118
+ const { session, accountId, elementId } = params;
119
+ const lookup = await session.run(`MATCH (n:Trashed) WHERE elementId(n) = $eid
120
+ RETURN labels(n) AS labels, n._trashedKeys AS keysJson`, { eid: elementId });
121
+ if (lookup.records.length === 0) {
122
+ throw new Error(`restoreNode: trashed node not found (elementId=${elementId})`);
123
+ }
124
+ const allLabels = lookup.records[0].get("labels");
125
+ const baseLabels = allLabels.filter((l) => l !== "Trashed");
126
+ const keysJson = lookup.records[0].get("keysJson");
127
+ const originalKeys = keysJson ? JSON.parse(keysJson) : {};
128
+ // Conflict check: an active node already holds the unique slot we want back?
129
+ for (const label of baseLabels) {
130
+ const uniqueKeys = UNIQUE_KEYS_BY_LABEL[label] ?? [];
131
+ for (const k of uniqueKeys) {
132
+ const v = originalKeys[k];
133
+ if (v === undefined || v === null)
134
+ continue;
135
+ const conflict = await session.run(`MATCH (other:\`${label}\`)
136
+ WHERE elementId(other) <> $eid
137
+ AND NOT other:Trashed
138
+ AND other.\`${k}\` = $val
139
+ RETURN elementId(other) AS otherId LIMIT 1`, { eid: elementId, val: v });
140
+ if (conflict.records.length > 0) {
141
+ const otherId = conflict.records[0].get("otherId");
142
+ throw new Error(`restoreNode: cannot restore ${label} elementId=${elementId} — active node elementId=${otherId} already holds ${k}=${JSON.stringify(v)}`);
143
+ }
144
+ }
145
+ }
146
+ const setClauses = Object.keys(originalKeys)
147
+ .map((k) => `n.\`${k}\` = $val_${k}`)
148
+ .join(", ");
149
+ const setSuffix = setClauses ? `, ${setClauses}` : "";
150
+ const setParams = { eid: elementId };
151
+ for (const [k, v] of Object.entries(originalKeys))
152
+ setParams[`val_${k}`] = v;
153
+ await session.run(`MATCH (n:Trashed) WHERE elementId(n) = $eid
154
+ REMOVE n:Trashed, n.trashedAt, n.trashedBy, n.trashReason, n._trashedKeys
155
+ SET n.restoredAt = datetime()${setSuffix}`, setParams);
156
+ process.stderr.write(`[trash:restored] accountId=${accountId} elementId=${elementId} labels=${baseLabels.join(",")}\n`);
157
+ return {
158
+ restored: true,
159
+ nodeId: elementId,
160
+ labels: baseLabels,
161
+ restoredKeys: originalKeys,
162
+ };
163
+ }
164
+ async function emptyTrash(params) {
165
+ const t0 = Date.now();
166
+ const { session, accountId, graceDays = 30, dryRun = false, labels: labelFilter, onEmpty } = params;
167
+ const cutoff = new Date(Date.now() - graceDays * 24 * 60 * 60 * 1000).toISOString();
168
+ const labelClause = labelFilter && labelFilter.length > 0
169
+ ? `AND ANY(l IN labels(n) WHERE l IN $labelFilter)`
170
+ : "";
171
+ const candidatesResult = await session.run(`MATCH (n:Trashed)
172
+ WHERE n.trashedAt < datetime($cutoff)
173
+ ${labelClause}
174
+ RETURN elementId(n) AS eid,
175
+ labels(n) AS labels,
176
+ toString(n.trashedAt) AS trashedAt,
177
+ n.trashedBy AS trashedBy,
178
+ n._trashedKeys AS keysJson
179
+ ORDER BY n.trashedAt ASC`, { cutoff, ...(labelFilter ? { labelFilter } : {}) });
180
+ const candidates = candidatesResult.records.map((r) => {
181
+ const keysJson = r.get("keysJson");
182
+ return {
183
+ elementId: r.get("eid"),
184
+ labels: r.get("labels").filter((l) => l !== "Trashed"),
185
+ trashedAt: String(r.get("trashedAt")),
186
+ trashedBy: r.get("trashedBy") ?? null,
187
+ trashedKeys: keysJson ? JSON.parse(keysJson) : {},
188
+ };
189
+ });
190
+ process.stderr.write(`[trash:empty-run] accountId=${accountId} graceDays=${graceDays} dryRun=${dryRun} candidates=${candidates.length}\n`);
191
+ if (dryRun || candidates.length === 0) {
192
+ process.stderr.write(`[graph:trash:summary] accountId=${accountId} trashedNow=0 emptiedThisRun=0 graceDays=${graceDays} dryRun=${dryRun} elapsedMs=${Date.now() - t0}\n`);
193
+ return { graceDays, dryRun, candidates, emptied: 0 };
194
+ }
195
+ let emptied = 0;
196
+ for (const c of candidates) {
197
+ if (onEmpty) {
198
+ try {
199
+ await onEmpty(c);
200
+ }
201
+ catch (err) {
202
+ process.stderr.write(`[trash:empty-run] onEmpty side-effect failed for elementId=${c.elementId}: ${err instanceof Error ? err.message : String(err)}\n`);
203
+ }
204
+ }
205
+ await session.run(`MATCH (n) WHERE elementId(n) = $eid DETACH DELETE n`, { eid: c.elementId });
206
+ const ageDays = Math.floor((Date.now() - new Date(c.trashedAt).getTime()) / (24 * 60 * 60 * 1000));
207
+ process.stderr.write(`[trash:emptied] accountId=${accountId} elementId=${c.elementId} labels=${c.labels.join(",")} trashedAt=${c.trashedAt} ageDays=${ageDays}\n`);
208
+ emptied++;
209
+ }
210
+ process.stderr.write(`[graph:trash:summary] accountId=${accountId} trashedNow=0 emptiedThisRun=${emptied} graceDays=${graceDays} dryRun=${dryRun} elapsedMs=${Date.now() - t0}\n`);
211
+ return { graceDays, dryRun, candidates, emptied };
212
+ }
213
+ /**
214
+ * Read-filter clause excluding trashed nodes for the given Cypher alias.
215
+ *
216
+ * Filters both the `:Trashed` label (Task 576 primitive) and the legacy
217
+ * `deletedAt` property (KnowledgeDocument soft-delete pre-Task 576). The
218
+ * `deletedAt` arm is a transitional belt-and-braces — production graphs may
219
+ * still hold pre-migration soft-deletes — and stays until those are confirmed
220
+ * empty, then is dropped.
221
+ *
222
+ * notTrashed("node") → "(NOT node:Trashed AND node.deletedAt IS NULL)"
223
+ * notTrashed("related") → "(NOT related:Trashed AND related.deletedAt IS NULL)"
224
+ */
225
+ function notTrashed(alias) {
226
+ return `(NOT \`${alias}\`:Trashed AND \`${alias}\`.deletedAt IS NULL)`;
227
+ }
228
+ /** Runtime accessor for the static unique-keys map (for tests + sibling tooling). */
229
+ function uniqueKeysForLabels(labels) {
230
+ const out = new Set();
231
+ for (const l of labels)
232
+ for (const k of UNIQUE_KEYS_BY_LABEL[l] ?? [])
233
+ out.add(k);
234
+ return [...out];
235
+ }
236
+ /** Property names trashNode writes — exposed so callers can re-MERGE without colliding. */
237
+ exports.TRASH_METADATA_PROPS = TRASH_PROP_NAMES;
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}