@rubytech/create-maxy 1.0.881 → 1.0.884
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.js +45 -0
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/index.ts +47 -0
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +57 -9
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +26 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js +81 -0
- package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts +38 -16
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +75 -35
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +59 -9
- package/payload/platform/lib/graph-write/src/conversation-provenance.ts +140 -0
- package/payload/platform/lib/graph-write/src/index.ts +76 -35
- package/payload/platform/lib/mcp-eager/dist/index.d.ts +61 -0
- package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/mcp-eager/dist/index.js +49 -0
- package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -0
- package/payload/platform/lib/mcp-eager/src/index.ts +78 -0
- package/payload/platform/lib/mcp-eager/tsconfig.json +8 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js +26 -2
- package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +36 -33
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +5 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +24 -8
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
- package/payload/platform/plugins/contacts/PLUGIN.md +8 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js +10 -9
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +17 -2
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +15 -2
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +10 -9
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +5 -3
- package/payload/platform/plugins/memory/mcp/dist/index.js +10 -9
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +18 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +9 -8
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/tasks/mcp/dist/index.js +15 -14
- package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/mcp/dist/index.js +4 -3
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/index.js +9 -8
- package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/__tests__/logs-read-prefix.sh +341 -0
- package/payload/platform/scripts/logs-read.sh +108 -41
- package/payload/platform/scripts/logs-read.test.sh +6 -2
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/premium-plugins/real-agency/BUNDLE.md +1 -1
- package/payload/server/chunk-5PQU2HW2.js +11672 -0
- package/payload/server/chunk-ECAQVMRA.js +759 -0
- package/payload/server/chunk-K7S5T4VG.js +11534 -0
- package/payload/server/cloudflare-task-tracker-JNZXLW32.js +22 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/server.js +38 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAK3B,cAAc,8BAA8B,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAQvD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAIvD,CAAC;AAiBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,SAAS,CAAC;IACrB;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,eAAe,CAAC,CA+M1B"}
|
|
@@ -14,13 +14,17 @@ 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
|
+
exports.PROVENANCE_SOURCE_LABELS = exports.ACTION_PROVENANCE_LABELS = void 0;
|
|
18
18
|
exports.stampCreatedBy = stampCreatedBy;
|
|
19
19
|
exports.writeNodeWithEdges = writeNodeWithEdges;
|
|
20
20
|
// Task 796: re-export the post-write audit so consumers (graph-mcp shim)
|
|
21
21
|
// import from the same package as `writeNodeWithEdges`. Keeps the
|
|
22
22
|
// `[graph-*]` log family colocated.
|
|
23
23
|
__exportStar(require("./audit.js"), exports);
|
|
24
|
+
// Task 997: re-export the conversation-provenance injector. MCP wrappers
|
|
25
|
+
// (contacts, memory) import it from the package root so the helper, the
|
|
26
|
+
// gate, and the label set live in one import surface.
|
|
27
|
+
__exportStar(require("./conversation-provenance.js"), exports);
|
|
24
28
|
/**
|
|
25
29
|
* Write doctrine (Task 673): a node without at least one adjacency is noise,
|
|
26
30
|
* not knowledge. `writeNodeWithEdges` is the single primitive every graph
|
|
@@ -31,21 +35,33 @@ __exportStar(require("./audit.js"), exports);
|
|
|
31
35
|
* maps, and flat props are queryable — `MATCH (n) WHERE n.createdBySession
|
|
32
36
|
* = $id RETURN n` is the forensic entry point).
|
|
33
37
|
*
|
|
34
|
-
* Process provenance doctrine (Task 885
|
|
35
|
-
* `ACTION_PROVENANCE_LABELS` (Person, UserProfile,
|
|
36
|
-
* LocalBusiness, CloudflareTunnel,
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* Process provenance doctrine (Task 885, widened by Task 997): writes
|
|
39
|
+
* targeting any label in `ACTION_PROVENANCE_LABELS` (Person, UserProfile,
|
|
40
|
+
* AdminUser, Organization, LocalBusiness, CloudflareTunnel,
|
|
41
|
+
* CloudflareHostname) must include at least one inbound `:PRODUCED` edge
|
|
42
|
+
* whose source carries one of `:Task`, `:Conversation`, or `:Message` (the
|
|
43
|
+
* label set is checked against the actual `labels()` of the target node, so
|
|
44
|
+
* subtype labels like `:AdminConversation`, `:UserMessage`, `:AssistantMessage`,
|
|
45
|
+
* and `:AdminMessage` all qualify). This makes every durable LLM-tool-driven
|
|
46
|
+
* entity creation traversable from either:
|
|
47
|
+
* - the Task that produced it (autonomous workflows: onboarding skill,
|
|
48
|
+
* cloudflare tunnel-login, etc.), or
|
|
49
|
+
* - the Conversation/Message turn that triggered it (direct admin asks —
|
|
50
|
+
* "add Anneke Roux as person" stamps a `:PRODUCED` edge from the active
|
|
51
|
+
* `:AdminConversation` so the entity is traceable to its causal turn
|
|
52
|
+
* without forcing the agent to mint a synthetic `:Task` per ask).
|
|
53
|
+
* Task 997 broadened the gate after the Task-only requirement created
|
|
54
|
+
* redundant-Task bloat for direct admin asks; the Task-mediated path stays
|
|
55
|
+
* canonical for autonomous workflows. Bootstrap writes
|
|
56
|
+
* (`createdBy.agent === 'system'`) are exempt — installer / migration /
|
|
57
|
+
* lazy-create paths run as system writers.
|
|
42
58
|
*
|
|
43
59
|
* Rejection paths (every one emits a stderr log the admin server pipes to
|
|
44
60
|
* server.log, so orphan pressure is visible per-write not just in the
|
|
45
61
|
* hourly [graph-health] signal):
|
|
46
62
|
* - zero relationships → `[graph-write] reject reason=zero-relationships`
|
|
47
63
|
* - unresolved target id → `[graph-write] reject reason=unresolved-target`
|
|
48
|
-
* - missing
|
|
64
|
+
* - missing provenance → `[graph-write] reject reason=missing-provenance`
|
|
49
65
|
* - removed-feature write → `[graph-write] reject reason=removed-feature`
|
|
50
66
|
* (`:ReviewAlert` and `:Event {actionTool:"review-digest-compose"}` are
|
|
51
67
|
* deleted features; the gate catches doctrine-compliant writers
|
|
@@ -59,16 +75,18 @@ __exportStar(require("./audit.js"), exports);
|
|
|
59
75
|
* this field for access control — it is forensic, not a security boundary.
|
|
60
76
|
*/
|
|
61
77
|
/**
|
|
62
|
-
* Labels that require an inbound `:PRODUCED` edge from a `:Task
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
78
|
+
* Labels that require an inbound `:PRODUCED` edge from a `:Task`,
|
|
79
|
+
* `:Conversation`, or `:Message` (Task 885, widened by Task 997). Bootstrap
|
|
80
|
+
* writes with `createdBy.agent === 'system'` are exempt — that carve-out
|
|
81
|
+
* covers PIN-setup `writeAdminUserAndPerson`, schema migrations, and the
|
|
82
|
+
* lazy `loadUserProfile` MERGE path that bypasses this primitive entirely
|
|
83
|
+
* (raw Cypher in `platform/ui/app/lib/neo4j-store.ts`).
|
|
67
84
|
*
|
|
68
85
|
* The set is intentionally not exhaustive — labels added here become a
|
|
69
86
|
* 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
|
|
71
|
-
*
|
|
87
|
+
* after every legitimate writer of it can either (a) carry a PRODUCED edge
|
|
88
|
+
* from one of the accepted source families, or (b) declare itself as
|
|
89
|
+
* `agent: 'system'`.
|
|
72
90
|
*/
|
|
73
91
|
exports.ACTION_PROVENANCE_LABELS = new Set([
|
|
74
92
|
"Person",
|
|
@@ -79,6 +97,17 @@ exports.ACTION_PROVENANCE_LABELS = new Set([
|
|
|
79
97
|
"CloudflareTunnel",
|
|
80
98
|
"CloudflareHostname",
|
|
81
99
|
]);
|
|
100
|
+
/**
|
|
101
|
+
* Source labels that satisfy the inbound-PRODUCED gate. The check is
|
|
102
|
+
* performed against the full `labels()` array of the target node, so
|
|
103
|
+
* subtype labels (`:AdminConversation`, `:UserMessage`, etc.) qualify
|
|
104
|
+
* because they carry the parent `:Conversation` / `:Message` label too.
|
|
105
|
+
*/
|
|
106
|
+
exports.PROVENANCE_SOURCE_LABELS = new Set([
|
|
107
|
+
"Task",
|
|
108
|
+
"Conversation",
|
|
109
|
+
"Message",
|
|
110
|
+
]);
|
|
82
111
|
function requiresActionProvenance(labels) {
|
|
83
112
|
for (const label of labels) {
|
|
84
113
|
if (exports.ACTION_PROVENANCE_LABELS.has(label))
|
|
@@ -86,7 +115,7 @@ function requiresActionProvenance(labels) {
|
|
|
86
115
|
}
|
|
87
116
|
return false;
|
|
88
117
|
}
|
|
89
|
-
function
|
|
118
|
+
function findProvenanceCandidates(relationships) {
|
|
90
119
|
return relationships.filter((r) => r.type === "PRODUCED" && r.direction === "incoming");
|
|
91
120
|
}
|
|
92
121
|
/** Stamp flattened provenance into node properties. */
|
|
@@ -167,26 +196,37 @@ async function writeNodeWithEdges(params) {
|
|
|
167
196
|
process.stderr.write(`[graph-write] reject reason=unresolved-target labels=${labelCsv} agent=${agentLabel} requested=${uniqueRequested} found=${found}\n`);
|
|
168
197
|
throw new Error(`Write doctrine violated: ${uniqueRequested - found} of ${uniqueRequested} relationship target(s) did not resolve (elementId mismatch). No node created.`);
|
|
169
198
|
}
|
|
170
|
-
// Process provenance gate (Task 885). Labels in
|
|
171
|
-
// ACTION_PROVENANCE_LABELS require
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
199
|
+
// Process provenance gate (Task 885, widened by Task 997). Labels in
|
|
200
|
+
// ACTION_PROVENANCE_LABELS require an inbound :PRODUCED edge from a
|
|
201
|
+
// node carrying one of PROVENANCE_SOURCE_LABELS (:Task, :Conversation,
|
|
202
|
+
// :Message — subtype labels qualify via the labels() array). This
|
|
203
|
+
// makes every durable LLM-tool entity write traversable to either the
|
|
204
|
+
// Task that produced it (autonomous workflows) or the
|
|
205
|
+
// Conversation/Message turn that triggered it (direct admin asks).
|
|
206
|
+
// Bootstrap writes (createdBy.agent === 'system') are exempt —
|
|
207
|
+
// installer + migration paths predate any provenance node. The check
|
|
208
|
+
// runs INSIDE the transaction so the labels-by-target map is
|
|
209
|
+
// consistent with target resolution.
|
|
210
|
+
let provenanceSourceId = null;
|
|
211
|
+
let provenanceSourceLabel = null;
|
|
178
212
|
if (requiresActionProvenance(labels) &&
|
|
179
213
|
(createdBy.agent ?? "") !== "system") {
|
|
180
|
-
const candidates =
|
|
181
|
-
const
|
|
214
|
+
const candidates = findProvenanceCandidates(relationships);
|
|
215
|
+
const matched = candidates
|
|
216
|
+
.map((r) => {
|
|
182
217
|
const lbls = labelsByTarget.get(r.targetNodeId);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
218
|
+
if (!Array.isArray(lbls))
|
|
219
|
+
return null;
|
|
220
|
+
const sourceLabel = lbls.find((l) => exports.PROVENANCE_SOURCE_LABELS.has(l));
|
|
221
|
+
return sourceLabel ? { rel: r, sourceLabel } : null;
|
|
222
|
+
})
|
|
223
|
+
.filter((m) => m !== null);
|
|
224
|
+
if (matched.length === 0) {
|
|
225
|
+
process.stderr.write(`[graph-write] reject reason=missing-provenance labels=${labelCsv} agent=${agentLabel}\n`);
|
|
226
|
+
throw new Error(`missing-provenance: write to ${labelCsv} requires an inbound :PRODUCED edge from a :Task, :Conversation, or :Message (createdBy.agent='${agentLabel}'). Either pass producedByTaskId on the write (autonomous workflows: call task-create at the start of the flow and thread the returned taskId), or rely on the MCP wrapper's CONVERSATION_NODE_ID env-stamp injection (direct admin asks).`);
|
|
188
227
|
}
|
|
189
|
-
|
|
228
|
+
provenanceSourceId = matched[0].rel.targetNodeId;
|
|
229
|
+
provenanceSourceLabel = matched[0].sourceLabel;
|
|
190
230
|
}
|
|
191
231
|
let nodeRes;
|
|
192
232
|
try {
|
|
@@ -241,7 +281,7 @@ async function writeNodeWithEdges(params) {
|
|
|
241
281
|
process.stderr.write(`[graph-write] reject reason=edge-count-mismatch labels=${labelCsv} agent=${agentLabel} requested=${relationships.length} created=${edgesCreated}\n`);
|
|
242
282
|
throw new Error(`Write doctrine violated: expected ${relationships.length} edges, created ${edgesCreated}. Transaction rolled back.`);
|
|
243
283
|
}
|
|
244
|
-
process.stderr.write(`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"}
|
|
284
|
+
process.stderr.write(`[graph-write] accepted labels=${labelCsv} edges=${edgesCreated} createdByAgent=${createdBy.agent ?? "unknown"} createdByTool=${createdBy.tool ?? createdBy.source ?? "unknown"} producedBy=${provenanceSourceLabel ?? "none"}:${provenanceSourceId ?? "none"}\n`);
|
|
245
285
|
return { nodeId, labels: nodeLabels, edgesCreated };
|
|
246
286
|
});
|
|
247
287
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -94,11 +94,12 @@ test("ACTION_PROVENANCE_LABELS includes the documented set", () => {
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
test("writeNodeWithEdges rejects :Person write missing PRODUCED
|
|
97
|
+
test("writeNodeWithEdges rejects :Person write missing any inbound PRODUCED edge (missing-provenance)", async () => {
|
|
98
98
|
const { session } = makeStubSession({
|
|
99
|
-
preCheckLabelsByTarget: new Map([["other-target", ["
|
|
99
|
+
preCheckLabelsByTarget: new Map([["other-target", ["LocalBusiness"]]]),
|
|
100
100
|
});
|
|
101
101
|
const relationships: GraphRelationship[] = [
|
|
102
|
+
// No inbound PRODUCED at all — neither Task nor Conversation nor Message.
|
|
102
103
|
{ type: "PART_OF", direction: "outgoing", targetNodeId: "other-target" },
|
|
103
104
|
];
|
|
104
105
|
await assert.rejects(
|
|
@@ -112,7 +113,7 @@ test("writeNodeWithEdges rejects :Person write missing PRODUCED-from-Task edge",
|
|
|
112
113
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
113
114
|
expectedAccountId: TEST_ACCOUNT_ID,
|
|
114
115
|
}),
|
|
115
|
-
/missing-
|
|
116
|
+
/missing-provenance/i,
|
|
116
117
|
);
|
|
117
118
|
});
|
|
118
119
|
|
|
@@ -156,16 +157,65 @@ test("writeNodeWithEdges accepts :Person write with no Task edge when agent='sys
|
|
|
156
157
|
assert.equal(res.edgesCreated, 1);
|
|
157
158
|
});
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
// Task 997 — gate widened to accept :Conversation provenance for direct
|
|
161
|
+
// admin asks. The wrapper auto-injects the inbound PRODUCED edge from the
|
|
162
|
+
// active :AdminConversation via CONVERSATION_NODE_ID.
|
|
163
|
+
test("writeNodeWithEdges accepts :Person write with PRODUCED-from-Conversation edge", async () => {
|
|
160
164
|
const { session } = makeStubSession({
|
|
161
165
|
preCheckLabelsByTarget: new Map([
|
|
162
|
-
|
|
166
|
+
// AdminConversation carries the parent :Conversation label too.
|
|
167
|
+
["conv-id", ["Conversation", "AdminConversation"]],
|
|
163
168
|
]),
|
|
164
169
|
});
|
|
165
170
|
const relationships: GraphRelationship[] = [
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
{ type: "PRODUCED", direction: "incoming", targetNodeId: "conv-id" },
|
|
172
|
+
];
|
|
173
|
+
const res = await writeNodeWithEdges({
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
|
+
session: session as any,
|
|
176
|
+
labels: ["Person"],
|
|
177
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
178
|
+
relationships,
|
|
179
|
+
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "contact-create" },
|
|
180
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
181
|
+
});
|
|
182
|
+
assert.equal(res.edgesCreated, 1);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Task 997 — Message subtypes (UserMessage, AssistantMessage, AdminMessage)
|
|
186
|
+
// also qualify because the labels() array includes the parent :Message label.
|
|
187
|
+
test("writeNodeWithEdges accepts :Person write with PRODUCED-from-Message edge", async () => {
|
|
188
|
+
const { session } = makeStubSession({
|
|
189
|
+
preCheckLabelsByTarget: new Map([
|
|
190
|
+
["msg-id", ["Message", "UserMessage"]],
|
|
191
|
+
]),
|
|
192
|
+
});
|
|
193
|
+
const relationships: GraphRelationship[] = [
|
|
194
|
+
{ type: "PRODUCED", direction: "incoming", targetNodeId: "msg-id" },
|
|
195
|
+
];
|
|
196
|
+
const res = await writeNodeWithEdges({
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
session: session as any,
|
|
199
|
+
labels: ["Person"],
|
|
200
|
+
props: { accountId: TEST_ACCOUNT_ID },
|
|
201
|
+
relationships,
|
|
202
|
+
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "contact-create" },
|
|
203
|
+
expectedAccountId: TEST_ACCOUNT_ID,
|
|
204
|
+
});
|
|
205
|
+
assert.equal(res.edgesCreated, 1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Task 997 — defence-in-depth: the gate's source-label allowlist still
|
|
209
|
+
// rejects PRODUCED edges from nodes outside {Task, Conversation, Message}.
|
|
210
|
+
// Prevents bypass via a spoofed PRODUCED edge from any unrelated node.
|
|
211
|
+
test("writeNodeWithEdges rejects :Person write with PRODUCED edge from a non-provenance-source label", async () => {
|
|
212
|
+
const { session } = makeStubSession({
|
|
213
|
+
preCheckLabelsByTarget: new Map([
|
|
214
|
+
["business-id", ["LocalBusiness"]],
|
|
215
|
+
]),
|
|
216
|
+
});
|
|
217
|
+
const relationships: GraphRelationship[] = [
|
|
218
|
+
{ type: "PRODUCED", direction: "incoming", targetNodeId: "business-id" },
|
|
169
219
|
];
|
|
170
220
|
await assert.rejects(
|
|
171
221
|
() =>
|
|
@@ -178,7 +228,7 @@ test("writeNodeWithEdges rejects :Person write whose 'PRODUCED-incoming' source
|
|
|
178
228
|
createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
|
|
179
229
|
expectedAccountId: TEST_ACCOUNT_ID,
|
|
180
230
|
}),
|
|
181
|
-
/missing-
|
|
231
|
+
/missing-provenance/i,
|
|
182
232
|
);
|
|
183
233
|
});
|
|
184
234
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Task 997 — direct-ask provenance auto-injection.
|
|
2
|
+
//
|
|
3
|
+
// MCP tool wrappers that write to ACTION_PROVENANCE_LABELS call this helper
|
|
4
|
+
// before `writeNodeWithEdges`. When the admin server has stamped
|
|
5
|
+
// `CONVERSATION_NODE_ID` in the MCP spawn env (the elementId of the active
|
|
6
|
+
// `:AdminConversation`), the helper:
|
|
7
|
+
// - verifies the source node exists, carries one of PROVENANCE_SOURCE_LABELS,
|
|
8
|
+
// and shares the write's accountId
|
|
9
|
+
// - prepends a synthetic inbound `:PRODUCED` edge to the relationships array
|
|
10
|
+
// so the gate inside `writeNodeWithEdges` accepts the write
|
|
11
|
+
//
|
|
12
|
+
// The injection is a no-op when:
|
|
13
|
+
// - the env var is unset (typical for autonomous workflow flows that pass an
|
|
14
|
+
// explicit `producedByTaskId` instead)
|
|
15
|
+
// - the labels being written do not require action provenance
|
|
16
|
+
// - the relationships array already carries an inbound `:PRODUCED` edge from
|
|
17
|
+
// any source (idempotency — explicit `producedByTaskId` already prepended
|
|
18
|
+
// one; a second source is redundant noise. If the caller's PRODUCED edge
|
|
19
|
+
// resolves to a node NOT in PROVENANCE_SOURCE_LABELS, the downstream gate
|
|
20
|
+
// rejects with `missing-provenance` — defence-in-depth covers that case)
|
|
21
|
+
//
|
|
22
|
+
// Loud-failure cases emit `[<logNamespace>] [provenance-missing] reason=<r>`
|
|
23
|
+
// to stderr and return the original relationships array unchanged — the
|
|
24
|
+
// downstream gate then fires `missing-provenance` so both observability
|
|
25
|
+
// signals land in server.log.
|
|
26
|
+
|
|
27
|
+
import type { Session } from "neo4j-driver";
|
|
28
|
+
import {
|
|
29
|
+
ACTION_PROVENANCE_LABELS,
|
|
30
|
+
PROVENANCE_SOURCE_LABELS,
|
|
31
|
+
} from "./index.js";
|
|
32
|
+
import type { GraphRelationship } from "./index.js";
|
|
33
|
+
|
|
34
|
+
export interface InjectConversationProvenanceParams {
|
|
35
|
+
session: Session;
|
|
36
|
+
/** The relationships array the caller is about to pass to `writeNodeWithEdges`. */
|
|
37
|
+
relationships: readonly GraphRelationship[];
|
|
38
|
+
/** The accountId of the write — must match the source node's accountId. */
|
|
39
|
+
accountId: string;
|
|
40
|
+
/** Labels of the node about to be written. Used to skip injection when provenance is not required. */
|
|
41
|
+
writeLabels: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Value of `process.env.CONVERSATION_NODE_ID` at call time. Passed as a
|
|
44
|
+
* parameter so tests can supply it without mutating process.env.
|
|
45
|
+
*/
|
|
46
|
+
conversationNodeId: string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Log namespace prefix — typically `"mcp:contacts"` or `"mcp:memory"`. Used
|
|
49
|
+
* to format the `[provenance-inject]` / `[provenance-missing]` lines so the
|
|
50
|
+
* operator can attribute the signal to a plugin.
|
|
51
|
+
*/
|
|
52
|
+
logNamespace: string;
|
|
53
|
+
/** Tool name (e.g. "contact-create") for the inject/missing log lines. */
|
|
54
|
+
tool: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function injectConversationProvenance(
|
|
58
|
+
params: InjectConversationProvenanceParams,
|
|
59
|
+
): Promise<GraphRelationship[]> {
|
|
60
|
+
const {
|
|
61
|
+
session,
|
|
62
|
+
relationships,
|
|
63
|
+
accountId,
|
|
64
|
+
writeLabels,
|
|
65
|
+
conversationNodeId,
|
|
66
|
+
logNamespace,
|
|
67
|
+
tool,
|
|
68
|
+
} = params;
|
|
69
|
+
|
|
70
|
+
const original = [...relationships];
|
|
71
|
+
|
|
72
|
+
if (!writeLabels.some((l) => ACTION_PROVENANCE_LABELS.has(l))) return original;
|
|
73
|
+
if (!conversationNodeId) return original;
|
|
74
|
+
if (hasInboundProducedEdge(original)) return original;
|
|
75
|
+
|
|
76
|
+
let lookup;
|
|
77
|
+
try {
|
|
78
|
+
lookup = await session.run(
|
|
79
|
+
`MATCH (n) WHERE elementId(n) = $id RETURN labels(n) AS labels, n.accountId AS accountId LIMIT 1`,
|
|
80
|
+
{ id: conversationNodeId },
|
|
81
|
+
);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`[${logNamespace}] [provenance-missing] tool=${tool} reason=driver-error message=${err instanceof Error ? err.message : String(err)} conversationNodeId=${conversationNodeId}\n`,
|
|
85
|
+
);
|
|
86
|
+
return original;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (lookup.records.length === 0) {
|
|
90
|
+
process.stderr.write(
|
|
91
|
+
`[${logNamespace}] [provenance-missing] tool=${tool} reason=node-not-found conversationNodeId=${conversationNodeId}\n`,
|
|
92
|
+
);
|
|
93
|
+
return original;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const labels = lookup.records[0].get("labels") as string[];
|
|
97
|
+
const sourceAccountId = lookup.records[0].get("accountId") as string;
|
|
98
|
+
const sourceLabel = labels.find((l) => PROVENANCE_SOURCE_LABELS.has(l));
|
|
99
|
+
|
|
100
|
+
if (!sourceLabel) {
|
|
101
|
+
process.stderr.write(
|
|
102
|
+
`[${logNamespace}] [provenance-missing] tool=${tool} reason=wrong-source-label labels=${labels.join(",")} conversationNodeId=${conversationNodeId}\n`,
|
|
103
|
+
);
|
|
104
|
+
return original;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (sourceAccountId !== accountId) {
|
|
108
|
+
process.stderr.write(
|
|
109
|
+
`[${logNamespace}] [provenance-missing] tool=${tool} reason=account-mismatch sourceAccountId=${sourceAccountId.slice(0, 8)} writeAccountId=${accountId.slice(0, 8)} conversationNodeId=${conversationNodeId}\n`,
|
|
110
|
+
);
|
|
111
|
+
return original;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
process.stderr.write(
|
|
115
|
+
`[${logNamespace}] [provenance-inject] tool=${tool} from=${sourceLabel}:${conversationNodeId}\n`,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
type: "PRODUCED",
|
|
121
|
+
direction: "incoming",
|
|
122
|
+
targetNodeId: conversationNodeId,
|
|
123
|
+
},
|
|
124
|
+
...original,
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect any pre-existing inbound `:PRODUCED` edge in the relationships
|
|
130
|
+
* array. Source-label validation is the downstream gate's job; here we only
|
|
131
|
+
* need to know whether the caller already provided one — if so, skip
|
|
132
|
+
* injection to avoid duplicate edges.
|
|
133
|
+
*/
|
|
134
|
+
function hasInboundProducedEdge(
|
|
135
|
+
relationships: readonly GraphRelationship[],
|
|
136
|
+
): boolean {
|
|
137
|
+
return relationships.some(
|
|
138
|
+
(r) => r.type === "PRODUCED" && r.direction === "incoming",
|
|
139
|
+
);
|
|
140
|
+
}
|