@rubytech/create-maxy 1.0.744 → 1.0.746

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 (29) hide show
  1. package/dist/__tests__/peer-brand-detect.test.js +103 -0
  2. package/dist/index.js +63 -4
  3. package/dist/peer-brand-detect.js +39 -0
  4. package/package.json +1 -1
  5. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts +37 -0
  6. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js +333 -0
  8. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts +71 -0
  10. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js +168 -0
  12. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js.map +1 -0
  13. package/payload/platform/lib/graph-mcp/dist/index.js +146 -3
  14. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  15. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +3 -6
  16. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -1
  17. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +30 -7
  18. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -1
  19. package/payload/platform/lib/graph-mcp/src/cypher-rewrite-stamp.ts +349 -0
  20. package/payload/platform/lib/graph-mcp/src/cypher-shim-write.ts +240 -0
  21. package/payload/platform/lib/graph-mcp/src/index.ts +216 -4
  22. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +37 -7
  23. package/payload/platform/plugins/docs/references/deployment.md +2 -1
  24. package/payload/platform/templates/specialists/agents/database-operator.md +3 -22
  25. package/payload/server/chunk-SPTD7L7Z.js +9474 -0
  26. package/payload/server/maxy-edge.js +1 -1
  27. package/payload/server/public/assets/{graph-DhNy70eS.js → graph-BoSJpLG3.js} +1 -1
  28. package/payload/server/public/graph.html +1 -1
  29. package/payload/server/server.js +1 -1
