@rubytech/create-realagent 1.0.705 → 1.0.706
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/dist/__tests__/apt-resolve.test.js +179 -0
- package/dist/apt-resolve.js +73 -0
- package/dist/index.js +48 -46
- package/package.json +3 -3
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts +2 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +89 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +42 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +87 -0
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -0
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +99 -0
- package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +84 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +30 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +46 -5
- package/payload/platform/plugins/memory/PLUGIN.md +3 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +56 -6
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +92 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +51 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +222 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +16 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +38 -11
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +136 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +180 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +11 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +6 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +44 -22
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +94 -57
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +7 -5
- 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 +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +4 -0
- package/payload/server/chunk-PE76FPYP.js +12040 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{Checkbox-B2Lk8F4X.js → Checkbox-CjbS9JcG.js} +1 -1
- package/payload/server/public/assets/{admin-agtgi48Q.js → admin-Ce9DbUuu.js} +1 -1
- package/payload/server/public/assets/{data-B7nsyBTV.js → data-C-SxjLC9.js} +1 -1
- package/payload/server/public/assets/{file-DHWTu8LP.js → file-D4cbAAuo.js} +1 -1
- package/payload/server/public/assets/{graph-ChDwqqhJ.js → graph-D-Rqh0Md.js} +1 -1
- package/payload/server/public/assets/{house-CfjnRPO6.js → house-CYsVygEQ.js} +1 -1
- package/payload/server/public/assets/{jsx-runtime-81wg0w0Q.css → jsx-runtime-DPXE45W9.css} +1 -1
- package/payload/server/public/assets/{public-CE1kyVnz.js → public-BTOF98iO.js} +1 -1
- package/payload/server/public/assets/{share-2-CAd1beVT.js → share-2-B-sbkB36.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-LSAU68Eo.js → useVoiceRecorder-DLVFx3ms.js} +1 -1
- package/payload/server/public/assets/{x-B0xK3Aoq.js → x-BNidzSAn.js} +1 -1
- package/payload/server/public/data.html +6 -6
- package/payload/server/public/graph.html +7 -7
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +1 -1
- /package/payload/server/public/assets/{jsx-runtime-DhzH26q8.js → jsx-runtime-BUs3sHtV.js} +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { validateWrite } from "../schema-validator.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Fixture builders. These avoid hitting the real schema-*.md files so the
|
|
6
|
+
// validator's behaviour can be asserted independently of the markdown
|
|
7
|
+
// contents. The shape is byte-identical to what loadSchema() produces.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function makeSchema(opts) {
|
|
10
|
+
const markdownLabels = new Map();
|
|
11
|
+
for (const entry of opts.documented ?? []) {
|
|
12
|
+
markdownLabels.set(entry.label, {
|
|
13
|
+
label: entry.label,
|
|
14
|
+
required: entry.required,
|
|
15
|
+
sourceFile: entry.sourceFile ?? "schema-test.md",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const wrongToCorrect = new Map();
|
|
19
|
+
for (const [wrong, correct] of opts.synonyms ?? []) {
|
|
20
|
+
wrongToCorrect.set(wrong, { correct, sourceFile: "schema-test.md" });
|
|
21
|
+
}
|
|
22
|
+
return { markdownLabels, wrongToCorrect, sourceFiles: ["schema-test.md"] };
|
|
23
|
+
}
|
|
24
|
+
function makeLiveSource(opts) {
|
|
25
|
+
const live = new Set(opts.live);
|
|
26
|
+
const declared = new Set(opts.declared);
|
|
27
|
+
return {
|
|
28
|
+
recognisedLabels: () => new Set([...live, ...declared]),
|
|
29
|
+
liveLabels: () => new Set(live),
|
|
30
|
+
declaredLabels: () => new Set(declared),
|
|
31
|
+
liveReady: () => true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function captureEmits() {
|
|
35
|
+
const lines = [];
|
|
36
|
+
return { lines, emit: (line) => lines.push(line) };
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// (a) Live label not in markdown → accept, log markdown-skip
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
test("(a) live-only label, markdown-undocumented → accepted with markdown-undocumented log", () => {
|
|
42
|
+
// KnowledgeDocument is in db.labels() but has no markdown row.
|
|
43
|
+
const schema = makeSchema({});
|
|
44
|
+
const liveSource = makeLiveSource({
|
|
45
|
+
live: ["KnowledgeDocument", "Person"],
|
|
46
|
+
declared: [],
|
|
47
|
+
});
|
|
48
|
+
const { lines, emit } = captureEmits();
|
|
49
|
+
validateWrite({
|
|
50
|
+
labels: ["KnowledgeDocument"],
|
|
51
|
+
properties: { title: "x" },
|
|
52
|
+
accountId: "acct-1",
|
|
53
|
+
}, { schema, liveSource, emit });
|
|
54
|
+
const accepted = lines.find((l) => l.includes("outcome=accepted"));
|
|
55
|
+
assert.ok(accepted, `expected accepted log, got: ${lines.join(" | ")}`);
|
|
56
|
+
assert.match(accepted, /source=live/);
|
|
57
|
+
const skip = lines.find((l) => l.includes("markdown-undocumented"));
|
|
58
|
+
assert.ok(skip, `expected markdown-undocumented log, got: ${lines.join(" | ")}`);
|
|
59
|
+
assert.match(skip, /label=KnowledgeDocument/);
|
|
60
|
+
});
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// (b) Declared-but-not-live (fresh-install bootstrap) → accept
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
test("(b) declared-but-not-live LocalBusiness → accepted (bootstrap regression)", () => {
|
|
65
|
+
// The exact incident: fresh install, db.labels()=[AdminUser], schema.cypher
|
|
66
|
+
// declares LocalBusiness, agent writes LocalBusiness — must succeed.
|
|
67
|
+
const schema = makeSchema({});
|
|
68
|
+
const liveSource = makeLiveSource({
|
|
69
|
+
live: ["AdminUser"],
|
|
70
|
+
declared: ["AdminUser", "LocalBusiness", "Person"],
|
|
71
|
+
});
|
|
72
|
+
const { lines, emit } = captureEmits();
|
|
73
|
+
validateWrite({
|
|
74
|
+
labels: ["LocalBusiness"],
|
|
75
|
+
properties: { name: "Acme Plumbing" },
|
|
76
|
+
accountId: "acct-1",
|
|
77
|
+
}, { schema, liveSource, emit });
|
|
78
|
+
const accepted = lines.find((l) => l.includes("outcome=accepted"));
|
|
79
|
+
assert.ok(accepted);
|
|
80
|
+
assert.match(accepted, /source=declared/);
|
|
81
|
+
});
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// (c) Label absent from both → reject with structured error
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
test("(c) label absent from live and declared → reject with structured error", () => {
|
|
86
|
+
const schema = makeSchema({});
|
|
87
|
+
const liveSource = makeLiveSource({
|
|
88
|
+
live: ["Person", "LocalBusiness"],
|
|
89
|
+
declared: ["Person", "LocalBusiness", "AdminUser"],
|
|
90
|
+
});
|
|
91
|
+
const { lines, emit } = captureEmits();
|
|
92
|
+
assert.throws(() => validateWrite({
|
|
93
|
+
labels: ["Quokka"],
|
|
94
|
+
properties: {},
|
|
95
|
+
accountId: "acct-1",
|
|
96
|
+
}, { schema, liveSource, emit }), (err) => {
|
|
97
|
+
assert.match(err.message, /unknown Neo4j label/);
|
|
98
|
+
assert.match(err.message, /\[Quokka\]/);
|
|
99
|
+
assert.match(err.message, /Live db\.labels\(\): \[/);
|
|
100
|
+
assert.match(err.message, /Declared in schema\.cypher: \[/);
|
|
101
|
+
// Both source lists must include something so operators can grep.
|
|
102
|
+
assert.match(err.message, /Person/);
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
const rejected = lines.find((l) => l.includes("outcome=rejected"));
|
|
106
|
+
assert.ok(rejected);
|
|
107
|
+
assert.match(rejected, /source=neither/);
|
|
108
|
+
});
|
|
109
|
+
test("(c) typo gets Levenshtein 'did you mean?' suggestion", () => {
|
|
110
|
+
const schema = makeSchema({});
|
|
111
|
+
const liveSource = makeLiveSource({
|
|
112
|
+
live: ["Person", "LocalBusiness"],
|
|
113
|
+
declared: ["Person", "LocalBusiness"],
|
|
114
|
+
});
|
|
115
|
+
assert.throws(() => validateWrite(
|
|
116
|
+
// capital-I-as-l typo per the incident's exact failure mode
|
|
117
|
+
{
|
|
118
|
+
labels: ["LocaIBusiness"],
|
|
119
|
+
properties: {},
|
|
120
|
+
accountId: "acct-1",
|
|
121
|
+
}, { schema, liveSource }), (err) => {
|
|
122
|
+
assert.match(err.message, /Did you mean 'LocalBusiness'\?/);
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// (d) Required-property check fires only when label is documented
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
test("(d) required-property check runs for markdown-documented labels", () => {
|
|
130
|
+
const schema = makeSchema({
|
|
131
|
+
documented: [
|
|
132
|
+
{
|
|
133
|
+
label: "Person",
|
|
134
|
+
required: [{ kind: "simple", name: "givenName" }],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
const liveSource = makeLiveSource({
|
|
139
|
+
live: ["Person"],
|
|
140
|
+
declared: ["Person"],
|
|
141
|
+
});
|
|
142
|
+
assert.throws(() => validateWrite({
|
|
143
|
+
labels: ["Person"],
|
|
144
|
+
properties: { familyName: "Smith" }, // missing givenName
|
|
145
|
+
accountId: "acct-1",
|
|
146
|
+
}, { schema, liveSource }), /missing required property 'givenName' for label Person/);
|
|
147
|
+
});
|
|
148
|
+
test("(d) required-property check is SKIPPED for markdown-undocumented labels", () => {
|
|
149
|
+
// LocalBusiness is recognised (declared) but has no markdown row.
|
|
150
|
+
// Requiring properties that aren't documented would be impossible to
|
|
151
|
+
// express; the validator must accept the write.
|
|
152
|
+
const schema = makeSchema({});
|
|
153
|
+
const liveSource = makeLiveSource({
|
|
154
|
+
live: [],
|
|
155
|
+
declared: ["LocalBusiness"],
|
|
156
|
+
});
|
|
157
|
+
// No throw expected — properties are agent's responsibility for
|
|
158
|
+
// markdown-undocumented labels.
|
|
159
|
+
validateWrite({
|
|
160
|
+
labels: ["LocalBusiness"],
|
|
161
|
+
properties: {}, // intentionally empty
|
|
162
|
+
accountId: "acct-1",
|
|
163
|
+
}, { schema, liveSource });
|
|
164
|
+
});
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// (f) Synonym layer (wrongToCorrect) still rejects when triggered
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
test("(f) wrongToCorrect synonym still rejects firstName→givenName even on documented label", () => {
|
|
169
|
+
const schema = makeSchema({
|
|
170
|
+
documented: [
|
|
171
|
+
{
|
|
172
|
+
label: "Person",
|
|
173
|
+
required: [{ kind: "simple", name: "givenName" }],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
synonyms: [["firstName", "givenName"]],
|
|
177
|
+
});
|
|
178
|
+
const liveSource = makeLiveSource({
|
|
179
|
+
live: ["Person"],
|
|
180
|
+
declared: ["Person"],
|
|
181
|
+
});
|
|
182
|
+
assert.throws(() => validateWrite({
|
|
183
|
+
labels: ["Person"],
|
|
184
|
+
properties: { firstName: "Jane" },
|
|
185
|
+
accountId: "acct-1",
|
|
186
|
+
}, { schema, liveSource }), /property name 'firstName' is not Schema.org — use 'givenName'/);
|
|
187
|
+
});
|
|
188
|
+
test("(f) synonym layer also rejects on markdown-undocumented labels", () => {
|
|
189
|
+
// The synonym map is independent of label — firstName is wrong everywhere.
|
|
190
|
+
const schema = makeSchema({
|
|
191
|
+
synonyms: [["firstName", "givenName"]],
|
|
192
|
+
});
|
|
193
|
+
const liveSource = makeLiveSource({
|
|
194
|
+
live: ["LocalBusiness"],
|
|
195
|
+
declared: ["LocalBusiness"],
|
|
196
|
+
});
|
|
197
|
+
assert.throws(() => validateWrite({
|
|
198
|
+
labels: ["LocalBusiness"],
|
|
199
|
+
properties: { firstName: "x" },
|
|
200
|
+
accountId: "acct-1",
|
|
201
|
+
}, { schema, liveSource }), /property name 'firstName' is not Schema.org — use 'givenName'/);
|
|
202
|
+
});
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// accountId boundary
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
test("rejects missing accountId before consulting live source", () => {
|
|
207
|
+
const schema = makeSchema({});
|
|
208
|
+
const liveSource = makeLiveSource({
|
|
209
|
+
live: ["Person"],
|
|
210
|
+
declared: ["Person"],
|
|
211
|
+
});
|
|
212
|
+
assert.throws(() => validateWrite({ labels: ["Person"], properties: {} }, { schema, liveSource }), /accountId is required/);
|
|
213
|
+
});
|
|
214
|
+
test("rejects empty labels[] before consulting live source", () => {
|
|
215
|
+
const schema = makeSchema({});
|
|
216
|
+
const liveSource = makeLiveSource({
|
|
217
|
+
live: ["Person"],
|
|
218
|
+
declared: ["Person"],
|
|
219
|
+
});
|
|
220
|
+
assert.throws(() => validateWrite({ labels: [], properties: {}, accountId: "acct-1" }, { schema, liveSource }), /labels is required/);
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=schema-validator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validator.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/schema-validator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,22 @@ export interface GateArgs {
|
|
|
10
10
|
accountId: string;
|
|
11
11
|
labels: string[];
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Gate result.
|
|
15
|
+
*
|
|
16
|
+
* The `reason` discriminator is intentionally NOT extended for the personal-
|
|
17
|
+
* profile branch — `"no-local-business"` now covers two sub-cases:
|
|
18
|
+
* 1. AdminUser exists, no LocalBusiness, no personal-profile Person.
|
|
19
|
+
* 2. AdminUser exists, the operator is in personal mode, but the personal-
|
|
20
|
+
* profile Person was never written or its `role` is wrong.
|
|
21
|
+
*
|
|
22
|
+
* Callers that branch on `reason` cannot distinguish (1) from (2) — both
|
|
23
|
+
* arrive as `"no-local-business"`. Today's downstream consumers (the user-
|
|
24
|
+
* facing `message`, business-profile SKILL.md prose) do not need to. If a
|
|
25
|
+
* future caller needs per-sub-case recovery, add a separate `subReason` field
|
|
26
|
+
* rather than overloading this one (existing callers depend on the current
|
|
27
|
+
* value set; broadening it would be a silent contract break).
|
|
28
|
+
*/
|
|
13
29
|
export type GateResult = {
|
|
14
30
|
ok: true;
|
|
15
31
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-write-gate.d.ts","sourceRoot":"","sources":["../../src/lib/graph-write-gate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph-write-gate.d.ts","sourceRoot":"","sources":["../../src/lib/graph-write-gate.ts"],"names":[],"mappings":"AAuCA,MAAM,WAAW,WAAW;IAC1B,GAAG,CACD,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAalF,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAyC7E"}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
// Graph-write precondition gate (Task 685).
|
|
1
|
+
// Graph-write precondition gate (Task 685; extended Task 704).
|
|
2
2
|
//
|
|
3
3
|
// Refuses any `memory-write` / `memory-update` against a non-exempt node
|
|
4
|
-
// label until the account's AdminUser AND
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// path entirely
|
|
4
|
+
// label until the account's AdminUser exists AND at least one of
|
|
5
|
+
// (LocalBusiness, personal-profile Person) exists. The purpose is doctrine
|
|
6
|
+
// enforcement: SOUL is personality, factual user/business data lives in the
|
|
7
|
+
// graph, and the agent must establish operator identity before writing
|
|
8
|
+
// user-domain nodes (WebSite, Service, etc.). Prose rules drifted across past
|
|
9
|
+
// sessions; this gate removes the LLM from the decision path entirely
|
|
10
|
+
// (feedback_deterministic_means_remove_llm.md).
|
|
11
|
+
//
|
|
12
|
+
// Two satisfaction shapes (Task 704):
|
|
13
|
+
// 1. Business-owner mode — AdminUser + LocalBusiness (original Task 685 path)
|
|
14
|
+
// 2. Personal/employee mode — AdminUser + Person {role: "admin-personal"}
|
|
15
|
+
//
|
|
16
|
+
// The personal-profile bootstrap exists because Maxy's Solo licence target is
|
|
17
|
+
// individual operators with no business at all; forcing them through a
|
|
18
|
+
// business-profile flow created a silent failure where employees registered
|
|
19
|
+
// their employer as a `LocalBusiness`.
|
|
10
20
|
//
|
|
11
21
|
// The exempt set is the base case that breaks the chicken-and-egg: the agent
|
|
12
22
|
// must be able to write Person, LocalBusiness, and AdminUser *before* anything
|
|
@@ -14,16 +24,26 @@
|
|
|
14
24
|
// capture writes `Person`, so exempting it keeps that flow working even when
|
|
15
25
|
// the admin side has not run business-profile.
|
|
16
26
|
//
|
|
27
|
+
// Cypher safety: COUNT subqueries return 0 for missing matches, which is the
|
|
28
|
+
// correct semantic here — no MATCH-SET silent-no-op risk (sprint-learning #5
|
|
29
|
+
// applies to mutations, not COUNTs).
|
|
30
|
+
//
|
|
17
31
|
// Scope: this gate lives only in the MCP memory-write/update path the agent
|
|
18
32
|
// calls. Server-side writers (neo4j-store, workflow-execute, admin/index)
|
|
19
33
|
// are owned by Task 673's provenance doctrine — disjoint enforcement.
|
|
34
|
+
//
|
|
35
|
+
// TODO (perf, post-Phase-0): once Person counts grow into the thousands per
|
|
36
|
+
// account, add a composite index on Person(accountId, role) so the
|
|
37
|
+
// hasPersonalProfile subquery doesn't full-scan. Same scaling concern applies
|
|
38
|
+
// to LocalBusiness(accountId) and AdminUser(accountId). Not in 704 scope.
|
|
20
39
|
const EXEMPT_LABELS = new Set([
|
|
21
40
|
"Person",
|
|
22
41
|
"LocalBusiness",
|
|
23
42
|
"AdminUser",
|
|
24
43
|
]);
|
|
25
|
-
const NEXT_MOVE = "Run the business-profile skill to establish the admin user and " +
|
|
26
|
-
"
|
|
44
|
+
const NEXT_MOVE = "Run the business-profile skill to establish the admin user and business " +
|
|
45
|
+
"identity, or complete the personal-profile bootstrap (onboarding step 9 " +
|
|
46
|
+
"personal/employee mode), before writing this node.";
|
|
27
47
|
export async function checkGraphWriteGate(args) {
|
|
28
48
|
const { session, accountId, labels } = args;
|
|
29
49
|
if (labels.some((l) => EXEMPT_LABELS.has(l))) {
|
|
@@ -31,15 +51,22 @@ export async function checkGraphWriteGate(args) {
|
|
|
31
51
|
}
|
|
32
52
|
// COUNT subquery form — one query, no cartesian-product surprises,
|
|
33
53
|
// account-scoped so multi-account installs don't cross-contaminate.
|
|
54
|
+
// Three subqueries: AdminUser, LocalBusiness, Person-with-admin-role.
|
|
34
55
|
const result = await session.run(`RETURN
|
|
35
56
|
COUNT { MATCH (au:AdminUser {accountId: $accountId}) } > 0 AS hasAdmin,
|
|
36
|
-
COUNT { MATCH (lb:LocalBusiness {accountId: $accountId}) } > 0 AS hasBusiness
|
|
57
|
+
COUNT { MATCH (lb:LocalBusiness {accountId: $accountId}) } > 0 AS hasBusiness,
|
|
58
|
+
COUNT { MATCH (p:Person {accountId: $accountId, role: "admin-personal"}) } > 0 AS hasPersonalProfile`, { accountId });
|
|
37
59
|
const record = result.records[0];
|
|
38
60
|
const hasAdmin = Boolean(record?.get("hasAdmin"));
|
|
39
61
|
const hasBusiness = Boolean(record?.get("hasBusiness"));
|
|
40
|
-
|
|
62
|
+
const hasPersonalProfile = Boolean(record?.get("hasPersonalProfile"));
|
|
63
|
+
if (hasAdmin && (hasBusiness || hasPersonalProfile)) {
|
|
41
64
|
return { ok: true };
|
|
42
65
|
}
|
|
66
|
+
// Reason discriminator stays backward-compatible: `no-admin-user` covers the
|
|
67
|
+
// missing-AdminUser case, `no-local-business` covers "AdminUser exists but
|
|
68
|
+
// neither business nor personal profile does." Existing callers keying on
|
|
69
|
+
// these strings (review-detector rules, error UI) keep working.
|
|
43
70
|
const reason = hasAdmin
|
|
44
71
|
? "no-local-business"
|
|
45
72
|
: "no-admin-user";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-write-gate.js","sourceRoot":"","sources":["../../src/lib/graph-write-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"graph-write-gate.js","sourceRoot":"","sources":["../../src/lib/graph-write-gate.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,iEAAiE;AACjE,2EAA2E;AAC3E,4EAA4E;AAC5E,uEAAuE;AACvE,8EAA8E;AAC9E,sEAAsE;AACtE,gDAAgD;AAChD,EAAE;AACF,sCAAsC;AACtC,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,uEAAuE;AACvE,4EAA4E;AAC5E,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,0EAA0E;AAC1E,6EAA6E;AAC7E,+CAA+C;AAC/C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,qCAAqC;AACrC,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,sEAAsE;AACtE,EAAE;AACF,4EAA4E;AAC5E,mEAAmE;AACnE,8EAA8E;AAC9E,0EAA0E;AAmC1E,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IACjD,QAAQ;IACR,eAAe;IACf,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,SAAS,GACb,0EAA0E;IAC1E,0EAA0E;IAC1E,oDAAoD,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAc;IACtD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAE5C,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,mEAAmE;IACnE,oEAAoE;IACpE,sEAAsE;IACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;4GAGwG,EACxG,EAAE,SAAS,EAAE,CACd,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;IACxD,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAEtE,IAAI,QAAQ,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,0EAA0E;IAC1E,gEAAgE;IAChE,MAAM,MAAM,GAA0C,QAAQ;QAC5D,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,eAAe,CAAC;IAEpB,OAAO,CAAC,GAAG,CACT,uCAAuC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI;QAC9D,UAAU,MAAM,eAAe,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACpD,CAAC;IAEF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live schema source for the memory-write / memory-update validator (Task 736).
|
|
3
|
+
*
|
|
4
|
+
* Three sources, one decision rule:
|
|
5
|
+
*
|
|
6
|
+
* liveLabels — `db.labels()` snapshot from Neo4j (refreshed every 60s
|
|
7
|
+
* by SchemaCache from platform/lib/graph-mcp).
|
|
8
|
+
* declaredLabels — labels declared in `platform/neo4j/schema.cypher`
|
|
9
|
+
* (constraint + index FOR (alias:Label) forms).
|
|
10
|
+
* markdownLabels — labels with documented required-property groups in
|
|
11
|
+
* `platform/plugins/memory/references/schema-*.md`.
|
|
12
|
+
*
|
|
13
|
+
* recognised = liveLabels ∪ declaredLabels ← gates label existence
|
|
14
|
+
* markdown documented? → run required-property check, else skip
|
|
15
|
+
*
|
|
16
|
+
* Why a per-process cache rather than a shared one. The graph-mcp proxy and
|
|
17
|
+
* the memory plugin run as separate stdio MCP servers spawned by Claude Code
|
|
18
|
+
* (each with its own Neo4j driver). "In-process" in the Task 736 brief means
|
|
19
|
+
* "no per-decision tool round-trip" — *not* a shared cache instance, which
|
|
20
|
+
* would require IPC. Two SchemaCache instances polling the same Neo4j every
|
|
21
|
+
* 60s costs roughly two `db.labels()` calls per minute and avoids the IPC
|
|
22
|
+
* surface entirely.
|
|
23
|
+
*
|
|
24
|
+
* Why drift runs from the cache's emit hook. SchemaCache uses single-flight
|
|
25
|
+
* refresh — concurrent `refresh()` calls share an in-flight promise. A
|
|
26
|
+
* parallel timer could race against an in-progress refresh and read a stale
|
|
27
|
+
* snapshot during the comparison. The cache's own `emit()` callback fires
|
|
28
|
+
* synchronously after a successful refresh swap, so tapping it guarantees
|
|
29
|
+
* the snapshot we read is the one the refresh just produced.
|
|
30
|
+
*
|
|
31
|
+
* Bootstrap posture. Fresh installs have no nodes yet, so `db.labels()`
|
|
32
|
+
* returns []. The validator must still accept the very first
|
|
33
|
+
* `LocalBusiness` write because that label is *declared* in schema.cypher
|
|
34
|
+
* even when no node carries it. The recognised-set union resolves this
|
|
35
|
+
* directly; no fail-open branch needed for that case.
|
|
36
|
+
*
|
|
37
|
+
* db.labels() declared in cypher recognised?
|
|
38
|
+
* ───────── ────────────────── ───────────
|
|
39
|
+
* [] [LocalBusiness] LocalBusiness ✓ (bootstrap)
|
|
40
|
+
* [LocalBusiness] [LocalBusiness] LocalBusiness ✓ (steady state)
|
|
41
|
+
* [Person] [LocalBusiness] Person + LocalBusiness ✓
|
|
42
|
+
* [] [] {} reject all (Neo4j
|
|
43
|
+
* unreachable AND
|
|
44
|
+
* schema.cypher
|
|
45
|
+
* unreadable — both
|
|
46
|
+
* logged loudly)
|
|
47
|
+
*/
|
|
48
|
+
import { SchemaCache, neo4jSchemaFetcher, type SchemaFetcher } from "../../../../../lib/graph-mcp/dist/schema-cache.js";
|
|
49
|
+
export type DriftKind = "live-not-declared" | "declared-not-live" | "markdown-orphan";
|
|
50
|
+
export interface DriftEntry {
|
|
51
|
+
kind: DriftKind;
|
|
52
|
+
token: string;
|
|
53
|
+
}
|
|
54
|
+
export interface LiveSchemaSource {
|
|
55
|
+
/** live ∪ declared — used for label existence */
|
|
56
|
+
recognisedLabels(): Set<string>;
|
|
57
|
+
/** Just `db.labels()` — for drift comparison and observability */
|
|
58
|
+
liveLabels(): Set<string>;
|
|
59
|
+
/** Just schema.cypher constraint+index declarations */
|
|
60
|
+
declaredLabels(): Set<string>;
|
|
61
|
+
/** Whether the live snapshot has resolved at least once */
|
|
62
|
+
liveReady(): boolean;
|
|
63
|
+
}
|
|
64
|
+
interface BuildLiveSchemaSourceOptions {
|
|
65
|
+
schemaCypherPath: string;
|
|
66
|
+
/**
|
|
67
|
+
* Markdown-documented label set (the renamed `LoadedSchema.markdownLabels`).
|
|
68
|
+
* Used only for boot/refresh drift comparison — not for the per-write
|
|
69
|
+
* decision path.
|
|
70
|
+
*/
|
|
71
|
+
markdownLabels: Iterable<string>;
|
|
72
|
+
fetcher: SchemaFetcher;
|
|
73
|
+
/** Override default 60s refresh in tests. */
|
|
74
|
+
refreshIntervalMs?: number;
|
|
75
|
+
/** Test-injectable log emitter. Default: process.stderr. */
|
|
76
|
+
emit?: (line: string) => void;
|
|
77
|
+
}
|
|
78
|
+
export interface LiveSchemaRuntime {
|
|
79
|
+
source: LiveSchemaSource;
|
|
80
|
+
cache: SchemaCache;
|
|
81
|
+
/**
|
|
82
|
+
* Resolves once the boot refresh has completed (success or failure).
|
|
83
|
+
* Callers that want drift logged at boot should `await runtime.ready`.
|
|
84
|
+
* Validators do not block on this — they fail-open against an empty live
|
|
85
|
+
* snapshot, which (combined with the declared set) still accepts the
|
|
86
|
+
* bootstrap labels on a fresh install.
|
|
87
|
+
*/
|
|
88
|
+
ready: Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the live schema source plus its backing cache.
|
|
92
|
+
*
|
|
93
|
+
* The caller (memory MCP `index.ts`) owns lifecycle: it must `await
|
|
94
|
+
* runtime.ready` (or fire-and-forget) and call `runtime.cache.stop()` on
|
|
95
|
+
* shutdown. Tests pass a stub fetcher and a synthetic interval.
|
|
96
|
+
*/
|
|
97
|
+
export declare function buildLiveSchemaSource(options: BuildLiveSchemaSourceOptions): LiveSchemaRuntime;
|
|
98
|
+
/** Re-export the fetcher factory so index.ts can wire one up cleanly. */
|
|
99
|
+
export { neo4jSchemaFetcher };
|
|
100
|
+
interface DriftCheckInput {
|
|
101
|
+
live: ReadonlySet<string>;
|
|
102
|
+
declared: ReadonlySet<string>;
|
|
103
|
+
markdown: ReadonlySet<string>;
|
|
104
|
+
emit: (line: string) => void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Emit one log line per drifted token. Pure aside from the supplied emitter.
|
|
108
|
+
* Returns the drift entries for tests.
|
|
109
|
+
*
|
|
110
|
+
* Three drift kinds:
|
|
111
|
+
* live-not-declared : a label that nodes carry but schema.cypher does not
|
|
112
|
+
* declare a constraint/index for. Migration-drift
|
|
113
|
+
* signal — someone created a node with a fresh label
|
|
114
|
+
* without adding the canonical declaration.
|
|
115
|
+
* declared-not-live : declared in schema.cypher but no node carries it
|
|
116
|
+
* yet. Expected on fresh installs (bootstrap labels);
|
|
117
|
+
* suspicious on populated graphs (dead declaration).
|
|
118
|
+
* markdown-orphan : documented in schema-*.md but missing from both
|
|
119
|
+
* live and declared. Stale-doc signal — markdown
|
|
120
|
+
* describing a label that no longer exists in the
|
|
121
|
+
* graph or its constraint declarations.
|
|
122
|
+
*/
|
|
123
|
+
export declare function runDriftCheck(input: DriftCheckInput): DriftEntry[];
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the absolute path to `platform/neo4j/schema.cypher`.
|
|
126
|
+
*
|
|
127
|
+
* Layout (compiled and source paths are siblings, so the offset is the same):
|
|
128
|
+
* platform/plugins/memory/mcp/{src,dist}/lib/live-schema-source.{ts,js}
|
|
129
|
+
* platform/neo4j/schema.cypher
|
|
130
|
+
*
|
|
131
|
+
* From `import.meta.dirname` (mcp/{src,dist}/lib/) the relative path is
|
|
132
|
+
* `../../../../../neo4j/schema.cypher` — 5 hops to platform/, then down.
|
|
133
|
+
* `PLATFORM_ROOT` overrides for production where the layout may differ.
|
|
134
|
+
*/
|
|
135
|
+
export declare function defaultSchemaCypherPath(): string;
|
|
136
|
+
//# sourceMappingURL=live-schema-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-schema-source.d.ts","sourceRoot":"","sources":["../../src/lib/live-schema-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAIH,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,KAAK,aAAa,EACnB,MAAM,mDAAmD,CAAC;AAG3D,MAAM,MAAM,SAAS,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,gBAAgB,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,kEAAkE;IAClE,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,uDAAuD;IACvD,cAAc,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,2DAA2D;IAC3D,SAAS,IAAI,OAAO,CAAC;CACtB;AAED,UAAU,4BAA4B;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,EAAE,aAAa,CAAC;IACvB,6CAA6C;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB;;;;;;OAMG;IACH,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,iBAAiB,CAwCnB;AAED,yEAAyE;AACzE,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAM9B,UAAU,eAAe;IACvB,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,EAAE,CAyBlE;AAuBD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAKhD"}
|