@rubytech/create-maxy 1.0.742 → 1.0.744
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/__tests__/cypher-validate-write.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js +97 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +13 -1
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +70 -3
- package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.js +154 -11
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate-write.test.ts +150 -0
- package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +95 -3
- package/payload/platform/lib/graph-mcp/src/index.ts +202 -17
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts +2 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js +147 -0
- package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/audit.d.ts +84 -0
- package/payload/platform/lib/graph-write/dist/audit.d.ts.map +1 -0
- package/payload/platform/lib/graph-write/dist/audit.js +129 -0
- package/payload/platform/lib/graph-write/dist/audit.js.map +1 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts +1 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +18 -22
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/__tests__/audit.test.ts +162 -0
- package/payload/platform/lib/graph-write/src/audit.ts +182 -0
- package/payload/platform/lib/graph-write/src/index.ts +5 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/docs/references/memory-guide.md +2 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +16 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +11 -4
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +119 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +22 -3
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +33 -7
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/package.json +4 -2
- package/payload/platform/plugins/scheduling/mcp/vitest.config.ts +9 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +39 -6
- package/payload/server/chunk-2T4RRIJK.js +9462 -0
- package/payload/server/chunk-JLVVVQN7.js +9447 -0
- package/payload/server/chunk-TXPEEAV6.js +2997 -0
- package/payload/server/client-pool-TCKGDZLE.js +28 -0
- package/payload/server/maxy-edge.js +95 -17
- package/payload/server/server.js +3 -3
|
@@ -0,0 +1,147 @@
|
|
|
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 audit_js_1 = require("../audit.js");
|
|
9
|
+
const baseInput = {
|
|
10
|
+
schema: {
|
|
11
|
+
labels: new Set(["Person", "Organization", "Task", "KnowledgeDocument"]),
|
|
12
|
+
relationshipTypes: new Set(["KNOWS", "PARTICIPANT", "REFERENCES", "MENTIONS", "PART_OF"]),
|
|
13
|
+
},
|
|
14
|
+
agentName: "database-operator",
|
|
15
|
+
sessionId: "session-abc",
|
|
16
|
+
nodesCreated: 0,
|
|
17
|
+
relsCreated: 0,
|
|
18
|
+
orphanIds: [],
|
|
19
|
+
};
|
|
20
|
+
(0, node_test_1.default)("audit: clean write with provenance + known types yields zero warnings", () => {
|
|
21
|
+
const cypher = `
|
|
22
|
+
MATCH (p:Person {name: $name}), (o:Organization {name: $org})
|
|
23
|
+
MERGE (p)-[r:KNOWS]->(o)
|
|
24
|
+
SET r.createdAt = datetime(),
|
|
25
|
+
r.createdByAgent = $agent,
|
|
26
|
+
r.createdByTool = 'graph-cypher-write',
|
|
27
|
+
r.createdBySession = $sessionId
|
|
28
|
+
`;
|
|
29
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, relsCreated: 1 });
|
|
30
|
+
strict_1.default.deepEqual(warnings, []);
|
|
31
|
+
});
|
|
32
|
+
(0, node_test_1.default)("audit: unknown edge type yields unknown-type-warning", () => {
|
|
33
|
+
const cypher = "MATCH (a:Person), (b:Person) CREATE (a)-[:HAS_KNOWN]->(b)";
|
|
34
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, relsCreated: 1 });
|
|
35
|
+
const w = warnings.find((x) => x.kind === "unknown-type-warning");
|
|
36
|
+
strict_1.default.ok(w, "expected unknown-type-warning");
|
|
37
|
+
strict_1.default.equal(w.kind, "unknown-type-warning");
|
|
38
|
+
if (w.kind === "unknown-type-warning") {
|
|
39
|
+
strict_1.default.equal(w.type, "HAS_KNOWN");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, node_test_1.default)("audit: known APOC edge in YIELD does not trip unknown-type", () => {
|
|
43
|
+
const cypher = "MATCH (a:Person), (b:Person) WHERE a.email = b.email AND id(a) < id(b) WITH a, b CALL apoc.refactor.mergeNodes([a, b]) YIELD node RETURN node";
|
|
44
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher });
|
|
45
|
+
const unknownTypeWarn = warnings.find((x) => x.kind === "unknown-type-warning");
|
|
46
|
+
strict_1.default.equal(unknownTypeWarn, undefined);
|
|
47
|
+
});
|
|
48
|
+
(0, node_test_1.default)("audit: CREATE with no provenance stamps yields missing-provenance-warning", () => {
|
|
49
|
+
const cypher = "CREATE (n:Person {name: 'Foo'})";
|
|
50
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, nodesCreated: 1 });
|
|
51
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
52
|
+
strict_1.default.ok(w, "expected missing-provenance-warning");
|
|
53
|
+
if (w.kind === "missing-provenance-warning") {
|
|
54
|
+
strict_1.default.equal(w.created, 1);
|
|
55
|
+
strict_1.default.equal(w.stamped, 0);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
(0, node_test_1.default)("audit: MERGE with createdBy* stamps does not trip missing-provenance", () => {
|
|
59
|
+
const cypher = "MERGE (n:Person {email: $email}) ON CREATE SET n.createdByAgent = $agent, n.createdBySession = $sid";
|
|
60
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, nodesCreated: 1 });
|
|
61
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
62
|
+
strict_1.default.equal(w, undefined);
|
|
63
|
+
});
|
|
64
|
+
(0, node_test_1.default)("audit: provenance check ignores stamps inside string literals", () => {
|
|
65
|
+
const cypher = "CREATE (n:Person {bio: 'I have createdByAgent in my bio'})";
|
|
66
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, nodesCreated: 1 });
|
|
67
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
68
|
+
strict_1.default.ok(w, "expected missing-provenance-warning despite stamp-like substring in literal");
|
|
69
|
+
});
|
|
70
|
+
(0, node_test_1.default)("audit: idempotent MERGE that matched existing node (nodesCreated=0) emits no missing-provenance-warning", () => {
|
|
71
|
+
// Reviewer-flagged false-positive case. MERGE (n:Person {email}) without
|
|
72
|
+
// ON CREATE SET stamps — static count = 1, stamps = 0. If the node
|
|
73
|
+
// already existed, upstream reports nodesCreated=0 and no provenance was
|
|
74
|
+
// needed; the audit must not warn.
|
|
75
|
+
const cypher = "MERGE (n:Person {email: $email})";
|
|
76
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({ ...baseInput, cypher, nodesCreated: 0 });
|
|
77
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
78
|
+
strict_1.default.equal(w, undefined, "must not warn when nodesCreated=0");
|
|
79
|
+
});
|
|
80
|
+
(0, node_test_1.default)("audit: orphanIds populated yields orphan-warning", () => {
|
|
81
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({
|
|
82
|
+
...baseInput,
|
|
83
|
+
cypher: "CREATE (n:Person)",
|
|
84
|
+
nodesCreated: 1,
|
|
85
|
+
orphanIds: ["4:abc:1", "4:abc:2"],
|
|
86
|
+
});
|
|
87
|
+
const w = warnings.find((x) => x.kind === "orphan-warning");
|
|
88
|
+
strict_1.default.ok(w, "expected orphan-warning");
|
|
89
|
+
if (w.kind === "orphan-warning") {
|
|
90
|
+
strict_1.default.deepEqual(w.orphanIds, ["4:abc:1", "4:abc:2"]);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
(0, node_test_1.default)("audit: empty orphanIds emits no orphan-warning", () => {
|
|
94
|
+
const warnings = (0, audit_js_1.auditCypherWrite)({
|
|
95
|
+
...baseInput,
|
|
96
|
+
cypher: "MATCH (a:Person), (b:Person) CREATE (a)-[:KNOWS]->(b)",
|
|
97
|
+
relsCreated: 1,
|
|
98
|
+
orphanIds: [],
|
|
99
|
+
});
|
|
100
|
+
const w = warnings.find((x) => x.kind === "orphan-warning");
|
|
101
|
+
strict_1.default.equal(w, undefined);
|
|
102
|
+
});
|
|
103
|
+
(0, node_test_1.default)("formatAuditLine: accepted line includes counters + provenance", () => {
|
|
104
|
+
const line = (0, audit_js_1.formatAuditLine)({
|
|
105
|
+
kind: "accepted",
|
|
106
|
+
cypherPrefix: "MATCH (a:Person) CREATE",
|
|
107
|
+
nodesCreated: 1,
|
|
108
|
+
relsCreated: 4,
|
|
109
|
+
agentName: "database-operator",
|
|
110
|
+
sessionId: "abc-123",
|
|
111
|
+
});
|
|
112
|
+
strict_1.default.match(line, /^\[graph-cypher-write\] accepted /);
|
|
113
|
+
strict_1.default.match(line, /nodesCreated=1/);
|
|
114
|
+
strict_1.default.match(line, /relsCreated=4/);
|
|
115
|
+
strict_1.default.match(line, /agentName=database-operator/);
|
|
116
|
+
strict_1.default.match(line, /sessionId=abc-123/);
|
|
117
|
+
});
|
|
118
|
+
(0, node_test_1.default)("formatAuditLine: orphan-warning line lists orphanIds", () => {
|
|
119
|
+
const line = (0, audit_js_1.formatAuditLine)({
|
|
120
|
+
kind: "orphan-warning",
|
|
121
|
+
cypherPrefix: "CREATE (n:Person)",
|
|
122
|
+
orphanIds: ["4:abc:1", "4:abc:2"],
|
|
123
|
+
});
|
|
124
|
+
strict_1.default.match(line, /^\[graph-cypher-write\] orphan-warning /);
|
|
125
|
+
strict_1.default.match(line, /orphanIds=4:abc:1,4:abc:2/);
|
|
126
|
+
});
|
|
127
|
+
(0, node_test_1.default)("formatAuditLine: unknown-type-warning names the type", () => {
|
|
128
|
+
const line = (0, audit_js_1.formatAuditLine)({
|
|
129
|
+
kind: "unknown-type-warning",
|
|
130
|
+
cypherPrefix: "CREATE (a)-[:HAS_KNOWN]->(b)",
|
|
131
|
+
type: "HAS_KNOWN",
|
|
132
|
+
});
|
|
133
|
+
strict_1.default.match(line, /^\[graph-cypher-write\] unknown-type-warning /);
|
|
134
|
+
strict_1.default.match(line, /type=HAS_KNOWN/);
|
|
135
|
+
});
|
|
136
|
+
(0, node_test_1.default)("formatAuditLine: missing-provenance-warning shows created vs stamped counts", () => {
|
|
137
|
+
const line = (0, audit_js_1.formatAuditLine)({
|
|
138
|
+
kind: "missing-provenance-warning",
|
|
139
|
+
cypherPrefix: "CREATE (n:Person)",
|
|
140
|
+
created: 3,
|
|
141
|
+
stamped: 1,
|
|
142
|
+
});
|
|
143
|
+
strict_1.default.match(line, /^\[graph-cypher-write\] missing-provenance-warning /);
|
|
144
|
+
strict_1.default.match(line, /created=3/);
|
|
145
|
+
strict_1.default.match(line, /stamped=1/);
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=audit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.test.js","sourceRoot":"","sources":["../../src/__tests__/audit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-write audit for the database-operator's raw `write_neo4j_cypher` tool
|
|
3
|
+
* (Task 796). Sibling to `[graph-write] accepted` for the wrapped writers
|
|
4
|
+
* (`memory-write`, `contact-create`, etc. — Task 673) — same module so the
|
|
5
|
+
* `[graph-*]` log family stays uniform across both write surfaces.
|
|
6
|
+
*
|
|
7
|
+
* Design posture:
|
|
8
|
+
* - Static parse first. Pre-flight regex on the cypher text catches missing
|
|
9
|
+
* provenance stamps and unknown edge types cheaply, no driver round-trip.
|
|
10
|
+
* - Dynamic orphan detection is the caller's responsibility — the graph-mcp
|
|
11
|
+
* shim runs the detection query against Neo4j after the upstream commits
|
|
12
|
+
* and passes the resulting elementId list as `orphanIds`. Keeping the
|
|
13
|
+
* module pure (no neo4j-driver import) makes it unit-testable without a
|
|
14
|
+
* live database.
|
|
15
|
+
* - Audit is observational. Warnings never block the write. The
|
|
16
|
+
* prompt-level Graph Stewardship Doctrine in
|
|
17
|
+
* [database-operator.md](../../../templates/specialists/agents/database-operator.md)
|
|
18
|
+
* names the discipline; the audit is the verification stream.
|
|
19
|
+
*
|
|
20
|
+
* String literals are stripped before pattern checks so that property values
|
|
21
|
+
* containing words like `'createdByAgent in my bio'` cannot trip a false
|
|
22
|
+
* provenance count, and edge-like patterns inside quoted strings cannot
|
|
23
|
+
* trip a false unknown-type warning. The regex shape is duplicated from
|
|
24
|
+
* [cypher-validate.ts](../../graph-mcp/src/cypher-validate.ts) intentionally:
|
|
25
|
+
* graph-write does not depend on graph-mcp, and a one-line regex helper
|
|
26
|
+
* doesn't justify a third package. Drift in either copy is a regression
|
|
27
|
+
* surfaced by both the validator tests and the audit tests.
|
|
28
|
+
*/
|
|
29
|
+
export interface SchemaSnapshot {
|
|
30
|
+
readonly labels: ReadonlySet<string>;
|
|
31
|
+
readonly relationshipTypes: ReadonlySet<string>;
|
|
32
|
+
}
|
|
33
|
+
export interface CypherWriteAuditInput {
|
|
34
|
+
cypher: string;
|
|
35
|
+
schema: SchemaSnapshot;
|
|
36
|
+
agentName: string;
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** Counter from upstream response — drives missing-provenance arithmetic. */
|
|
39
|
+
nodesCreated: number;
|
|
40
|
+
/** Counter from upstream response — informational only. */
|
|
41
|
+
relsCreated: number;
|
|
42
|
+
/**
|
|
43
|
+
* elementIds of nodes the post-write orphan query identified. Caller scopes
|
|
44
|
+
* the query to (createdBySession, createdAt >= writeStartTimestamp) so this
|
|
45
|
+
* list cannot include nodes from prior writes in the same session. Empty
|
|
46
|
+
* array = no orphans (or no scope to check).
|
|
47
|
+
*/
|
|
48
|
+
orphanIds: string[];
|
|
49
|
+
}
|
|
50
|
+
export type AuditWarning = {
|
|
51
|
+
kind: "orphan-warning";
|
|
52
|
+
orphanIds: string[];
|
|
53
|
+
} | {
|
|
54
|
+
kind: "unknown-type-warning";
|
|
55
|
+
type: string;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "missing-provenance-warning";
|
|
58
|
+
created: number;
|
|
59
|
+
stamped: number;
|
|
60
|
+
};
|
|
61
|
+
export type AuditLine = {
|
|
62
|
+
kind: "accepted";
|
|
63
|
+
cypherPrefix: string;
|
|
64
|
+
nodesCreated: number;
|
|
65
|
+
relsCreated: number;
|
|
66
|
+
agentName: string;
|
|
67
|
+
sessionId: string;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "orphan-warning";
|
|
70
|
+
cypherPrefix: string;
|
|
71
|
+
orphanIds: string[];
|
|
72
|
+
} | {
|
|
73
|
+
kind: "unknown-type-warning";
|
|
74
|
+
cypherPrefix: string;
|
|
75
|
+
type: string;
|
|
76
|
+
} | {
|
|
77
|
+
kind: "missing-provenance-warning";
|
|
78
|
+
cypherPrefix: string;
|
|
79
|
+
created: number;
|
|
80
|
+
stamped: number;
|
|
81
|
+
};
|
|
82
|
+
export declare function auditCypherWrite(input: CypherWriteAuditInput): AuditWarning[];
|
|
83
|
+
export declare function formatAuditLine(line: AuditLine): string;
|
|
84
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,4BAA4B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,MAAM,SAAS,GACjB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpE;IACE,IAAI,EAAE,4BAA4B,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA8CN,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,YAAY,EAAE,CAgD7E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAYvD"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Post-write audit for the database-operator's raw `write_neo4j_cypher` tool
|
|
4
|
+
* (Task 796). Sibling to `[graph-write] accepted` for the wrapped writers
|
|
5
|
+
* (`memory-write`, `contact-create`, etc. — Task 673) — same module so the
|
|
6
|
+
* `[graph-*]` log family stays uniform across both write surfaces.
|
|
7
|
+
*
|
|
8
|
+
* Design posture:
|
|
9
|
+
* - Static parse first. Pre-flight regex on the cypher text catches missing
|
|
10
|
+
* provenance stamps and unknown edge types cheaply, no driver round-trip.
|
|
11
|
+
* - Dynamic orphan detection is the caller's responsibility — the graph-mcp
|
|
12
|
+
* shim runs the detection query against Neo4j after the upstream commits
|
|
13
|
+
* and passes the resulting elementId list as `orphanIds`. Keeping the
|
|
14
|
+
* module pure (no neo4j-driver import) makes it unit-testable without a
|
|
15
|
+
* live database.
|
|
16
|
+
* - Audit is observational. Warnings never block the write. The
|
|
17
|
+
* prompt-level Graph Stewardship Doctrine in
|
|
18
|
+
* [database-operator.md](../../../templates/specialists/agents/database-operator.md)
|
|
19
|
+
* names the discipline; the audit is the verification stream.
|
|
20
|
+
*
|
|
21
|
+
* String literals are stripped before pattern checks so that property values
|
|
22
|
+
* containing words like `'createdByAgent in my bio'` cannot trip a false
|
|
23
|
+
* provenance count, and edge-like patterns inside quoted strings cannot
|
|
24
|
+
* trip a false unknown-type warning. The regex shape is duplicated from
|
|
25
|
+
* [cypher-validate.ts](../../graph-mcp/src/cypher-validate.ts) intentionally:
|
|
26
|
+
* graph-write does not depend on graph-mcp, and a one-line regex helper
|
|
27
|
+
* doesn't justify a third package. Drift in either copy is a regression
|
|
28
|
+
* surfaced by both the validator tests and the audit tests.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.auditCypherWrite = auditCypherWrite;
|
|
32
|
+
exports.formatAuditLine = formatAuditLine;
|
|
33
|
+
// Mirrors cypher-validate's pattern. Captures TYPE(|TYPE)* alternation; the
|
|
34
|
+
// audit splits on `|` to enumerate every referenced type.
|
|
35
|
+
const EDGE_PATTERN = /\[[^\]]*?:([A-Z_][A-Za-z0-9_]*(?:\|[A-Z_][A-Za-z0-9_]*)*)[^\]]*?\]/g;
|
|
36
|
+
// Counts CREATE (n:Label) and MERGE (n:Label) statements that introduce a
|
|
37
|
+
// node. The trailing `[A-Z]` requires at least one label (so `CREATE INDEX`
|
|
38
|
+
// and bare `CREATE (a)-[:R]->(b)` shapes don't trip this — bare patterns
|
|
39
|
+
// reference a previously-MATCHed alias and don't introduce new nodes).
|
|
40
|
+
const CREATE_OR_MERGE_NODE = /\b(?:CREATE|MERGE)\s*\(\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*[A-Z]/g;
|
|
41
|
+
// Stamp tokens — at least one of these MUST appear per created node for the
|
|
42
|
+
// write to satisfy provenance discipline. The audit counts occurrences;
|
|
43
|
+
// stamp-count >= node-count is the pass criterion.
|
|
44
|
+
const PROVENANCE_TOKEN = /\bcreatedBy(?:Agent|Tool|Session|Source)\b/g;
|
|
45
|
+
function stripStringLiterals(cypher) {
|
|
46
|
+
// Identical regex to cypher-validate.ts. Replaces single- and double-
|
|
47
|
+
// quoted literals with empty quotes so e.g. 'CREATE (n:Person)' inside a
|
|
48
|
+
// property value cannot inflate the create-count.
|
|
49
|
+
return cypher.replace(/'[^']*'|"[^"]*"/g, '""');
|
|
50
|
+
}
|
|
51
|
+
function extractEdgeTypes(cleaned) {
|
|
52
|
+
const out = new Set();
|
|
53
|
+
// Use String.matchAll for stateless iteration — no shared regex lastIndex.
|
|
54
|
+
for (const m of cleaned.matchAll(EDGE_PATTERN)) {
|
|
55
|
+
for (const t of m[1].split("|")) {
|
|
56
|
+
const clean = t.trim();
|
|
57
|
+
if (clean)
|
|
58
|
+
out.add(clean);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function countCreateOrMergeNodes(cleaned) {
|
|
64
|
+
const matches = cleaned.match(CREATE_OR_MERGE_NODE);
|
|
65
|
+
return matches ? matches.length : 0;
|
|
66
|
+
}
|
|
67
|
+
function countProvenanceStamps(cleaned) {
|
|
68
|
+
const matches = cleaned.match(PROVENANCE_TOKEN);
|
|
69
|
+
return matches ? matches.length : 0;
|
|
70
|
+
}
|
|
71
|
+
function auditCypherWrite(input) {
|
|
72
|
+
const warnings = [];
|
|
73
|
+
const cleaned = stripStringLiterals(input.cypher);
|
|
74
|
+
// 1. Unknown edge type — cypher names a type not in the live ontology.
|
|
75
|
+
// Enumerate every referenced type; emit one warning per unknown.
|
|
76
|
+
const referencedTypes = extractEdgeTypes(cleaned);
|
|
77
|
+
for (const t of referencedTypes) {
|
|
78
|
+
if (!input.schema.relationshipTypes.has(t)) {
|
|
79
|
+
warnings.push({ kind: "unknown-type-warning", type: t });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 2. Missing provenance — count CREATE/MERGE-with-label statements vs
|
|
83
|
+
// `createdBy*` token occurrences. Stamps may live in inline maps
|
|
84
|
+
// (`{createdByAgent: $agent}`) or SET clauses; both shapes contribute
|
|
85
|
+
// to the token count. The arithmetic is precision-imprecise — a
|
|
86
|
+
// multi-stamp single CREATE inflates `stamped`, a multi-CREATE single
|
|
87
|
+
// SET undercounts — but the directional signal (zero stamps for many
|
|
88
|
+
// creates) catches the failure mode this audit exists to surface.
|
|
89
|
+
//
|
|
90
|
+
// nodesCreated=0 short-circuits the check: the upstream counter is
|
|
91
|
+
// ground truth for whether any node was actually persisted. An
|
|
92
|
+
// idempotent MERGE that matched an existing node has a static
|
|
93
|
+
// create-count of 1 but contributed no new node — no provenance was
|
|
94
|
+
// needed, and warning here would be a false positive.
|
|
95
|
+
if (input.nodesCreated > 0) {
|
|
96
|
+
const createOrMergeNodes = countCreateOrMergeNodes(cleaned);
|
|
97
|
+
if (createOrMergeNodes > 0) {
|
|
98
|
+
const stamps = countProvenanceStamps(cleaned);
|
|
99
|
+
if (stamps < createOrMergeNodes) {
|
|
100
|
+
warnings.push({
|
|
101
|
+
kind: "missing-provenance-warning",
|
|
102
|
+
created: createOrMergeNodes,
|
|
103
|
+
stamped: stamps,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 3. Orphans — caller-supplied. The dynamic detection happens at the
|
|
109
|
+
// graph-mcp shim layer (which has the Neo4j driver); the audit module
|
|
110
|
+
// only renders the warning shape.
|
|
111
|
+
if (input.orphanIds.length > 0) {
|
|
112
|
+
warnings.push({ kind: "orphan-warning", orphanIds: input.orphanIds });
|
|
113
|
+
}
|
|
114
|
+
return warnings;
|
|
115
|
+
}
|
|
116
|
+
function formatAuditLine(line) {
|
|
117
|
+
const prefixField = `query="${line.cypherPrefix.replace(/"/g, "'")}"`;
|
|
118
|
+
switch (line.kind) {
|
|
119
|
+
case "accepted":
|
|
120
|
+
return `[graph-cypher-write] accepted ${prefixField} nodesCreated=${line.nodesCreated} relsCreated=${line.relsCreated} agentName=${line.agentName} sessionId=${line.sessionId}`;
|
|
121
|
+
case "orphan-warning":
|
|
122
|
+
return `[graph-cypher-write] orphan-warning ${prefixField} orphanIds=${line.orphanIds.join(",")}`;
|
|
123
|
+
case "unknown-type-warning":
|
|
124
|
+
return `[graph-cypher-write] unknown-type-warning ${prefixField} type=${line.type}`;
|
|
125
|
+
case "missing-provenance-warning":
|
|
126
|
+
return `[graph-cypher-write] missing-provenance-warning ${prefixField} created=${line.created} stamped=${line.stamped}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;GAqBG;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,CA8G1B"}
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* - unresolved target id → `[graph-write] reject reason=unresolved-target`
|
|
17
|
-
*
|
|
18
|
-
* The `createdBy.agent` field is advisory, not authoritative — it is sourced
|
|
19
|
-
* from the MCP server's AGENT_SLUG env var, which is set by the trusted
|
|
20
|
-
* spawning code (admin server / workflow runner). A misconfigured spawner
|
|
21
|
-
* will write "unknown"; that's the signal something is wrong. Do not use
|
|
22
|
-
* this field for access control — it is forensic, not a security boundary.
|
|
23
|
-
*/
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
24
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
17
|
exports.stampCreatedBy = stampCreatedBy;
|
|
26
18
|
exports.writeNodeWithEdges = writeNodeWithEdges;
|
|
19
|
+
// Task 796: re-export the post-write audit so consumers (graph-mcp shim)
|
|
20
|
+
// import from the same package as `writeNodeWithEdges`. Keeps the
|
|
21
|
+
// `[graph-*]` log family colocated.
|
|
22
|
+
__exportStar(require("./audit.js"), exports);
|
|
27
23
|
/** Stamp flattened provenance into node properties. */
|
|
28
24
|
function stampCreatedBy(props, createdBy) {
|
|
29
25
|
return {
|
|
@@ -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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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
auditCypherWrite,
|
|
5
|
+
formatAuditLine,
|
|
6
|
+
type CypherWriteAuditInput,
|
|
7
|
+
} from "../audit.js";
|
|
8
|
+
|
|
9
|
+
const baseInput: Omit<CypherWriteAuditInput, "cypher"> = {
|
|
10
|
+
schema: {
|
|
11
|
+
labels: new Set(["Person", "Organization", "Task", "KnowledgeDocument"]),
|
|
12
|
+
relationshipTypes: new Set(["KNOWS", "PARTICIPANT", "REFERENCES", "MENTIONS", "PART_OF"]),
|
|
13
|
+
},
|
|
14
|
+
agentName: "database-operator",
|
|
15
|
+
sessionId: "session-abc",
|
|
16
|
+
nodesCreated: 0,
|
|
17
|
+
relsCreated: 0,
|
|
18
|
+
orphanIds: [],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
test("audit: clean write with provenance + known types yields zero warnings", () => {
|
|
22
|
+
const cypher = `
|
|
23
|
+
MATCH (p:Person {name: $name}), (o:Organization {name: $org})
|
|
24
|
+
MERGE (p)-[r:KNOWS]->(o)
|
|
25
|
+
SET r.createdAt = datetime(),
|
|
26
|
+
r.createdByAgent = $agent,
|
|
27
|
+
r.createdByTool = 'graph-cypher-write',
|
|
28
|
+
r.createdBySession = $sessionId
|
|
29
|
+
`;
|
|
30
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, relsCreated: 1 });
|
|
31
|
+
assert.deepEqual(warnings, []);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("audit: unknown edge type yields unknown-type-warning", () => {
|
|
35
|
+
const cypher = "MATCH (a:Person), (b:Person) CREATE (a)-[:HAS_KNOWN]->(b)";
|
|
36
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, relsCreated: 1 });
|
|
37
|
+
const w = warnings.find((x) => x.kind === "unknown-type-warning");
|
|
38
|
+
assert.ok(w, "expected unknown-type-warning");
|
|
39
|
+
assert.equal(w.kind, "unknown-type-warning");
|
|
40
|
+
if (w.kind === "unknown-type-warning") {
|
|
41
|
+
assert.equal(w.type, "HAS_KNOWN");
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("audit: known APOC edge in YIELD does not trip unknown-type", () => {
|
|
46
|
+
const cypher =
|
|
47
|
+
"MATCH (a:Person), (b:Person) WHERE a.email = b.email AND id(a) < id(b) WITH a, b CALL apoc.refactor.mergeNodes([a, b]) YIELD node RETURN node";
|
|
48
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher });
|
|
49
|
+
const unknownTypeWarn = warnings.find((x) => x.kind === "unknown-type-warning");
|
|
50
|
+
assert.equal(unknownTypeWarn, undefined);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("audit: CREATE with no provenance stamps yields missing-provenance-warning", () => {
|
|
54
|
+
const cypher = "CREATE (n:Person {name: 'Foo'})";
|
|
55
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 });
|
|
56
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
57
|
+
assert.ok(w, "expected missing-provenance-warning");
|
|
58
|
+
if (w.kind === "missing-provenance-warning") {
|
|
59
|
+
assert.equal(w.created, 1);
|
|
60
|
+
assert.equal(w.stamped, 0);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("audit: MERGE with createdBy* stamps does not trip missing-provenance", () => {
|
|
65
|
+
const cypher =
|
|
66
|
+
"MERGE (n:Person {email: $email}) ON CREATE SET n.createdByAgent = $agent, n.createdBySession = $sid";
|
|
67
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 });
|
|
68
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
69
|
+
assert.equal(w, undefined);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("audit: provenance check ignores stamps inside string literals", () => {
|
|
73
|
+
const cypher =
|
|
74
|
+
"CREATE (n:Person {bio: 'I have createdByAgent in my bio'})";
|
|
75
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 1 });
|
|
76
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
77
|
+
assert.ok(w, "expected missing-provenance-warning despite stamp-like substring in literal");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("audit: idempotent MERGE that matched existing node (nodesCreated=0) emits no missing-provenance-warning", () => {
|
|
81
|
+
// Reviewer-flagged false-positive case. MERGE (n:Person {email}) without
|
|
82
|
+
// ON CREATE SET stamps — static count = 1, stamps = 0. If the node
|
|
83
|
+
// already existed, upstream reports nodesCreated=0 and no provenance was
|
|
84
|
+
// needed; the audit must not warn.
|
|
85
|
+
const cypher = "MERGE (n:Person {email: $email})";
|
|
86
|
+
const warnings = auditCypherWrite({ ...baseInput, cypher, nodesCreated: 0 });
|
|
87
|
+
const w = warnings.find((x) => x.kind === "missing-provenance-warning");
|
|
88
|
+
assert.equal(w, undefined, "must not warn when nodesCreated=0");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("audit: orphanIds populated yields orphan-warning", () => {
|
|
92
|
+
const warnings = auditCypherWrite({
|
|
93
|
+
...baseInput,
|
|
94
|
+
cypher: "CREATE (n:Person)",
|
|
95
|
+
nodesCreated: 1,
|
|
96
|
+
orphanIds: ["4:abc:1", "4:abc:2"],
|
|
97
|
+
});
|
|
98
|
+
const w = warnings.find((x) => x.kind === "orphan-warning");
|
|
99
|
+
assert.ok(w, "expected orphan-warning");
|
|
100
|
+
if (w.kind === "orphan-warning") {
|
|
101
|
+
assert.deepEqual(w.orphanIds, ["4:abc:1", "4:abc:2"]);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("audit: empty orphanIds emits no orphan-warning", () => {
|
|
106
|
+
const warnings = auditCypherWrite({
|
|
107
|
+
...baseInput,
|
|
108
|
+
cypher: "MATCH (a:Person), (b:Person) CREATE (a)-[:KNOWS]->(b)",
|
|
109
|
+
relsCreated: 1,
|
|
110
|
+
orphanIds: [],
|
|
111
|
+
});
|
|
112
|
+
const w = warnings.find((x) => x.kind === "orphan-warning");
|
|
113
|
+
assert.equal(w, undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("formatAuditLine: accepted line includes counters + provenance", () => {
|
|
117
|
+
const line = formatAuditLine({
|
|
118
|
+
kind: "accepted",
|
|
119
|
+
cypherPrefix: "MATCH (a:Person) CREATE",
|
|
120
|
+
nodesCreated: 1,
|
|
121
|
+
relsCreated: 4,
|
|
122
|
+
agentName: "database-operator",
|
|
123
|
+
sessionId: "abc-123",
|
|
124
|
+
});
|
|
125
|
+
assert.match(line, /^\[graph-cypher-write\] accepted /);
|
|
126
|
+
assert.match(line, /nodesCreated=1/);
|
|
127
|
+
assert.match(line, /relsCreated=4/);
|
|
128
|
+
assert.match(line, /agentName=database-operator/);
|
|
129
|
+
assert.match(line, /sessionId=abc-123/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("formatAuditLine: orphan-warning line lists orphanIds", () => {
|
|
133
|
+
const line = formatAuditLine({
|
|
134
|
+
kind: "orphan-warning",
|
|
135
|
+
cypherPrefix: "CREATE (n:Person)",
|
|
136
|
+
orphanIds: ["4:abc:1", "4:abc:2"],
|
|
137
|
+
});
|
|
138
|
+
assert.match(line, /^\[graph-cypher-write\] orphan-warning /);
|
|
139
|
+
assert.match(line, /orphanIds=4:abc:1,4:abc:2/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("formatAuditLine: unknown-type-warning names the type", () => {
|
|
143
|
+
const line = formatAuditLine({
|
|
144
|
+
kind: "unknown-type-warning",
|
|
145
|
+
cypherPrefix: "CREATE (a)-[:HAS_KNOWN]->(b)",
|
|
146
|
+
type: "HAS_KNOWN",
|
|
147
|
+
});
|
|
148
|
+
assert.match(line, /^\[graph-cypher-write\] unknown-type-warning /);
|
|
149
|
+
assert.match(line, /type=HAS_KNOWN/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("formatAuditLine: missing-provenance-warning shows created vs stamped counts", () => {
|
|
153
|
+
const line = formatAuditLine({
|
|
154
|
+
kind: "missing-provenance-warning",
|
|
155
|
+
cypherPrefix: "CREATE (n:Person)",
|
|
156
|
+
created: 3,
|
|
157
|
+
stamped: 1,
|
|
158
|
+
});
|
|
159
|
+
assert.match(line, /^\[graph-cypher-write\] missing-provenance-warning /);
|
|
160
|
+
assert.match(line, /created=3/);
|
|
161
|
+
assert.match(line, /stamped=1/);
|
|
162
|
+
});
|