@rubytech/create-realagent 1.0.818 → 1.0.820

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 (58) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts +2 -0
  3. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts.map +1 -0
  4. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +168 -0
  5. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -0
  6. package/payload/platform/lib/graph-write/dist/index.d.ts +28 -5
  7. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  8. package/payload/platform/lib/graph-write/dist/index.js +97 -3
  9. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  10. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +191 -0
  11. package/payload/platform/lib/graph-write/src/index.ts +90 -9
  12. package/payload/platform/neo4j/schema.cypher +24 -0
  13. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +25 -8
  14. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
  15. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +70 -20
  16. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  17. package/payload/platform/plugins/docs/references/graph.md +20 -0
  18. package/payload/platform/plugins/docs/references/internals.md +16 -0
  19. package/payload/platform/plugins/email/PLUGIN.md +2 -0
  20. package/payload/platform/plugins/memory/PLUGIN.md +6 -0
  21. package/payload/platform/plugins/memory/mcp/dist/index.js +15 -4
  22. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  23. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +8 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +26 -2
  26. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  27. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +20 -0
  28. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  29. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +40 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  31. package/payload/platform/plugins/memory/references/schema-base.md +8 -0
  32. package/payload/platform/plugins/tasks/PLUGIN.md +2 -2
  33. package/payload/platform/plugins/tasks/mcp/dist/index.js +10 -5
  34. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  35. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +27 -1
  36. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -1
  37. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +45 -2
  38. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -1
  39. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts +20 -1
  40. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts.map +1 -1
  41. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js +46 -6
  42. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js.map +1 -1
  43. package/payload/server/chunk-5OG7TUQL.js +315 -0
  44. package/payload/server/chunk-CZGOB575.js +593 -0
  45. package/payload/server/chunk-NUXYHO6N.js +10079 -0
  46. package/payload/server/chunk-SALVIGXH.js +1116 -0
  47. package/payload/server/chunk-ZT6LKDTP.js +2238 -0
  48. package/payload/server/client-pool-IQU6H43X.js +32 -0
  49. package/payload/server/cloudflare-task-tracker-Q4X5BYR7.js +17 -0
  50. package/payload/server/maxy-edge.js +4 -3
  51. package/payload/server/neo4j-migrations-CYIKMSEO.js +366 -0
  52. package/payload/server/public/assets/admin-BoGPEBe_.js +352 -0
  53. package/payload/server/public/assets/{graph-DeH6ulGh.js → graph-LLMJa4Ch.js} +1 -1
  54. package/payload/server/public/assets/{page-WIAWD2Oi.js → page-DoaF3DB0.js} +1 -1
  55. package/payload/server/public/graph.html +2 -2
  56. package/payload/server/public/index.html +2 -2
  57. package/payload/server/server.js +276 -16
  58. package/payload/server/public/assets/admin-CdVYoqKD.js +0 -352
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.818",
3
+ "version": "1.0.820",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=action-provenance-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-provenance-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/action-provenance-gate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,168 @@
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(opts) {
10
+ const calls = [];
11
+ const tx = {
12
+ async run(cypher, params = {}) {
13
+ calls.push({ cypher, params });
14
+ if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
15
+ const ids = params.ids ?? [];
16
+ const records = ids
17
+ .filter((id) => opts.preCheckLabelsByTarget.has(id))
18
+ .map((id) => ({
19
+ get(field) {
20
+ if (field === "id")
21
+ return id;
22
+ if (field === "labels")
23
+ return opts.preCheckLabelsByTarget.get(id) ?? [];
24
+ throw new Error(`unknown field ${field}`);
25
+ },
26
+ }));
27
+ return { records };
28
+ }
29
+ if (cypher.includes("CREATE (n:")) {
30
+ return {
31
+ records: [
32
+ {
33
+ get(field) {
34
+ if (field === "nodeId")
35
+ return "new-element-id";
36
+ if (field === "nodeLabels")
37
+ return ["Person"];
38
+ throw new Error(`unknown field ${field}`);
39
+ },
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
45
+ return {
46
+ records: [],
47
+ summary: {
48
+ counters: {
49
+ updates() {
50
+ return { relationshipsCreated: opts.edgesCreatedPerCall ?? 1 };
51
+ },
52
+ },
53
+ },
54
+ };
55
+ }
56
+ return { records: [] };
57
+ },
58
+ };
59
+ const session = {
60
+ async executeWrite(work) {
61
+ return await work(tx);
62
+ },
63
+ };
64
+ return { session, calls };
65
+ }
66
+ (0, node_test_1.default)("ACTION_PROVENANCE_LABELS includes the documented set", () => {
67
+ for (const label of [
68
+ "Person",
69
+ "UserProfile",
70
+ "AdminUser",
71
+ "Organization",
72
+ "LocalBusiness",
73
+ "CloudflareTunnel",
74
+ "CloudflareHostname",
75
+ ]) {
76
+ strict_1.default.ok(index_js_1.ACTION_PROVENANCE_LABELS.has(label), `${label} should be in ACTION_PROVENANCE_LABELS`);
77
+ }
78
+ });
79
+ (0, node_test_1.default)("writeNodeWithEdges rejects :Person write missing PRODUCED-from-Task edge", async () => {
80
+ const { session } = makeStubSession({
81
+ preCheckLabelsByTarget: new Map([["other-target", ["Conversation"]]]),
82
+ });
83
+ const relationships = [
84
+ { type: "PART_OF", direction: "outgoing", targetNodeId: "other-target" },
85
+ ];
86
+ await strict_1.default.rejects(() => (0, index_js_1.writeNodeWithEdges)({
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ session: session,
89
+ labels: ["Person"],
90
+ props: { accountId: "acct-123" },
91
+ relationships,
92
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
93
+ }), /missing-action-provenance|Process provenance/i);
94
+ });
95
+ (0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED-from-Task edge", async () => {
96
+ const { session } = makeStubSession({
97
+ preCheckLabelsByTarget: new Map([
98
+ ["task-id", ["Task"]],
99
+ ]),
100
+ });
101
+ const relationships = [
102
+ { type: "PRODUCED", direction: "incoming", targetNodeId: "task-id" },
103
+ ];
104
+ const res = await (0, index_js_1.writeNodeWithEdges)({
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ session: session,
107
+ labels: ["Person"],
108
+ props: { accountId: "acct-123" },
109
+ relationships,
110
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
111
+ });
112
+ strict_1.default.equal(res.edgesCreated, 1);
113
+ });
114
+ (0, node_test_1.default)("writeNodeWithEdges accepts :Person write with no Task edge when agent='system' (bootstrap exemption)", async () => {
115
+ const { session } = makeStubSession({
116
+ preCheckLabelsByTarget: new Map([["admin-user-id", ["AdminUser"]]]),
117
+ });
118
+ const relationships = [
119
+ { type: "OWNS", direction: "incoming", targetNodeId: "admin-user-id" },
120
+ ];
121
+ const res = await (0, index_js_1.writeNodeWithEdges)({
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ session: session,
124
+ labels: ["Person"],
125
+ props: { accountId: "acct-123" },
126
+ relationships,
127
+ createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
128
+ });
129
+ strict_1.default.equal(res.edgesCreated, 1);
130
+ });
131
+ (0, node_test_1.default)("writeNodeWithEdges rejects :Person write whose 'PRODUCED-incoming' source is NOT a :Task", async () => {
132
+ const { session } = makeStubSession({
133
+ preCheckLabelsByTarget: new Map([
134
+ ["other-id", ["Conversation"]],
135
+ ]),
136
+ });
137
+ const relationships = [
138
+ // Inbound PRODUCED edge but the source is a Conversation, not a Task.
139
+ // Must reject — prevents bypass via spoofed PRODUCED edge from any node.
140
+ { type: "PRODUCED", direction: "incoming", targetNodeId: "other-id" },
141
+ ];
142
+ await strict_1.default.rejects(() => (0, index_js_1.writeNodeWithEdges)({
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ session: session,
145
+ labels: ["Person"],
146
+ props: { accountId: "acct-123" },
147
+ relationships,
148
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
149
+ }), /missing-action-provenance|Process provenance/i);
150
+ });
151
+ (0, node_test_1.default)("writeNodeWithEdges accepts non-action-provenance label writes without Task edge", async () => {
152
+ const { session } = makeStubSession({
153
+ preCheckLabelsByTarget: new Map([["doc-id", ["KnowledgeDocument"]]]),
154
+ });
155
+ const relationships = [
156
+ { type: "ABOUT", direction: "outgoing", targetNodeId: "doc-id" },
157
+ ];
158
+ const res = await (0, index_js_1.writeNodeWithEdges)({
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ session: session,
161
+ labels: ["Question"], // not in ACTION_PROVENANCE_LABELS
162
+ props: { accountId: "acct-123" },
163
+ relationships,
164
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
165
+ });
166
+ strict_1.default.equal(res.edgesCreated, 1);
167
+ });
168
+ //# sourceMappingURL=action-provenance-gate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-provenance-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/action-provenance-gate.test.ts"],"names":[],"mappings":"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"}
@@ -9,14 +9,24 @@ export * from "./audit.js";
9
9
  * maps, and flat props are queryable — `MATCH (n) WHERE n.createdBySession
10
10
  * = $id RETURN n` is the forensic entry point).