@@ -0,0 +1,103 @@
1
+ // Task 800 — acceptance grid for findPeerBrandOnDefaultNeo4jPort.
2
+ //
3
+ // Each test exercises one branch of the matching rule. The wrapper in
4
+ // index.ts owns fs reads + warning emission; this suite does not touch fs.
5
+ //
6
+ // Runs via Node's built-in test runner (matches apt-resolve.test.ts +
7
+ // port-canonicalisation.test.ts). The brief asked for vitest, but the
8
+ // package has no vitest dependency — node:test is the codebase convention
9
+ // and ships with the toolchain.
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { findPeerBrandOnDefaultNeo4jPort } from "../peer-brand-detect.js";
13
+ const DEFAULT_PORT = 7687;
14
+ const CURRENT = "realagent"; // installer is realagent; peer would be maxy
15
+ test("no peer hostnames → null", () => {
16
+ const result = findPeerBrandOnDefaultNeo4jPort({
17
+ currentBrandHostname: CURRENT,
18
+ defaultNeo4jPort: DEFAULT_PORT,
19
+ peerEnvContents: [],
20
+ });
21
+ assert.equal(result, null);
22
+ });
23
+ test("peer with content=null (no .env on disk) → null", () => {
24
+ const result = findPeerBrandOnDefaultNeo4jPort({
25
+ currentBrandHostname: CURRENT,
26
+ defaultNeo4jPort: DEFAULT_PORT,
27
+ peerEnvContents: [["maxy", null]],
28
+ });
29
+ assert.equal(result, null);
30
+ });
31
+ test("peer pins NEO4J_URI=bolt://localhost:7687 → returns peer hostname", () => {
32
+ const env = `EMBED_MODEL=nomic-embed-text\nNEO4J_URI=bolt://localhost:7687\nACCOUNT_ID=abc\n`;
33
+ const result = findPeerBrandOnDefaultNeo4jPort({
34
+ currentBrandHostname: CURRENT,
35
+ defaultNeo4jPort: DEFAULT_PORT,
36
+ peerEnvContents: [["maxy", env]],
37
+ });
38
+ assert.equal(result, "maxy");
39
+ });
40
+ test("peer pins dedicated port 7688 (not the system unit) → null", () => {
41
+ const env = `NEO4J_URI=bolt://localhost:7688\n`;
42
+ const result = findPeerBrandOnDefaultNeo4jPort({
43
+ currentBrandHostname: CURRENT,
44
+ defaultNeo4jPort: DEFAULT_PORT,
45
+ peerEnvContents: [["maxy", env]],
46
+ });
47
+ assert.equal(result, null);
48
+ });
49
+ test("peer .env has no NEO4J_URI line → null", () => {
50
+ const env = `EMBED_MODEL=nomic-embed-text\nDISPLAY_MODE=hd\n`;
51
+ const result = findPeerBrandOnDefaultNeo4jPort({
52
+ currentBrandHostname: CURRENT,
53
+ defaultNeo4jPort: DEFAULT_PORT,
54
+ peerEnvContents: [["maxy", env]],
55
+ });
56
+ assert.equal(result, null);
57
+ });
58
+ test("peer .env is empty → null", () => {
59
+ const result = findPeerBrandOnDefaultNeo4jPort({
60
+ currentBrandHostname: CURRENT,
61
+ defaultNeo4jPort: DEFAULT_PORT,
62
+ peerEnvContents: [["maxy", ""]],
63
+ });
64
+ assert.equal(result, null);
65
+ });
66
+ test("multiple peers, second one matches → returns the matching hostname", () => {
67
+ const result = findPeerBrandOnDefaultNeo4jPort({
68
+ currentBrandHostname: CURRENT,
69
+ defaultNeo4jPort: DEFAULT_PORT,
70
+ peerEnvContents: [
71
+ ["future-brand", `NEO4J_URI=bolt://localhost:7689\n`],
72
+ ["maxy", `NEO4J_URI=bolt://localhost:7687\n`],
73
+ ],
74
+ });
75
+ assert.equal(result, "maxy");
76
+ });
77
+ test("only the current brand in iterable → filtered, returns null", () => {
78
+ // Caller may pass the full known-brand list; the helper filters self.
79
+ const result = findPeerBrandOnDefaultNeo4jPort({
80
+ currentBrandHostname: CURRENT,
81
+ defaultNeo4jPort: DEFAULT_PORT,
82
+ peerEnvContents: [[CURRENT, `NEO4J_URI=bolt://localhost:7687\n`]],
83
+ });
84
+ assert.equal(result, null);
85
+ });
86
+ test("URI with non-bolt scheme is not a match", () => {
87
+ const env = `NEO4J_URI=neo4j://localhost:7687\n`;
88
+ const result = findPeerBrandOnDefaultNeo4jPort({
89
+ currentBrandHostname: CURRENT,
90
+ defaultNeo4jPort: DEFAULT_PORT,
91
+ peerEnvContents: [["maxy", env]],
92
+ });
93
+ assert.equal(result, null);
94
+ });
95
+ test("URI with non-localhost host is not a match", () => {
96
+ const env = `NEO4J_URI=bolt://neo4j.example.com:7687\n`;
97
+ const result = findPeerBrandOnDefaultNeo4jPort({
98
+ currentBrandHostname: CURRENT,
99
+ defaultNeo4jPort: DEFAULT_PORT,
100
+ peerEnvContents: [["maxy", env]],
101
+ });
102
+ assert.equal(result, null);
103
+ });
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { resolve, join, dirname } from "node:path";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { resolveInstallPortFromFs, buildMaxyUnitFile } from "./port-resolution.js";
7
7
  import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
8
+ import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
8
9
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
9
10
  // Brand manifest — read from payload to derive all brand-specific installation values.
10
11
  // The bundler stamps brand.json into the payload at build time.
@@ -841,6 +842,48 @@ function installNeo4j() {
841
842
  shell("systemctl", ["start", "neo4j"], { sudo: true });
842
843
  console.log(" Neo4j started. Password stored securely.");
843
844
  }
845
+ /**
846
+ * Task 800 — does any peer brand on this host pin `NEO4J_URI=bolt://localhost:<default>`
847
+ * in its `.env`? If yes, the apt-installed `neo4j.service` is its database and the
848
+ * dedicated-unit installer must NOT stop+disable it. Returns the first matching
849
+ * peer's hostname, or `null` when no peer pins the default port.
850
+ *
851
+ * Wraps the pure decision in `peer-brand-detect.ts` with the fs reads of
852
+ * `~/.<peer>/.env`. Bias on read errors: if `.env` exists but is unreadable
853
+ * (permissions, transient I/O), the wrapper treats that peer as a *potential*
854
+ * dependency and short-circuits to the kept-active path. Disabling the system
855
+ * unit on faulty evidence would silently kill the peer's database (the exact
856
+ * failure Task 800 prevents); a conservatively-skipped disable is recoverable
857
+ * because the dedicated-unit bind check at the end of `setupDedicatedNeo4j`
858
+ * fails loud if the system unit is actually free.
859
+ */
860
+ function peerBrandUsingSystemUnit() {
861
+ const home = process.env.HOME ?? "/root";
862
+ const peerEnvContents = [];
863
+ for (const hostname of KNOWN_BRAND_HOSTNAMES) {
864
+ if (hostname === BRAND.hostname) {
865
+ peerEnvContents.push([hostname, null]);
866
+ continue;
867
+ }
868
+ const envPath = resolve(home, `.${hostname}`, ".env");
869
+ if (!existsSync(envPath)) {
870
+ peerEnvContents.push([hostname, null]);
871
+ continue;
872
+ }
873
+ try {
874
+ peerEnvContents.push([hostname, readFileSync(envPath, "utf-8")]);
875
+ }
876
+ catch (err) {
877
+ console.error(` WARNING: unable to read peer brand .env at ${envPath} — treating as potential dependency to avoid data loss (Task 800): ${err instanceof Error ? err.message : String(err)}`);
878
+ return hostname;
879
+ }
880
+ }
881
+ return findPeerBrandOnDefaultNeo4jPort({
882
+ currentBrandHostname: BRAND.hostname,
883
+ defaultNeo4jPort: DEFAULT_NEO4J_PORT,
884
+ peerEnvContents,
885
+ });
886
+ }
844
887
  /**
845
888
  * Create a dedicated Neo4j instance for this brand when NEO4J_DEDICATED is true.
846
889
  * Produces: separate config dir, data dir, log dir, systemd service, and password.
@@ -849,6 +892,10 @@ function installNeo4j() {
849
892
  * dedicated, start, verify) so a half-installed Pi recovers in-place without
850
893
  * manual systemctl. ensureNeo4jPassword() handles password verification on the
851
894
  * recovery path.
895
+ *
896
+ * Task 800: on multi-brand hosts where a peer brand still depends on the apt
897
+ * `neo4j.service` (port 7687), the stop+disable step is skipped — disabling
898
+ * the system unit would kill the peer's database.
852
899
  */