11
11
  *
12
+ * Process provenance doctrine (Task 885): writes targeting any label in
13
+ * `ACTION_PROVENANCE_LABELS` (Person, UserProfile, AdminUser, Organization,
14
+ * LocalBusiness, CloudflareTunnel, CloudflareHostname) must include exactly
15
+ * one inbound `:PRODUCED` edge whose source is a `:Task` node. This makes
16
+ * every durable LLM-tool-driven entity creation traversable from the Task
17
+ * node that produced it, and forward from Task→Conversation via
18
+ * `:RAISED_DURING`. Bootstrap writes (`createdBy.agent === 'system'`) are
19
+ * exempt — installer / migration / lazy-create paths run as system writers.
20
+ *
12
21
  * Rejection paths (every one emits a stderr log the admin server pipes to
13
22
  * server.log, so orphan pressure is visible per-write not just in the
14
23
  * hourly [graph-health] signal):
15
- * - zero relationships → `[graph-write] reject reason=zero-relationships`
16
- * - unresolved target id → `[graph-write] reject reason=unresolved-target`
17
- * - removed-feature write → `[graph-write] reject reason=removed-feature`
18
- * (Task 884 `:ReviewAlert` and `:Event {actionTool:"review-digest-compose"}`
19
- * are deleted features; the gate catches doctrine-compliant writers
24
+ * - zero relationships → `[graph-write] reject reason=zero-relationships`
25
+ * - unresolved target id → `[graph-write] reject reason=unresolved-target`
26
+ * - missing action provenance → `[graph-write] reject reason=missing-action-provenance`
27
+ * - removed-feature write → `[graph-write] reject reason=removed-feature`
28
+ * (`:ReviewAlert` and `:Event {actionTool:"review-digest-compose"}` are
29
+ * deleted features; the gate catches doctrine-compliant writers
20
30
  * re-introducing them. The vitest tombstone grep is the primary fence
21
31
  * for raw `session.run("MERGE …")` callers that bypass this primitive.)
22
32
  *
@@ -26,6 +36,19 @@ export * from "./audit.js";
26
36
  * will write "unknown"; that's the signal something is wrong. Do not use
27
37
  * this field for access control — it is forensic, not a security boundary.
28
38
  */
39
+ /**
40
+ * Labels that require an inbound `:PRODUCED` edge from a `:Task` (Task 885).
41
+ * Bootstrap writes with `createdBy.agent === 'system'` are exempt — that
42
+ * carve-out covers PIN-setup `writeAdminUserAndPerson`, schema migrations,
43
+ * and the lazy `loadUserProfile` MERGE path that bypasses this primitive
44
+ * entirely (raw Cypher in `platform/ui/app/lib/neo4j-store.ts`).
45
+ *
46
+ * The set is intentionally not exhaustive — labels added here become a
47
+ * blocking gate for every LLM-tool write of that label. Add a label only
48
+ * after every legitimate writer of it can either (a) carry a Task-PRODUCED
49
+ * edge, or (b) declare itself as `agent: 'system'`.
50
+ */
51
+ export declare const ACTION_PROVENANCE_LABELS: ReadonlySet<string>;
29
52
  import type { Session } from "neo4j-driver";
30
53
  export interface GraphRelationship {
31
54
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAkI1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAQvD,CAAC;AAiBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAoK1B"}
@@ -14,12 +14,81 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ACTION_PROVENANCE_LABELS = void 0;
17
18
  exports.stampCreatedBy = stampCreatedBy;
18
19
  exports.writeNodeWithEdges = writeNodeWithEdges;
19
20
  // Task 796: re-export the post-write audit so consumers (graph-mcp shim)
20
21
  // import from the same package as `writeNodeWithEdges`. Keeps the
21
22
  // `[graph-*]` log family colocated.
22
23
  __exportStar(require("./audit.js"), exports);
24
+ /**
25
+ * Write doctrine (Task 673): a node without at least one adjacency is noise,
26
+ * not knowledge. `writeNodeWithEdges` is the single primitive every graph
27
+ * writer should call; it rejects zero-relationship writes, verifies every
28
+ * relationship target resolves before the node is created, and commits the
29
+ * node + all edges in one managed transaction. Provenance is stamped on the
30
+ * node as flattened `createdBy*` properties (Neo4j does not persist nested
31
+ * maps, and flat props are queryable — `MATCH (n) WHERE n.createdBySession
32
+ * = $id RETURN n` is the forensic entry point).
33
+ *
34
+ * Process provenance doctrine (Task 885): writes targeting any label in
35
+ * `ACTION_PROVENANCE_LABELS` (Person, UserProfile, AdminUser, Organization,
36
+ * LocalBusiness, CloudflareTunnel, CloudflareHostname) must include exactly
37
+ * one inbound `:PRODUCED` edge whose source is a `:Task` node. This makes
38
+ * every durable LLM-tool-driven entity creation traversable from the Task
39
+ * node that produced it, and forward from Task→Conversation via
40
+ * `:RAISED_DURING`. Bootstrap writes (`createdBy.agent === 'system'`) are
41
+ * exempt — installer / migration / lazy-create paths run as system writers.
42
+ *
43
+ * Rejection paths (every one emits a stderr log the admin server pipes to
44
+ * server.log, so orphan pressure is visible per-write not just in the
45
+ * hourly [graph-health] signal):
46
+ * - zero relationships → `[graph-write] reject reason=zero-relationships`
47
+ * - unresolved target id → `[graph-write] reject reason=unresolved-target`
48
+ * - missing action provenance → `[graph-write] reject reason=missing-action-provenance`
49
+ * - removed-feature write → `[graph-write] reject reason=removed-feature`
50
+ * (`:ReviewAlert` and `:Event {actionTool:"review-digest-compose"}` are
51
+ * deleted features; the gate catches doctrine-compliant writers
52
+ * re-introducing them. The vitest tombstone grep is the primary fence
53
+ * for raw `session.run("MERGE …")` callers that bypass this primitive.)
54
+ *
55
+ * The `createdBy.agent` field is advisory, not authoritative — it is sourced
56
+ * from the MCP server's AGENT_SLUG env var, which is set by the trusted
57
+ * spawning code (admin server / workflow runner). A misconfigured spawner
58
+ * will write "unknown"; that's the signal something is wrong. Do not use
59
+ * this field for access control — it is forensic, not a security boundary.
60
+ */
61
+ /**
62
+ * Labels that require an inbound `:PRODUCED` edge from a `:Task` (Task 885).
63
+ * Bootstrap writes with `createdBy.agent === 'system'` are exempt — that
64
+ * carve-out covers PIN-setup `writeAdminUserAndPerson`, schema migrations,
65
+ * and the lazy `loadUserProfile` MERGE path that bypasses this primitive
66
+ * entirely (raw Cypher in `platform/ui/app/lib/neo4j-store.ts`).
67
+ *
68
+ * The set is intentionally not exhaustive — labels added here become a
69
+ * blocking gate for every LLM-tool write of that label. Add a label only
70
+ * after every legitimate writer of it can either (a) carry a Task-PRODUCED
71
+ * edge, or (b) declare itself as `agent: 'system'`.
72
+ */
73
+ exports.ACTION_PROVENANCE_LABELS = new Set([
74
+ "Person",
75
+ "UserProfile",
76
+ "AdminUser",
77
+ "Organization",
78
+ "LocalBusiness",
79
+ "CloudflareTunnel",
80
+ "CloudflareHostname",
81
+ ]);
82
+ function requiresActionProvenance(labels) {
83
+ for (const label of labels) {
84
+ if (exports.ACTION_PROVENANCE_LABELS.has(label))
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+ function findProducedFromTaskCandidates(relationships) {
90
+ return relationships.filter((r) => r.type === "PRODUCED" && r.direction === "incoming");
91
+ }
23
92
  /** Stamp flattened provenance into node properties. */
24
93
  function stampCreatedBy(props, createdBy) {
25
94
  return {
@@ -62,13 +131,38 @@ async function writeNodeWithEdges(params) {
62
131
  const nodeProps = stampCreatedBy(props, createdBy);
63
132
  return await session.executeWrite(async (tx) => {
64
133
  const targetIds = relationships.map((r) => r.targetNodeId);
65
- const check = await tx.run(`UNWIND $ids AS id MATCH (t) WHERE elementId(t) = id RETURN count(DISTINCT t) AS found`, { ids: targetIds });
66
- const found = check.records[0].get("found").toNumber();
134
+ const check = await tx.run(`UNWIND $ids AS id MATCH (t) WHERE elementId(t) = id RETURN elementId(t) AS id, labels(t) AS labels`, { ids: targetIds });
135
+ const labelsByTarget = new Map();
136
+ for (const rec of check.records) {
137
+ labelsByTarget.set(rec.get("id"), rec.get("labels"));
138
+ }
139
+ const found = labelsByTarget.size;
67
140
  const uniqueRequested = new Set(targetIds).size;
68
141
  if (found !== uniqueRequested) {
69
142
  process.stderr.write(`[graph-write] reject reason=unresolved-target labels=${labelCsv} agent=${agentLabel} requested=${uniqueRequested} found=${found}\n`);
70
143
  throw new Error(`Write doctrine violated: ${uniqueRequested - found} of ${uniqueRequested} relationship target(s) did not resolve (elementId mismatch). No node created.`);
71
144
  }
145
+ // Process provenance gate (Task 885). Labels in
146
+ // ACTION_PROVENANCE_LABELS require a `:Task` source on at least one
147
+ // inbound :PRODUCED edge so every durable LLM-tool entity write is
148
+ // traversable from the Task that produced it. Bootstrap writes
149
+ // (createdBy.agent === 'system') are exempt — installer + migration
150
+ // paths predate any Task. The check runs INSIDE the transaction so
151
+ // the labels-by-target map is consistent with target resolution.
152
+ let producedByTaskId = null;
153
+ if (requiresActionProvenance(labels) &&
154
+ (createdBy.agent ?? "") !== "system") {
155
+ const candidates = findProducedFromTaskCandidates(relationships);
156
+ const taskCandidates = candidates.filter((r) => {
157
+ const lbls = labelsByTarget.get(r.targetNodeId);
158
+ return Array.isArray(lbls) && lbls.includes("Task");
159
+ });
160
+ if (taskCandidates.length === 0) {
161
+ process.stderr.write(`[graph-write] reject reason=missing-action-provenance labels=${labelCsv} agent=${agentLabel}\n`);
162
+ throw new Error(`Process provenance doctrine violated: write to ${labelCsv} requires an inbound :PRODUCED edge from a :Task (createdBy.agent='${agentLabel}'). See .docs/neo4j.md (Process provenance doctrine).`);
163
+ }
164
+ producedByTaskId = taskCandidates[0].targetNodeId;
165
+ }
72
166
  let nodeRes;
73
167
  try {
74
168
  nodeRes = await tx.run(`CREATE (n:${labelStr} $props) RETURN elementId(n) AS nodeId, labels(n) AS nodeLabels`, { props: nodeProps });
@@ -122,7 +216,7 @@ async function writeNodeWithEdges(params) {
122
216
  process.stderr.write(`[graph-write] reject reason=edge-count-mismatch labels=${labelCsv} agent=${agentLabel} requested=${relationships.length} created=${edgesCreated}\n`);
123
217
  throw new Error(`Write doctrine violated: expected ${relationships.length} edges, created ${edgesCreated}. Transaction rolled back.`);
124
218
  }
125
- process.stderr.write(`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"}\n`);
219
+ process.stderr.write(`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"} producedByTask=${producedByTaskId ?? "none"}\n`);
126
220
  return { nodeId, labels: nodeLabels, edgesCreated };
127
221
  });
128
222
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ ACTION_PROVENANCE_LABELS,
5
+ writeNodeWithEdges,
6
+ type GraphRelationship,
7
+ } from "../index.js";
8
+
9
+ // Stub session/tx — captures Cypher calls and returns canned results.
10
+ interface StubTx {
11
+ run(cypher: string, params?: Record<string, unknown>): Promise<{
12
+ records: { get(field: string): unknown }[];
13
+ summary?: { counters: { updates(): { relationshipsCreated: number } } };
14
+ }>;
15
+ }
16
+
17
+ function makeStubSession(opts: {
18
+ preCheckLabelsByTarget: Map<string, string[]>;
19
+ edgesCreatedPerCall?: number;
20
+ }) {
21
+ const calls: { cypher: string; params: Record<string, unknown> }[] = [];
22
+ const tx: StubTx = {
23
+ async run(cypher: string, params: Record<string, unknown> = {}) {
24
+ calls.push({ cypher, params });
25
+ if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
26
+ const ids = (params.ids as string[]) ?? [];
27
+ const records = ids
28
+ .filter((id) => opts.preCheckLabelsByTarget.has(id))
29
+ .map((id) => ({
30
+ get(field: string) {
31
+ if (field === "id") return id;
32
+ if (field === "labels") return opts.preCheckLabelsByTarget.get(id) ?? [];
33
+ throw new Error(`unknown field ${field}`);
34
+ },
35
+ }));
36
+ return { records };
37
+ }
38
+ if (cypher.includes("CREATE (n:")) {
39
+ return {
40
+ records: [
41
+ {
42
+ get(field: string) {
43
+ if (field === "nodeId") return "new-element-id";
44
+ if (field === "nodeLabels") return ["Person"];
45
+ throw new Error(`unknown field ${field}`);
46
+ },
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
52
+ return {
53
+ records: [],
54
+ summary: {
55
+ counters: {
56
+ updates() {
57
+ return { relationshipsCreated: opts.edgesCreatedPerCall ?? 1 };
58
+ },
59
+ },
60
+ },
61
+ };
62
+ }
63
+ return { records: [] };
64
+ },
65
+ };
66
+ const session = {
67
+ async executeWrite<T>(work: (tx: StubTx) => Promise<T>): Promise<T> {
68
+ return await work(tx);
69
+ },
70
+ };
71
+ return { session, calls };
72
+ }
73
+
74
+ test("ACTION_PROVENANCE_LABELS includes the documented set", () => {
75
+ for (const label of [
76
+ "Person",
77
+ "UserProfile",
78
+ "AdminUser",
79
+ "Organization",
80
+ "LocalBusiness",
81
+ "CloudflareTunnel",
82
+ "CloudflareHostname",
83
+ ]) {
84
+ assert.ok(
85
+ ACTION_PROVENANCE_LABELS.has(label),
86
+ `${label} should be in ACTION_PROVENANCE_LABELS`,
87
+ );
88
+ }
89
+ });
90
+
91
+ test("writeNodeWithEdges rejects :Person write missing PRODUCED-from-Task edge", async () => {
92
+ const { session } = makeStubSession({
93
+ preCheckLabelsByTarget: new Map([["other-target", ["Conversation"]]]),
94
+ });
95
+ const relationships: GraphRelationship[] = [
96
+ { type: "PART_OF", direction: "outgoing", targetNodeId: "other-target" },
97
+ ];
98
+ await assert.rejects(
99
+ () =>
100
+ writeNodeWithEdges({
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ session: session as any,
103
+ labels: ["Person"],
104
+ props: { accountId: "acct-123" },
105
+ relationships,
106
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
107
+ }),
108
+ /missing-action-provenance|Process provenance/i,
109
+ );
110
+ });
111
+
112
+ test("writeNodeWithEdges accepts :Person write with PRODUCED-from-Task edge", async () => {
113
+ const { session } = makeStubSession({
114
+ preCheckLabelsByTarget: new Map([
115
+ ["task-id", ["Task"]],
116
+ ]),
117
+ });
118
+ const relationships: GraphRelationship[] = [
119
+ { type: "PRODUCED", direction: "incoming", targetNodeId: "task-id" },
120
+ ];
121
+ const res = await writeNodeWithEdges({
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ session: session as any,
124
+ labels: ["Person"],
125
+ props: { accountId: "acct-123" },
126
+ relationships,
127
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
128
+ });
129
+ assert.equal(res.edgesCreated, 1);
130
+ });
131
+
132
+ test("writeNodeWithEdges accepts :Person write with no Task edge when agent='system' (bootstrap exemption)", async () => {
133
+ const { session } = makeStubSession({
134
+ preCheckLabelsByTarget: new Map([["admin-user-id", ["AdminUser"]]]),
135
+ });
136
+ const relationships: GraphRelationship[] = [
137
+ { type: "OWNS", direction: "incoming", targetNodeId: "admin-user-id" },
138
+ ];
139
+ const res = await writeNodeWithEdges({
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ session: session as any,
142
+ labels: ["Person"],
143
+ props: { accountId: "acct-123" },
144
+ relationships,
145
+ createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
146
+ });
147
+ assert.equal(res.edgesCreated, 1);
148
+ });
149
+
150
+ test("writeNodeWithEdges rejects :Person write whose 'PRODUCED-incoming' source is NOT a :Task", async () => {
151
+ const { session } = makeStubSession({
152
+ preCheckLabelsByTarget: new Map([
153
+ ["other-id", ["Conversation"]],
154
+ ]),
155
+ });
156
+ const relationships: GraphRelationship[] = [
157
+ // Inbound PRODUCED edge but the source is a Conversation, not a Task.
158
+ // Must reject — prevents bypass via spoofed PRODUCED edge from any node.
159
+ { type: "PRODUCED", direction: "incoming", targetNodeId: "other-id" },
160
+ ];
161
+ await assert.rejects(
162
+ () =>
163
+ writeNodeWithEdges({
164
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
+ session: session as any,
166
+ labels: ["Person"],
167
+ props: { accountId: "acct-123" },
168
+ relationships,
169
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
170
+ }),
171
+ /missing-action-provenance|Process provenance/i,
172
+ );
173
+ });
174
+
175
+ test("writeNodeWithEdges accepts non-action-provenance label writes without Task edge", async () => {
176
+ const { session } = makeStubSession({
177
+ preCheckLabelsByTarget: new Map([["doc-id", ["KnowledgeDocument"]]]),
178
+ });
179
+ const relationships: GraphRelationship[] = [
180
+ { type: "ABOUT", direction: "outgoing", targetNodeId: "doc-id" },
181
+ ];
182
+ const res = await writeNodeWithEdges({
183
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
+ session: session as any,
185
+ labels: ["Question"], // not in ACTION_PROVENANCE_LABELS
186
+ props: { accountId: "acct-123" },
187
+ relationships,
188
+ createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
189
+ });
190
+ assert.equal(res.edgesCreated, 1);
191
+ });