853
900
  function setupDedicatedNeo4j() {
854
901
  if (!NEO4J_DEDICATED)
@@ -952,10 +999,22 @@ WantedBy=multi-user.target
952
999
  spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
953
1000
  console.log(" [privileged] systemctl enable");
954
1001
  shell("systemctl", ["enable", serviceName], { sudo: true });
955
- console.log(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
956
- logFile(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
957
- shell("systemctl", ["stop", "neo4j"], { sudo: true, bestEffort: true });
958
- shell("systemctl", ["disable", "neo4j"], { sudo: true, bestEffort: true });
1002
+ // Task 800: skip stop+disable when a peer brand on this host still depends
1003
+ // on the apt `neo4j.service` (port 7687). Disabling it would kill the peer's
1004
+ // database Task 797 reproducer on Neo's laptop. The kept-active path is
1005
+ // mutually exclusive with the disable path: exactly one log line per install.
1006
+ const peerOnSystemUnit = peerBrandUsingSystemUnit();
1007
+ if (peerOnSystemUnit !== null) {
1008
+ const keptActiveMsg = ` [neo4j] system unit kept active — peer brand ${peerOnSystemUnit} depends on port ${DEFAULT_NEO4J_PORT} (Task 800)`;
1009
+ console.log(keptActiveMsg);
1010
+ logFile(keptActiveMsg);
1011
+ }
1012
+ else {
1013
+ console.log(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
1014
+ logFile(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
1015
+ shell("systemctl", ["stop", "neo4j"], { sudo: true });
1016
+ shell("systemctl", ["disable", "neo4j"], { sudo: true });
1017
+ }
959
1018
  console.log(` [neo4j] reset-failed ${serviceName} before start`);
960
1019
  logFile(` [neo4j] reset-failed ${serviceName} before start`);
961
1020
  shell("systemctl", ["reset-failed", serviceName], { sudo: true, bestEffort: true });
@@ -0,0 +1,39 @@
1
+ // Task 800 — pure peer-brand detection. Extracted from index.ts so the
2
+ // "should we leave the system neo4j.service alone?" decision can be unit-
3
+ // tested with concrete `.env` fixtures, no fs reads. Mirrors the apt-resolve
4
+ // / port-resolution pattern: inputs in, decision out, no I/O.
5
+ //
6
+ // The installer's `setupDedicatedNeo4j()` wraps this with the fs reads of
7
+ // `~/.<peer>/.env` and the `console.error` warning on read failure. This
8
+ // module owns only the parsing + matching rule.
9
+ /**
10
+ * Decide whether any peer brand on the same host depends on the apt-installed
11
+ * `neo4j.service` (port `defaultNeo4jPort`, conventionally 7687). Returns the
12
+ * first matching peer brand's hostname, or `null` if no peer pins that port.
13
+ *
14
+ * `peerEnvContents` is an iterable of `[hostname, envContent | null]` pairs.
15
+ * `null` content means the peer's `.env` is absent or unreadable — treated
16
+ * the same as "no evidence of dependency". The wrapper logs the read failure;
17
+ * this module stays decision-only.
18
+ *
19
+ * Matching rule: a peer pins the default port iff its `.env` contains a line
20
+ * matching `NEO4J_URI=bolt://localhost:<defaultNeo4jPort>` exactly. Any other
21
+ * scheme, host, or port → not a match. Self-references (peer hostname equal
22
+ * to the current install's hostname) are silently skipped so the caller can
23
+ * pass the full known-brand list without filtering first.
24
+ */
25
+ export function findPeerBrandOnDefaultNeo4jPort(args) {
26
+ const { currentBrandHostname, defaultNeo4jPort, peerEnvContents } = args;
27
+ const expected = `bolt://localhost:${defaultNeo4jPort}`;
28
+ for (const [hostname, content] of peerEnvContents) {
29
+ if (hostname === currentBrandHostname)
30
+ continue;
31
+ if (content === null)
32
+ continue;
33
+ const match = content.match(/^NEO4J_URI=(.+)$/m);
34
+ if (match && match[1].trim() === expected) {
35
+ return hostname;
36
+ }
37
+ }
38
+ return null;
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "1.0.744",
3
+ "version": "1.0.746",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Cypher rewrite pass that injects provenance stamps into operator-supplied
3
+ * write_neo4j_cypher (Task 797). Sibling to cypher-validate.ts: validate first,
4
+ * rewrite second, run third.
5
+ *
6
+ * Doctrine context: Task 673 wrapped writers stamp every CREATE node
7
+ * structurally via writeNodeWithEdges; raw cypher (Task 796) relied on
8
+ * prompt-internalised discipline. This module closes the asymmetry — the
9
+ * rewriter appends `SET <alias>.createdAt = $__autoStartTimestamp,
10
+ * <alias>.createdByAgent = $__autoAgent, <alias>.createdByTool =
11
+ * 'graph-cypher-write', <alias>.createdBySession = $__autoSession` to every
12
+ * CREATE/MERGE clause that introduces a new labeled node. Caller supplies
13
+ * the three `$__auto*` params at tx.run time.
14
+ *
15
+ * MERGE handling preserves the create-vs-match distinction. CREATE always
16
+ * stamps (every CREATE introduces a new node by definition). MERGE injects
17
+ * `ON CREATE SET` so an existing-node match path keeps the original writer's
18
+ * provenance intact — overwriting it would destroy forensic attribution.
19
+ *
20
+ * Node-only stamping. Edge aliases (`[r:R]`) are not auto-stamped — matches
21
+ * the wrapped writer's writeNodeWithEdges, which stamps the node only.
22
+ *
23
+ * Idempotent: re-applying the rewriter to its own output is a no-op. The
24
+ * marker that proves "already stamped" is `<alias>.createdBySession =
25
+ * $__autoSession` — the unique combination of property name + auto param.
26
+ *
27
+ * String literals and comments are redacted (chars replaced with spaces,
28
+ * offsets preserved) so a `CREATE (fake:Foo)` inside a comment or quoted
29
+ * string cannot mistakenly trip the rewriter.
30
+ */
31
+ export interface RewriteResult {
32
+ cypher: string;
33
+ /** Number of node aliases that received a fresh stamp injection. */
34
+ stampsAppended: number;
35
+ }
36
+ export declare function rewriteWithProvenanceStamps(cypher: string): RewriteResult;
37
+ //# sourceMappingURL=cypher-rewrite-stamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cypher-rewrite-stamp.d.ts","sourceRoot":"","sources":["../src/cypher-rewrite-stamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;CACxB;AAsPD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAmEzE"}
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ /**
3
+ * Cypher rewrite pass that injects provenance stamps into operator-supplied
4
+ * write_neo4j_cypher (Task 797). Sibling to cypher-validate.ts: validate first,
5
+ * rewrite second, run third.
6
+ *
7
+ * Doctrine context: Task 673 wrapped writers stamp every CREATE node
8
+ * structurally via writeNodeWithEdges; raw cypher (Task 796) relied on
9
+ * prompt-internalised discipline. This module closes the asymmetry — the
10
+ * rewriter appends `SET <alias>.createdAt = $__autoStartTimestamp,
11
+ * <alias>.createdByAgent = $__autoAgent, <alias>.createdByTool =
12
+ * 'graph-cypher-write', <alias>.createdBySession = $__autoSession` to every
13
+ * CREATE/MERGE clause that introduces a new labeled node. Caller supplies
14
+ * the three `$__auto*` params at tx.run time.
15
+ *
16
+ * MERGE handling preserves the create-vs-match distinction. CREATE always
17
+ * stamps (every CREATE introduces a new node by definition). MERGE injects
18
+ * `ON CREATE SET` so an existing-node match path keeps the original writer's
19
+ * provenance intact — overwriting it would destroy forensic attribution.
20
+ *
21
+ * Node-only stamping. Edge aliases (`[r:R]`) are not auto-stamped — matches
22
+ * the wrapped writer's writeNodeWithEdges, which stamps the node only.
23
+ *
24
+ * Idempotent: re-applying the rewriter to its own output is a no-op. The
25
+ * marker that proves "already stamped" is `<alias>.createdBySession =
26
+ * $__autoSession` — the unique combination of property name + auto param.
27
+ *
28
+ * String literals and comments are redacted (chars replaced with spaces,
29
+ * offsets preserved) so a `CREATE (fake:Foo)` inside a comment or quoted
30
+ * string cannot mistakenly trip the rewriter.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.rewriteWithProvenanceStamps = rewriteWithProvenanceStamps;
34
+ const AUTO_PARAM_AGENT = "$__autoAgent";
35
+ const AUTO_PARAM_SESSION = "$__autoSession";
36
+ const AUTO_PARAM_TIMESTAMP = "$__autoStartTimestamp";
37
+ const AUTO_TOOL_LITERAL = "'graph-cypher-write'";
38
+ const KEYWORD_TERMINATORS = new Set([
39
+ "SET",
40
+ "ON",
41
+ "MATCH",
42
+ "MERGE",
43
+ "CREATE",
44
+ "RETURN",
45
+ "WITH",
46
+ "WHERE",
47
+ "REMOVE",
48
+ "DELETE",
49
+ "DETACH",
50
+ "UNION",
51
+ "ORDER",
52
+ "LIMIT",
53
+ "SKIP",
54
+ "FOREACH",
55
+ "UNWIND",
56
+ "CALL",
57
+ "USE",
58
+ "LOAD",
59
+ "YIELD",
60
+ ]);
61
+ function buildRedacted(cypher) {
62
+ let out = "";
63
+ let i = 0;
64
+ while (i < cypher.length) {
65
+ const c = cypher[i];
66
+ if (c === "/" && cypher[i + 1] === "/") {
67
+ while (i < cypher.length && cypher[i] !== "\n") {
68
+ out += " ";
69
+ i++;
70
+ }
71
+ continue;
72
+ }
73
+ if (c === "/" && cypher[i + 1] === "*") {
74
+ out += " ";
75
+ i += 2;
76
+ while (i < cypher.length && !(cypher[i] === "*" && cypher[i + 1] === "/")) {
77
+ out += " ";
78
+ i++;
79
+ }
80
+ if (i < cypher.length) {
81
+ out += " ";
82
+ i += 2;
83
+ }
84
+ continue;
85
+ }
86
+ if (c === "'" || c === '"') {
87
+ const quote = c;
88
+ out += c;
89
+ i++;
90
+ while (i < cypher.length && cypher[i] !== quote) {
91
+ if (cypher[i] === "\\" && i + 1 < cypher.length) {
92
+ out += " ";
93
+ i += 2;
94
+ continue;
95
+ }
96
+ out += " ";
97
+ i++;
98
+ }
99
+ if (i < cypher.length) {
100
+ out += cypher[i];
101
+ i++;
102
+ }
103
+ continue;
104
+ }
105
+ out += c;
106
+ i++;
107
+ }
108
+ return out;
109
+ }
110
+ function findKeywordHits(redacted) {
111
+ const re = /\b(CREATE|MERGE)\b/gi;
112
+ const results = [];
113
+ for (const m of redacted.matchAll(re)) {
114
+ if (m.index === undefined)
115
+ continue;
116
+ const upper = m[1].toUpperCase();
117
+ // Skip `CREATE` that's part of `ON CREATE SET` (a MERGE sub-clause, not
118
+ // an introducing CREATE). Without this, the auto-stamp's own injected
119
+ // `ON CREATE SET` would register as a separate CREATE hit on the next
120
+ // rewriter pass, breaking idempotency by truncating the stamp window.
121
+ if (upper === "CREATE") {
122
+ const prefix = redacted.slice(Math.max(0, m.index - 8), m.index);
123
+ if (/\bON\s+$/i.test(prefix))
124
+ continue;
125
+ }
126
+ results.push({
127
+ kw: upper,
128
+ afterKw: m.index + m[0].length,
129
+ });
130
+ }
131
+ return results;
132
+ }
133
+ const TERMINATOR_PROBE = /^(\s*)(?:;|([A-Za-z]+))/;
134
+ /**
135
+ * Returns true iff position `i` in `redacted` is at a word boundary — i.e.,
136
+ * the immediately preceding character is not part of an identifier (letters,
137
+ * digits, underscore). Without this guard, TERMINATOR_PROBE matches the
138
+ * trailing letters of a multi-char identifier as a keyword (e.g. `Session`
139
+ * ending in `on` would match the `ON` keyword and truncate the scan
140
+ * mid-identifier).
141
+ */
142
+ function atWordBoundary(redacted, i) {
143
+ if (i === 0)
144
+ return true;
145
+ return !/[A-Za-z0-9_]/.test(redacted[i - 1]);
146
+ }
147
+ /**
148
+ * Returns the index just past the closing `)` of the CREATE/MERGE pattern,
149
+ * or null if no node pattern follows the keyword (e.g. CREATE INDEX, CREATE
150
+ * CONSTRAINT — neither would have reached the rewriter, but defensive).
151
+ */
152
+ function findPatternEnd(redacted, afterKw) {
153
+ let i = afterKw;
154
+ while (i < redacted.length && /\s/.test(redacted[i]))
155
+ i++;
156
+ if (i >= redacted.length || redacted[i] !== "(")
157
+ return null;
158
+ let parenDepth = 0;
159
+ let bracketDepth = 0;
160
+ while (i < redacted.length) {
161
+ if (parenDepth === 0 && bracketDepth === 0 && i > afterKw) {
162
+ const tmatch = redacted.slice(i).match(TERMINATOR_PROBE);
163
+ if (tmatch) {
164
+ const wsLen = tmatch[1].length;
165
+ const ch = redacted[i + wsLen];
166
+ if (ch === ";")
167
+ return i + wsLen;
168
+ const word = tmatch[2];
169
+ if (word &&
170
+ KEYWORD_TERMINATORS.has(word.toUpperCase()) &&
171
+ atWordBoundary(redacted, i + wsLen)) {
172
+ return i + wsLen;
173
+ }
174
+ }
175
+ }
176
+ const c = redacted[i];
177
+ if (c === "(")
178
+ parenDepth++;
179
+ else if (c === ")")
180
+ parenDepth--;
181
+ else if (c === "[")
182
+ bracketDepth++;
183
+ else if (c === "]")
184
+ bracketDepth--;
185
+ i++;
186
+ }
187
+ return i;
188
+ }
189
+ function extractLabeledAliases(redactedSlice) {
190
+ const out = [];
191
+ const seen = new Set();
192
+ const re = /\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*[A-Z]/g;
193
+ for (const m of redactedSlice.matchAll(re)) {
194
+ const alias = m[1];
195
+ if (!seen.has(alias)) {
196
+ seen.add(alias);
197
+ out.push(alias);
198
+ }
199
+ }
200
+ return out;
201
+ }
202
+ function aliasAlreadyStamped(slice, alias) {
203
+ // Per-pattern scope (review F1). The `slice` covers this CREATE/MERGE
204
+ // pattern's possible SET / ON CREATE SET clauses up to the next CREATE/
205
+ // MERGE boundary — scoping the marker check globally would mis-classify
206
+ // a re-introduction of the same alias name across two distinct patterns
207
+ // as "already stamped" (the first pattern's stamp would shadow the second).
208
+ const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
209
+ const marker = new RegExp(`\\b${escaped}\\.createdBySession\\s*=\\s*\\$__autoSession\\b`);
210
+ return marker.test(slice);
211
+ }
212
+ function buildAssignments(aliases) {
213
+ return aliases
214
+ .flatMap((a) => [
215
+ `${a}.createdAt = ${AUTO_PARAM_TIMESTAMP}`,
216
+ `${a}.createdByAgent = ${AUTO_PARAM_AGENT}`,
217
+ `${a}.createdByTool = ${AUTO_TOOL_LITERAL}`,
218
+ `${a}.createdBySession = ${AUTO_PARAM_SESSION}`,
219
+ ])
220
+ .join(", ");
221
+ }
222
+ const ON_CREATE_SET_PROBE = /^ON\s+CREATE\s+SET\s+/i;
223
+ const ON_MATCH_SET_PROBE = /^ON\s+MATCH\s+SET\b/i;
224
+ function findOnCreateSetEnd(redacted, fromIdx) {
225
+ let i = fromIdx;
226
+ while (i < redacted.length && /\s/.test(redacted[i]))
227
+ i++;
228
+ const m = redacted.slice(i).match(ON_CREATE_SET_PROBE);
229
+ if (!m)
230
+ return null;
231
+ let j = i + m[0].length;
232
+ let parenDepth = 0;
233
+ let bracketDepth = 0;
234
+ while (j < redacted.length) {
235
+ if (parenDepth === 0 && bracketDepth === 0) {
236
+ const tmatch = redacted.slice(j).match(TERMINATOR_PROBE);
237
+ if (tmatch) {
238
+ const wsLen = tmatch[1].length;
239
+ const ch = redacted[j + wsLen];
240
+ if (ch === ";")
241
+ return { end: j + wsLen };
242
+ const word = tmatch[2];
243
+ if (word &&
244
+ KEYWORD_TERMINATORS.has(word.toUpperCase()) &&
245
+ atWordBoundary(redacted, j + wsLen)) {
246
+ return { end: j + wsLen };
247
+ }
248
+ }
249
+ }
250
+ const c = redacted[j];
251
+ if (c === "(")
252
+ parenDepth++;
253
+ else if (c === ")")
254
+ parenDepth--;
255
+ else if (c === "[")
256
+ bracketDepth++;
257
+ else if (c === "]")
258
+ bracketDepth--;
259
+ j++;
260
+ }
261
+ return { end: j };
262
+ }
263
+ function findOnMatchSetStart(redacted, fromIdx) {
264
+ let i = fromIdx;
265
+ while (i < redacted.length && /\s/.test(redacted[i]))
266
+ i++;
267
+ if (ON_MATCH_SET_PROBE.test(redacted.slice(i))) {
268
+ return i;
269
+ }
270
+ return null;
271
+ }
272
+ function rewriteWithProvenanceStamps(cypher) {
273
+ const redacted = buildRedacted(cypher);
274
+ const hits = findKeywordHits(redacted);
275
+ const edits = [];
276
+ let stampsAppended = 0;
277
+ for (let h = 0; h < hits.length; h++) {
278
+ const { kw, afterKw } = hits[h];
279
+ const patternEnd = findPatternEnd(redacted, afterKw);
280
+ if (patternEnd === null)
281
+ continue;
282
+ let pStart = afterKw;
283
+ while (pStart < redacted.length && /\s/.test(redacted[pStart]))
284
+ pStart++;
285
+ const patternSlice = redacted.slice(pStart, patternEnd);
286
+ const labeled = extractLabeledAliases(patternSlice);
287
+ // Per-pattern stamp window: from this pattern's start through the next
288
+ // CREATE/MERGE boundary (or end of cypher). Scoping the "already stamped"
289
+ // check globally would mis-classify a re-introduction of the same alias
290
+ // name across two distinct patterns (review F1).
291
+ const nextHitAfterKw = h + 1 < hits.length ? hits[h + 1].afterKw : redacted.length;
292
+ const stampWindow = redacted.slice(pStart, nextHitAfterKw);
293
+ const aliases = labeled.filter((a) => !aliasAlreadyStamped(stampWindow, a));
294
+ if (aliases.length === 0)
295
+ continue;
296
+ if (kw === "CREATE") {
297
+ // Trailing space matters: when patternEnd lands at a clause keyword
298
+ // (e.g. ` WITH` immediately after `(n:Person)`), our `findPatternEnd`
299
+ // returns the keyword's position. Without a trailing space, the
300
+ // injected `$__autoSession` runs into the keyword and Cypher parses
301
+ // `$__autoSessionWITH` as one parameter name.
302
+ edits.push({
303
+ at: patternEnd,
304
+ insert: ` SET ${buildAssignments(aliases)} `,
305
+ });
306
+ stampsAppended += aliases.length;
307
+ continue;
308
+ }
309
+ const existingOnCreate = findOnCreateSetEnd(redacted, patternEnd);
310
+ if (existingOnCreate) {
311
+ edits.push({
312
+ at: existingOnCreate.end,
313
+ insert: `, ${buildAssignments(aliases)} `,
314
+ });
315
+ stampsAppended += aliases.length;
316
+ continue;
317
+ }
318
+ const onMatchStart = findOnMatchSetStart(redacted, patternEnd);
319
+ const insertAt = onMatchStart ?? patternEnd;
320
+ edits.push({
321
+ at: insertAt,
322
+ insert: ` ON CREATE SET ${buildAssignments(aliases)} `,
323
+ });
324
+ stampsAppended += aliases.length;
325
+ }
326
+ edits.sort((a, b) => b.at - a.at);
327
+ let result = cypher;
328
+ for (const edit of edits) {
329
+ result = result.slice(0, edit.at) + edit.insert + result.slice(edit.at);
330
+ }
331
+ return { cypher: result, stampsAppended };
332
+ }
333
+ //# sourceMappingURL=cypher-rewrite-stamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cypher-rewrite-stamp.js","sourceRoot":"","sources":["../src/cypher-rewrite-stamp.ts"],"names":[],"mappings":"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"}