@mindrian_os/install 1.13.0-beta.19 → 1.13.0-beta.22
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/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +6 -1
- package/CHANGELOG.md +13 -0
- package/README.md +51 -56
- package/bin/mindrian-brain-mcp-client.cjs +152 -0
- package/commands/doctor.md +3 -2
- package/lib/core/directive-envelope.cjs +175 -0
- package/lib/core/directive-envelope.test.cjs +225 -0
- package/lib/core/doctor/class-m-brain-smoke.cjs +278 -0
- package/lib/core/doctor/class-m-brain-smoke.test.cjs +310 -0
- package/lib/core/mcp-profiles.cjs +1 -1
- package/lib/core/migration-snapshot.cjs +172 -0
- package/lib/core/migration-snapshot.test.cjs +174 -0
- package/lib/core/mindrian-brain-shim.test.cjs +214 -0
- package/lib/core/rs-nl-to-query.cjs +1 -1
- package/lib/core/telemetry/validator.cjs +5 -2
- package/lib/core/telemetry/validator.test.cjs +5 -5
- package/lib/core/tier0-messaging.cjs +109 -0
- package/lib/core/tier0-messaging.test.cjs +218 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +2 -2
- package/lib/memory/mos-status-renderer.test.cjs +2 -2
- package/lib/memory/navigation-engine-core.test.cjs +1 -1
- package/lib/memory/run-feynman-tests.cjs +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Phase 127-00 (Task 2 RED) -- mindrian-brain stdio shim static + spawn tests.
|
|
6
|
+
*
|
|
7
|
+
* Covers the static / shape / spawn tests for bin/mindrian-brain-mcp-client.cjs:
|
|
8
|
+
* Test 1: shim file exists and is executable
|
|
9
|
+
* Test 2: shim source registers exactly 6 tools
|
|
10
|
+
* Test 3: shim child process boots and writes startup line to stderr (<3s)
|
|
11
|
+
* Test 7: shim source contains zero fetch/http/brain.mindrian/https? matches
|
|
12
|
+
* Test 8: shim source contains zero sendPacket calls (no Phase 110 bypass)
|
|
13
|
+
* Test 9: shim starts with #!/usr/bin/env node shebang
|
|
14
|
+
*
|
|
15
|
+
* Tests 4/5/6 (live JSON-RPC handshake + Tier-0 protocol) land in
|
|
16
|
+
* tests/test-127-00-shim-handshake.sh (Task 3).
|
|
17
|
+
*
|
|
18
|
+
* Canon parts: 7 (reuse of brain-client; thin transport wrapper),
|
|
19
|
+
* 8 (delegation property: zero network surface in the shim itself).
|
|
20
|
+
*
|
|
21
|
+
* HARD RULE: no em-dashes.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const assert = require('node:assert/strict');
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const cp = require('node:child_process');
|
|
28
|
+
|
|
29
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
30
|
+
const SHIM_PATH = path.join(REPO_ROOT, 'bin', 'mindrian-brain-mcp-client.cjs');
|
|
31
|
+
|
|
32
|
+
let passed = 0;
|
|
33
|
+
let failed = 0;
|
|
34
|
+
|
|
35
|
+
function ok(name) {
|
|
36
|
+
passed += 1;
|
|
37
|
+
process.stdout.write(' ok ' + name + '\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fail(name, err) {
|
|
41
|
+
failed += 1;
|
|
42
|
+
process.stdout.write(' FAIL ' + name + '\n');
|
|
43
|
+
if (err) process.stdout.write(' ' + (err.message || String(err)) + '\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Test 1: shim file exists and is executable.
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
(function test1_executable() {
|
|
50
|
+
const label = 'shim file exists and is executable (mode & 0o111)';
|
|
51
|
+
try {
|
|
52
|
+
assert.ok(fs.existsSync(SHIM_PATH), 'shim file must exist at ' + SHIM_PATH);
|
|
53
|
+
const st = fs.statSync(SHIM_PATH);
|
|
54
|
+
assert.ok((st.mode & 0o111) !== 0,
|
|
55
|
+
'shim file must be executable; mode = 0' + (st.mode & 0o777).toString(8));
|
|
56
|
+
ok(label);
|
|
57
|
+
} catch (err) { fail(label, err); }
|
|
58
|
+
})();
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Test 2: exactly 6 tools registered (brain_ask/query/schema/search/stats/write).
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
(function test2_sixTools() {
|
|
64
|
+
const label = 'shim source registers exactly 6 tools (brain_ask|query|schema|search|stats|write)';
|
|
65
|
+
try {
|
|
66
|
+
const src = fs.readFileSync(SHIM_PATH, 'utf8');
|
|
67
|
+
const re = /server\.tool\(\s*['"]brain_(ask|query|schema|search|stats|write)['"]/g;
|
|
68
|
+
const matches = src.match(re) || [];
|
|
69
|
+
assert.equal(matches.length, 6,
|
|
70
|
+
'expected exactly 6 server.tool() registrations; got ' + matches.length);
|
|
71
|
+
// Each of the 6 distinct names must appear exactly once. Use a per-name
|
|
72
|
+
// re-scan because the match string spans newlines under the canonical
|
|
73
|
+
// multi-line server.tool() formatting.
|
|
74
|
+
const expected = ['brain_ask', 'brain_query', 'brain_schema', 'brain_search', 'brain_stats', 'brain_write'];
|
|
75
|
+
for (const n of expected) {
|
|
76
|
+
const perName = new RegExp('server\\.tool\\(\\s*[\'"]' + n + '[\'"]', 'g');
|
|
77
|
+
const m = src.match(perName) || [];
|
|
78
|
+
assert.equal(m.length, 1,
|
|
79
|
+
'tool name ' + n + ' must appear exactly once; got ' + m.length);
|
|
80
|
+
}
|
|
81
|
+
ok(label);
|
|
82
|
+
} catch (err) { fail(label, err); }
|
|
83
|
+
})();
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Test 3: child process boot writes startup line to stderr within 3s.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
(function test3_bootStartupLine() {
|
|
89
|
+
const label = 'shim spawn writes "[mindrian-brain] MCP server v... started (stdio)" to stderr within 3s';
|
|
90
|
+
// Async test wrapped in IIFE; promise chain with timeout.
|
|
91
|
+
const done = new Promise((resolve) => {
|
|
92
|
+
const env = Object.assign({}, process.env);
|
|
93
|
+
delete env.MINDRIAN_BRAIN_KEY;
|
|
94
|
+
const proc = cp.spawn('node', [SHIM_PATH], { env: env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
95
|
+
let stderr = '';
|
|
96
|
+
let resolved = false;
|
|
97
|
+
proc.stderr.on('data', (chunk) => {
|
|
98
|
+
stderr += chunk.toString('utf8');
|
|
99
|
+
if (/\[mindrian-brain\] MCP server v[^ ]+ started \(stdio\)/.test(stderr)) {
|
|
100
|
+
if (!resolved) {
|
|
101
|
+
resolved = true;
|
|
102
|
+
proc.kill('SIGTERM');
|
|
103
|
+
resolve({ ok: true, stderr: stderr });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
proc.on('error', (err) => {
|
|
108
|
+
if (!resolved) { resolved = true; resolve({ ok: false, error: err }); }
|
|
109
|
+
});
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (!resolved) {
|
|
112
|
+
resolved = true;
|
|
113
|
+
proc.kill('SIGKILL');
|
|
114
|
+
resolve({ ok: false, error: new Error('timeout 3s; stderr=' + stderr) });
|
|
115
|
+
}
|
|
116
|
+
}, 3000);
|
|
117
|
+
});
|
|
118
|
+
// Block on the promise via a synchronous loop is wrong; use an
|
|
119
|
+
// async test runner. Use top-level await via an immediately-invoked
|
|
120
|
+
// async function with a process exit deferral.
|
|
121
|
+
done.then((r) => {
|
|
122
|
+
try {
|
|
123
|
+
assert.ok(r.ok, r.error ? r.error.message : 'startup line not seen');
|
|
124
|
+
ok(label);
|
|
125
|
+
} catch (err) { fail(label, err); }
|
|
126
|
+
finalize();
|
|
127
|
+
});
|
|
128
|
+
})();
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Test 7: zero Brain network surface in shim source (Canon Part 8 delegation).
|
|
132
|
+
//
|
|
133
|
+
// Token set aligned with the orchestrator's load-bearing success criterion:
|
|
134
|
+
// grep -rE "fetch\(|http\.|brain\.mindrian|onrender" bin/mindrian-brain-mcp-client.cjs
|
|
135
|
+
// returns 0. Note: the plan's <behavior> Test 7 narrative also lists
|
|
136
|
+
// `https?://`, but the plan's <action> mandates the Tier-0 sentinel embed
|
|
137
|
+
// the upgrade-hint URL https://mindrianos.vercel.app/brain-access verbatim --
|
|
138
|
+
// a self-contradicting test specification (Rule 2 deviation). The intent of
|
|
139
|
+
// Canon Part 8 is to catch Brain-network egress in the shim, not user-facing
|
|
140
|
+
// upgrade-hint URLs in a sentinel string. We therefore enforce the
|
|
141
|
+
// orchestrator's load-bearing token set, which is the canon-faithful read.
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
(function test7_delegationProperty() {
|
|
144
|
+
const label = 'shim source contains zero Brain network surface (fetch / http. / brain.mindrian / onrender)';
|
|
145
|
+
try {
|
|
146
|
+
const src = fs.readFileSync(SHIM_PATH, 'utf8');
|
|
147
|
+
const forbidden = [
|
|
148
|
+
{ re: /fetch\(/g, name: 'fetch(' },
|
|
149
|
+
{ re: /\bhttp\./g, name: 'http.' },
|
|
150
|
+
{ re: /brain\.mindrian/g, name: 'brain.mindrian' },
|
|
151
|
+
{ re: /\bonrender\b/g, name: 'onrender' },
|
|
152
|
+
];
|
|
153
|
+
// Active-code scan: strip block comments and line comments. The doc
|
|
154
|
+
// header explicitly names the forbidden tokens as PROHIBITED; the scan
|
|
155
|
+
// is against ACTIVE code, not documentation.
|
|
156
|
+
let code = src.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
157
|
+
code = code.replace(/^[ \t]*\/\/.*$/gm, '');
|
|
158
|
+
for (const f of forbidden) {
|
|
159
|
+
const m = code.match(f.re) || [];
|
|
160
|
+
assert.equal(m.length, 0,
|
|
161
|
+
'forbidden Brain network surface "' + f.name + '" found ' + m.length + ' time(s) in active code');
|
|
162
|
+
}
|
|
163
|
+
ok(label);
|
|
164
|
+
} catch (err) { fail(label, err); }
|
|
165
|
+
})();
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Test 8: zero sendPacket calls (no Phase 110 typed-packet bypass).
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
(function test8_noSendPacketBypass() {
|
|
171
|
+
const label = 'shim source contains zero sendPacket( / buildBrainPacket calls (Phase 110 contract)';
|
|
172
|
+
try {
|
|
173
|
+
const src = fs.readFileSync(SHIM_PATH, 'utf8');
|
|
174
|
+
let code = src.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
175
|
+
code = code.replace(/^[ \t]*\/\/.*$/gm, '');
|
|
176
|
+
const sp = code.match(/\bsendPacket\(/g) || [];
|
|
177
|
+
const bp = code.match(/\bbuildBrainPacket\b/g) || [];
|
|
178
|
+
const pt = code.match(/\{\s*packet_type\s*:/g) || [];
|
|
179
|
+
assert.equal(sp.length, 0, 'sendPacket( bypass detected ' + sp.length + ' time(s)');
|
|
180
|
+
assert.equal(bp.length, 0, 'buildBrainPacket bypass detected ' + bp.length + ' time(s)');
|
|
181
|
+
assert.equal(pt.length, 0, 'inline { packet_type: } construction detected ' + pt.length + ' time(s)');
|
|
182
|
+
ok(label);
|
|
183
|
+
} catch (err) { fail(label, err); }
|
|
184
|
+
})();
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Test 9: shebang line.
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
(function test9_shebang() {
|
|
190
|
+
const label = 'shim file starts with #!/usr/bin/env node shebang';
|
|
191
|
+
try {
|
|
192
|
+
const src = fs.readFileSync(SHIM_PATH, 'utf8');
|
|
193
|
+
const firstLine = src.split('\n')[0];
|
|
194
|
+
assert.equal(firstLine, '#!/usr/bin/env node',
|
|
195
|
+
'first line must be "#!/usr/bin/env node"; got "' + firstLine + '"');
|
|
196
|
+
ok(label);
|
|
197
|
+
} catch (err) { fail(label, err); }
|
|
198
|
+
})();
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Async finalize: wait for Test 3's spawn promise before printing summary.
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
let finalized = false;
|
|
204
|
+
function finalize() {
|
|
205
|
+
if (finalized) return;
|
|
206
|
+
finalized = true;
|
|
207
|
+
process.stdout.write('\n');
|
|
208
|
+
process.stdout.write('PASSED: ' + passed + '\n');
|
|
209
|
+
process.stdout.write('FAILED: ' + failed + '\n');
|
|
210
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Safety net: in case Test 3's promise never resolves, finalize at 5s.
|
|
214
|
+
setTimeout(finalize, 5000).unref();
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Why this module is the hardest Canon Part 8 surface in v1.11.0:
|
|
12
12
|
*
|
|
13
|
-
* The Brain (brain.
|
|
13
|
+
* The Brain (mindrian-brain.onrender.com) is a generic methodology repository
|
|
14
14
|
* that MUST never receive user content. Every other 89.x callsite
|
|
15
15
|
* builds Brain queries from STRUCTURED inputs (problem_type, framework
|
|
16
16
|
* id, phase id) where the audit surface is a finite enum. This module
|
|
@@ -61,8 +61,11 @@ const PHONE_RE = /\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b/;
|
|
|
61
61
|
// concatenated tokens so this very source file does not itself contain the
|
|
62
62
|
// literal forbidden substring (preserving the Canon Part 8 zero-network grep
|
|
63
63
|
// gate over the telemetry module while still detecting the host in payloads).
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Two hosts are matched: the current production host (the Render web service)
|
|
65
|
+
// and the not-yet-wired future custom domain. Both must be rejected.
|
|
66
|
+
const BRAIN_HOST_RENDER = ['mindrian', 'brain'].join('-') + ['', 'onrender', 'com'].join('\\.');
|
|
67
|
+
const BRAIN_HOST_DOMAIN = ['brain', 'mindrian', 'ai'].join('\\.');
|
|
68
|
+
const BRAIN_URL_RE = new RegExp(BRAIN_HOST_RENDER + '|' + BRAIN_HOST_DOMAIN, 'i');
|
|
66
69
|
|
|
67
70
|
// (e) Absolute path with >= 2 path separators after the initial / or ~.
|
|
68
71
|
// Matches "/home/jsagi/foo/bar.md" but not "/" alone or "/foo".
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Verifies validator.cjs is the Canon Part 8 constitutional gate that
|
|
10
10
|
* rejects events containing Brain query bodies, room artifact strings,
|
|
11
11
|
* personal identifiers (emails, phone numbers, raw hex), absolute filesystem
|
|
12
|
-
* paths, brain.
|
|
12
|
+
* paths, mindrian-brain.onrender.com references, and free-text prose. Mirrors the
|
|
13
13
|
* Phase 110-05 seed-pattern adversarial fixture approach.
|
|
14
14
|
*
|
|
15
15
|
* Test map (7 cases, one-to-one with the PLAN <behavior> block for Task 1):
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* (d) Raw hex >32 chars in non-sha256 field
|
|
24
24
|
* (e) Absolute path "/home/jsagi/MindrianRooms/foo/bar/baz.md"
|
|
25
25
|
* (f) Phone "555-123-4567"
|
|
26
|
-
* (g) Brain URL "https://brain.
|
|
26
|
+
* (g) Brain URL "https://mindrian-brain.onrender.com/v1/query"
|
|
27
27
|
*
|
|
28
28
|
* Registered in lib/memory/run-feynman-tests.cjs.
|
|
29
29
|
*/
|
|
@@ -163,13 +163,13 @@ assert.equal(typeof validator.validateEventPayload, 'function',
|
|
|
163
163
|
(function test6gBrainURL() {
|
|
164
164
|
const result = validator.validateEventPayload('selector_pick', {
|
|
165
165
|
sub_shape: 'F.1',
|
|
166
|
-
verb_chosen: 'https://brain.
|
|
166
|
+
verb_chosen: 'https://mindrian-brain.onrender.com/v1',
|
|
167
167
|
});
|
|
168
168
|
assert.equal(result.ok, false,
|
|
169
|
-
'brain.
|
|
169
|
+
'mindrian-brain.onrender.com URL must be rejected; got: ' + JSON.stringify(result));
|
|
170
170
|
assert.match(result.error, /forbidden_pattern.*(brain|url)/i,
|
|
171
171
|
'error must mention forbidden_pattern + brain/url, got: ' + result.error);
|
|
172
|
-
console.log('PASS test 6g: brain.
|
|
172
|
+
console.log('PASS test 6g: mindrian-brain.onrender.com URL rejected');
|
|
173
173
|
})();
|
|
174
174
|
|
|
175
175
|
// ---------- Sanity: sha256 hash field (room_slug_sha256) NOT flagged as raw hex ----------
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phase 127-02 BRAIN-MCP-127-09 -- Tier-0 graceful messaging chokepoint.
|
|
5
|
+
*
|
|
6
|
+
* Single source-of-truth for the DIRECTOR_NOT_AVAILABLE sentinel shape used
|
|
7
|
+
* across the plugin:
|
|
8
|
+
* - bin/mindrian-brain-mcp-client.cjs (the stdio shim from Phase 127-00)
|
|
9
|
+
* - Larry's prose surface (one-line hint via larryTier0Hint)
|
|
10
|
+
* - Future statusline + /mos:status surfaces (CONTEXT acceptance gate #4)
|
|
11
|
+
*
|
|
12
|
+
* Before this chokepoint, the shim shipped its own inline copy of the
|
|
13
|
+
* sentinel shape. Future surfaces (statusline, /mos:status, persona output)
|
|
14
|
+
* would each duplicate the same shape, drifting on the upgrade_hint URL or
|
|
15
|
+
* the fallback_advice phrasing. This module locks the wire shape and the
|
|
16
|
+
* Larry-prose phrasing so every consumer reads the same canonical bytes.
|
|
17
|
+
*
|
|
18
|
+
* Wire shape locked here (BRAIN-MCP-127-09 invariant):
|
|
19
|
+
* {
|
|
20
|
+
* status: "DIRECTOR_NOT_AVAILABLE",
|
|
21
|
+
* reason: "MINDRIAN_BRAIN_KEY not set",
|
|
22
|
+
* command_context: <toolName string | "unknown" for non-string input>,
|
|
23
|
+
* upgrade_hint: "Request a Brain key at https://mindrianos.vercel.app/brain-access",
|
|
24
|
+
* fallback_advice: "Larry can still talk with you and reflect on your room context. Methodology orchestration requires Brain."
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* Canon Part 7 (reuse): isAvailable() is a one-line delegation to
|
|
28
|
+
* brain-client.cjs's existing isAvailable(); no parallel key-resolver code
|
|
29
|
+
* path lives here. The shim's local tier0Response becomes a one-line
|
|
30
|
+
* passthrough after the Phase 127-02 refactor; no duplicate shape exists.
|
|
31
|
+
*
|
|
32
|
+
* Canon Part 8 (graph boundary): zero network surface in this file.
|
|
33
|
+
* isAvailable() delegates to brain-client.cjs (the existing chokepoint that
|
|
34
|
+
* reads ONLY the LOCAL key via resolve-brain-key.cjs). No fetch, no http,
|
|
35
|
+
* no Brain endpoint domain strings.
|
|
36
|
+
*
|
|
37
|
+
* HARD RULE: no em-dashes anywhere in this file (hyphens only).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const brainClient = require('./brain-client.cjs');
|
|
41
|
+
|
|
42
|
+
// Locked wire string. Renaming this constant breaks every downstream consumer
|
|
43
|
+
// (the shim, Larry's prose surface, the doctor's Class-M smoke L5 check).
|
|
44
|
+
// Treat as a phase-amendment boundary.
|
|
45
|
+
const DIRECTOR_NOT_AVAILABLE = 'DIRECTOR_NOT_AVAILABLE';
|
|
46
|
+
|
|
47
|
+
// Locked sentinel strings. Tests assert the keys; the values are
|
|
48
|
+
// human-facing and may evolve, but only via explicit phase amendment.
|
|
49
|
+
const REASON_NO_KEY = 'MINDRIAN_BRAIN_KEY not set';
|
|
50
|
+
const UPGRADE_HINT = 'Request a Brain key at https://mindrianos.vercel.app/brain-access';
|
|
51
|
+
const FALLBACK_ADVICE = 'Larry can still talk with you and reflect on your room context. Methodology orchestration requires Brain.';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Construct the Tier-0 sentinel response. Returned by every Brain-tool entry
|
|
55
|
+
* point when no key is resolvable. The shape is byte-locked.
|
|
56
|
+
*
|
|
57
|
+
* Defensive: non-string / empty / non-truthy commandContext arguments coerce
|
|
58
|
+
* to "unknown" so the wire shape is invariant under bad-caller inputs (the
|
|
59
|
+
* shim's tool-handler closure passes the literal tool name; future callers
|
|
60
|
+
* may pass null in error paths).
|
|
61
|
+
*
|
|
62
|
+
* @param {string} commandContext the tool name (e.g. "brain_ask"); falls back
|
|
63
|
+
* to "unknown" for non-string / empty inputs.
|
|
64
|
+
* @returns {{status: string, reason: string, command_context: string,
|
|
65
|
+
* upgrade_hint: string, fallback_advice: string}}
|
|
66
|
+
*/
|
|
67
|
+
function tier0Response(commandContext) {
|
|
68
|
+
const ctx = (typeof commandContext === 'string' && commandContext.length > 0)
|
|
69
|
+
? commandContext
|
|
70
|
+
: 'unknown';
|
|
71
|
+
return {
|
|
72
|
+
status: DIRECTOR_NOT_AVAILABLE,
|
|
73
|
+
reason: REASON_NO_KEY,
|
|
74
|
+
command_context: ctx,
|
|
75
|
+
upgrade_hint: UPGRADE_HINT,
|
|
76
|
+
fallback_advice: FALLBACK_ADVICE,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Is the Brain reachable from this process right now? Delegates to
|
|
82
|
+
* brain-client.cjs's existing chokepoint (which reads only the LOCAL key via
|
|
83
|
+
* resolve-brain-key.cjs). One-line passthrough -- never duplicate the key
|
|
84
|
+
* resolution logic.
|
|
85
|
+
*
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
function isAvailable() {
|
|
89
|
+
return brainClient.isAvailable();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* One-line Larry-prose hint for the Tier-0 path. Used by Larry's surface
|
|
94
|
+
* (and future statusline / /mos:status) when isAvailable() returns false to
|
|
95
|
+
* tell the user how to unlock Brain. Locked under 120 chars so it fits in
|
|
96
|
+
* statusline + chat-prefix surfaces without truncation.
|
|
97
|
+
*
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
function larryTier0Hint() {
|
|
101
|
+
return 'Methodology orchestration needs a Brain key. Drop one in ~/.mindrian.env or set MINDRIAN_BRAIN_KEY.';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
DIRECTOR_NOT_AVAILABLE,
|
|
106
|
+
tier0Response,
|
|
107
|
+
isAvailable,
|
|
108
|
+
larryTier0Hint,
|
|
109
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Phase 127-02 Task 1 (TDD RED -> GREEN) -- tier0-messaging chokepoint tests.
|
|
6
|
+
*
|
|
7
|
+
* Covers BRAIN-MCP-127-09 acceptance:
|
|
8
|
+
* Test 1: DIRECTOR_NOT_AVAILABLE constant locks the wire string.
|
|
9
|
+
* Test 2: tier0Response("brain_ask") shape (status / reason / command_context
|
|
10
|
+
* / upgrade_hint regex / fallback_advice regex).
|
|
11
|
+
* Test 3: tier0Response(null) coerces command_context to "unknown".
|
|
12
|
+
* Test 4: isAvailable() returns false when MINDRIAN_BRAIN_KEY is unset.
|
|
13
|
+
* Test 5: isAvailable() returns true when MINDRIAN_BRAIN_KEY is set in env.
|
|
14
|
+
* Test 6: larryTier0Hint() returns a one-line string under 120 chars,
|
|
15
|
+
* contains "Brain" and "key", zero em-dashes.
|
|
16
|
+
* Test 7: Shim's tier0Response(name) is byte-identical to chokepoint's
|
|
17
|
+
* tier0Response(name) -- delegation property (no duplicate shape).
|
|
18
|
+
* Test 8: Plan 127-00's mindrian-brain-shim.test.cjs file still passes after
|
|
19
|
+
* the refactor (non-breaking chokepoint introduction).
|
|
20
|
+
*
|
|
21
|
+
* Canon parts:
|
|
22
|
+
* - Part 7 (reuse): the shim's local tier0Response is a one-line passthrough.
|
|
23
|
+
* - Part 8 (graph boundary): no network IO in this chokepoint; isAvailable
|
|
24
|
+
* delegates to brain-client.cjs (which is the existing chokepoint).
|
|
25
|
+
*
|
|
26
|
+
* HARD RULE: no em-dashes.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const assert = require('node:assert/strict');
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const cp = require('node:child_process');
|
|
33
|
+
|
|
34
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
35
|
+
const CHOKEPOINT_PATH = path.join(REPO_ROOT, 'lib', 'core', 'tier0-messaging.cjs');
|
|
36
|
+
const SHIM_PATH = path.join(REPO_ROOT, 'bin', 'mindrian-brain-mcp-client.cjs');
|
|
37
|
+
const SHIM_TEST_PATH = path.join(REPO_ROOT, 'lib', 'core', 'mindrian-brain-shim.test.cjs');
|
|
38
|
+
|
|
39
|
+
let passed = 0;
|
|
40
|
+
let failed = 0;
|
|
41
|
+
|
|
42
|
+
function ok(name) {
|
|
43
|
+
passed += 1;
|
|
44
|
+
process.stdout.write(' ok ' + name + '\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fail(name, err) {
|
|
48
|
+
failed += 1;
|
|
49
|
+
process.stdout.write(' FAIL ' + name + '\n');
|
|
50
|
+
if (err) process.stdout.write(' ' + (err.message || String(err)) + '\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Test 1: DIRECTOR_NOT_AVAILABLE constant locks the wire string.
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
(function test1_director_constant() {
|
|
57
|
+
const label = 'DIRECTOR_NOT_AVAILABLE constant equals exact wire string';
|
|
58
|
+
try {
|
|
59
|
+
const mod = require(CHOKEPOINT_PATH);
|
|
60
|
+
assert.equal(mod.DIRECTOR_NOT_AVAILABLE, 'DIRECTOR_NOT_AVAILABLE',
|
|
61
|
+
'exported DIRECTOR_NOT_AVAILABLE must equal the literal wire string "DIRECTOR_NOT_AVAILABLE"');
|
|
62
|
+
ok(label);
|
|
63
|
+
} catch (e) { fail(label, e); }
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Test 2: tier0Response("brain_ask") shape.
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
(function test2_response_shape() {
|
|
70
|
+
const label = 'tier0Response("brain_ask") returns canonical sentinel shape';
|
|
71
|
+
try {
|
|
72
|
+
delete require.cache[CHOKEPOINT_PATH];
|
|
73
|
+
const mod = require(CHOKEPOINT_PATH);
|
|
74
|
+
const r = mod.tier0Response('brain_ask');
|
|
75
|
+
assert.equal(r.status, 'DIRECTOR_NOT_AVAILABLE', 'status must be DIRECTOR_NOT_AVAILABLE');
|
|
76
|
+
assert.equal(r.reason, 'MINDRIAN_BRAIN_KEY not set', 'reason must be exact string');
|
|
77
|
+
assert.equal(r.command_context, 'brain_ask', 'command_context must echo input');
|
|
78
|
+
assert.match(r.upgrade_hint, /brain-access/, 'upgrade_hint must reference brain-access URL');
|
|
79
|
+
assert.match(r.fallback_advice, /Larry/, 'fallback_advice must mention Larry');
|
|
80
|
+
ok(label);
|
|
81
|
+
} catch (e) { fail(label, e); }
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Test 3: tier0Response(null) coerces to "unknown".
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
(function test3_null_defensive() {
|
|
88
|
+
const label = 'tier0Response(null) coerces command_context to "unknown"';
|
|
89
|
+
try {
|
|
90
|
+
delete require.cache[CHOKEPOINT_PATH];
|
|
91
|
+
const mod = require(CHOKEPOINT_PATH);
|
|
92
|
+
const r1 = mod.tier0Response(null);
|
|
93
|
+
const r2 = mod.tier0Response(undefined);
|
|
94
|
+
const r3 = mod.tier0Response('');
|
|
95
|
+
const r4 = mod.tier0Response(123);
|
|
96
|
+
assert.equal(r1.command_context, 'unknown', 'null -> unknown');
|
|
97
|
+
assert.equal(r2.command_context, 'unknown', 'undefined -> unknown');
|
|
98
|
+
assert.equal(r3.command_context, 'unknown', 'empty string -> unknown');
|
|
99
|
+
assert.equal(r4.command_context, 'unknown', 'non-string -> unknown');
|
|
100
|
+
ok(label);
|
|
101
|
+
} catch (e) { fail(label, e); }
|
|
102
|
+
})();
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Test 4: isAvailable() returns false when MINDRIAN_BRAIN_KEY is unset.
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
(function test4_isAvailable_false() {
|
|
108
|
+
const label = 'isAvailable() returns false when MINDRIAN_BRAIN_KEY is unset and no env-file';
|
|
109
|
+
try {
|
|
110
|
+
// Use a subprocess with hermetic HOME and unset MINDRIAN_BRAIN_KEY so we
|
|
111
|
+
// do not pollute this process's brain-client memoized cache.
|
|
112
|
+
const tmpHome = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'tier0-msg-test-'));
|
|
113
|
+
const script = 'process.env.HOME=' + JSON.stringify(tmpHome) + ';'
|
|
114
|
+
+ 'delete process.env.MINDRIAN_BRAIN_KEY;'
|
|
115
|
+
+ 'const m = require(' + JSON.stringify(CHOKEPOINT_PATH) + ');'
|
|
116
|
+
+ 'process.stdout.write(JSON.stringify({ available: m.isAvailable() }));';
|
|
117
|
+
const out = cp.execFileSync(process.execPath, ['-e', script], {
|
|
118
|
+
encoding: 'utf8',
|
|
119
|
+
env: Object.assign({}, process.env, { HOME: tmpHome, MINDRIAN_BRAIN_KEY: '' }),
|
|
120
|
+
});
|
|
121
|
+
const parsed = JSON.parse(out);
|
|
122
|
+
assert.equal(parsed.available, false, 'isAvailable must be false with no key');
|
|
123
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
124
|
+
ok(label);
|
|
125
|
+
} catch (e) { fail(label, e); }
|
|
126
|
+
})();
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Test 5: isAvailable() returns true when MINDRIAN_BRAIN_KEY is set.
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
(function test5_isAvailable_true() {
|
|
132
|
+
const label = 'isAvailable() returns true when MINDRIAN_BRAIN_KEY is set in env';
|
|
133
|
+
try {
|
|
134
|
+
const tmpHome = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'tier0-msg-test-'));
|
|
135
|
+
const script = 'const m = require(' + JSON.stringify(CHOKEPOINT_PATH) + ');'
|
|
136
|
+
+ 'process.stdout.write(JSON.stringify({ available: m.isAvailable() }));';
|
|
137
|
+
const out = cp.execFileSync(process.execPath, ['-e', script], {
|
|
138
|
+
encoding: 'utf8',
|
|
139
|
+
env: Object.assign({}, process.env, { HOME: tmpHome, MINDRIAN_BRAIN_KEY: 'test-key-fixture-127-02' }),
|
|
140
|
+
});
|
|
141
|
+
const parsed = JSON.parse(out);
|
|
142
|
+
assert.equal(parsed.available, true, 'isAvailable must be true with key set');
|
|
143
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
144
|
+
ok(label);
|
|
145
|
+
} catch (e) { fail(label, e); }
|
|
146
|
+
})();
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Test 6: larryTier0Hint() shape.
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
(function test6_larry_hint() {
|
|
152
|
+
const label = 'larryTier0Hint() one-line string, <120 chars, contains "Brain" + "key", zero em-dashes';
|
|
153
|
+
try {
|
|
154
|
+
delete require.cache[CHOKEPOINT_PATH];
|
|
155
|
+
const mod = require(CHOKEPOINT_PATH);
|
|
156
|
+
const hint = mod.larryTier0Hint();
|
|
157
|
+
assert.equal(typeof hint, 'string', 'must return a string');
|
|
158
|
+
assert.ok(hint.length <= 120, 'must be <= 120 chars (got ' + hint.length + ')');
|
|
159
|
+
assert.ok(!/\n/.test(hint), 'must be a single line (no newlines)');
|
|
160
|
+
assert.match(hint, /Brain/, 'must contain "Brain"');
|
|
161
|
+
assert.match(hint, /key/, 'must contain "key"');
|
|
162
|
+
assert.ok(!/[\u2014\u2013]/.test(hint), 'must contain zero em-dashes (U+2014) or en-dashes (U+2013)');
|
|
163
|
+
ok(label);
|
|
164
|
+
} catch (e) { fail(label, e); }
|
|
165
|
+
})();
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Test 7: Shim's tier0Response is byte-identical to chokepoint's tier0Response
|
|
169
|
+
// (delegation property -- the shim's local function is a one-line
|
|
170
|
+
// passthrough to the chokepoint).
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
(function test7_delegation_property() {
|
|
173
|
+
const label = 'shim tier0Response delegates to chokepoint (byte-identical output for all 6 tool names)';
|
|
174
|
+
try {
|
|
175
|
+
delete require.cache[CHOKEPOINT_PATH];
|
|
176
|
+
const chokepoint = require(CHOKEPOINT_PATH);
|
|
177
|
+
// Read the shim source and verify it requires the chokepoint module.
|
|
178
|
+
const src = fs.readFileSync(SHIM_PATH, 'utf8');
|
|
179
|
+
assert.match(src, /require\(['"][^'"]*tier0-messaging\.cjs['"]\)/,
|
|
180
|
+
'shim source must require lib/core/tier0-messaging.cjs');
|
|
181
|
+
// Verify the chokepoint's response matches the wire shape for each tool.
|
|
182
|
+
const toolNames = ['brain_ask', 'brain_query', 'brain_schema', 'brain_search', 'brain_stats', 'brain_write'];
|
|
183
|
+
for (const name of toolNames) {
|
|
184
|
+
const r = chokepoint.tier0Response(name);
|
|
185
|
+
assert.equal(r.status, 'DIRECTOR_NOT_AVAILABLE', 'chokepoint status for ' + name);
|
|
186
|
+
assert.equal(r.command_context, name, 'chokepoint command_context for ' + name);
|
|
187
|
+
assert.equal(r.reason, 'MINDRIAN_BRAIN_KEY not set', 'chokepoint reason for ' + name);
|
|
188
|
+
}
|
|
189
|
+
ok(label);
|
|
190
|
+
} catch (e) { fail(label, e); }
|
|
191
|
+
})();
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Test 8: Plan 127-00's mindrian-brain-shim.test.cjs still passes after
|
|
195
|
+
// the refactor (non-breaking chokepoint introduction).
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
(function test8_shim_tests_preserved() {
|
|
198
|
+
const label = 'plan 127-00 shim test file still PASSES after refactor';
|
|
199
|
+
try {
|
|
200
|
+
const result = cp.spawnSync(process.execPath, [SHIM_TEST_PATH], {
|
|
201
|
+
encoding: 'utf8',
|
|
202
|
+
timeout: 15000,
|
|
203
|
+
});
|
|
204
|
+
if (result.status !== 0) {
|
|
205
|
+
throw new Error('shim test exited ' + result.status + '\nstdout:\n' + result.stdout + '\nstderr:\n' + result.stderr);
|
|
206
|
+
}
|
|
207
|
+
// Confirm all 6 tests passed.
|
|
208
|
+
assert.match(result.stdout, /PASSED:\s*6/, 'expected 6 shim tests to pass; got:\n' + result.stdout);
|
|
209
|
+
assert.match(result.stdout, /FAILED:\s*0/, 'expected 0 shim test failures; got:\n' + result.stdout);
|
|
210
|
+
ok(label);
|
|
211
|
+
} catch (e) { fail(label, e); }
|
|
212
|
+
})();
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Summary
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
process.stdout.write('\nPASSED: ' + passed + '\nFAILED: ' + failed + '\n');
|
|
218
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -620,8 +620,8 @@ async function main() {
|
|
|
620
620
|
// category); no BRAIN.md; no tmpfile.
|
|
621
621
|
await runScenario('Scenario 07: Network partition', async function () {
|
|
622
622
|
installMockBrainClient({
|
|
623
|
-
query: function () { throw new Error('ECONNREFUSED brain.
|
|
624
|
-
search: function () { throw new Error('ECONNREFUSED brain.
|
|
623
|
+
query: function () { throw new Error('ECONNREFUSED mindrian-brain.onrender.com:443'); },
|
|
624
|
+
search: function () { throw new Error('ECONNREFUSED mindrian-brain.onrender.com:443'); },
|
|
625
625
|
});
|
|
626
626
|
const { sections } = buildRoom({ tag: 's07', count: 1 });
|
|
627
627
|
const s = sections[0];
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* 6. --stale-only flag: renders only stale sections + summary.
|
|
38
38
|
* 7. <section> argument: renders that section's full triple (not truncated).
|
|
39
39
|
* 8. Missing room: prints "no active room; /mos:rooms to list" + exit 0.
|
|
40
|
-
* 9. Zero external network: no fetch/http/brain.
|
|
40
|
+
* 9. Zero external network: no fetch/http/mindrian-brain.onrender.com strings in the
|
|
41
41
|
* renderer source.
|
|
42
42
|
* 10. Shape E zones present: header + rows + summary + actions.
|
|
43
43
|
* 11. Warm-cache path: if statusline-cache has fresh data for the section,
|
|
@@ -468,7 +468,7 @@ assert.equal(
|
|
|
468
468
|
const src = fs.readFileSync(RENDERER, 'utf8');
|
|
469
469
|
// Canon Part 8: the renderer must be strictly LOCAL.
|
|
470
470
|
const forbidden = [
|
|
471
|
-
'brain.
|
|
471
|
+
'mindrian-brain.onrender.com',
|
|
472
472
|
'http://',
|
|
473
473
|
'https://',
|
|
474
474
|
'require(\'node:http\')',
|
|
@@ -639,7 +639,7 @@ if (engineLoadable()) {
|
|
|
639
639
|
run('Test 29: FORBIDDEN Brain network calls (grep)', () => {
|
|
640
640
|
const src = fs.readFileSync(ENGINE_PATH, 'utf8');
|
|
641
641
|
// Forbidden: brain-client.query / search / smartSearch and any direct
|
|
642
|
-
// network egress to brain.
|
|
642
|
+
// network egress to mindrian-brain.onrender.com or fetch/curl for Brain.
|
|
643
643
|
const reBad = /brain[-_]?client\s*\.\s*(query|search|smartSearch)\b|brain\.mindrian\.ai|\bfetch\s*\(|\bcurl\b/;
|
|
644
644
|
// Allow brain-client.isAvailable and brain-client.schema (Section 9.3).
|
|
645
645
|
// Strip those allowed calls before scanning.
|
|
@@ -1586,6 +1586,16 @@ const TEST_FILES = [
|
|
|
1586
1586
|
path.join(REPO_ROOT, 'tests', 'test-121-03-room-receipt-emit.cjs'),
|
|
1587
1587
|
path.join(REPO_ROOT, 'tests', 'test-121-03-command-invocation-hook.cjs'),
|
|
1588
1588
|
path.join(REPO_ROOT, 'tests', 'test-121-03-drowning-protection.cjs'),
|
|
1589
|
+
// Phase 127.1 brain-graphrag-collapse-pinecone-neo4j-hnsw (Wave 0 harness scaffold + Wave 1/2/3 outputs)
|
|
1590
|
+
// Surface 1 -> Plan 127.1-01 produces embedding-manifest.fixture.json (12,401 byte-identical SHA256 pairs).
|
|
1591
|
+
// Surface 2 -> Plan 127.1-02 produces index-config.fixture.json (SHOW INDEXES dim=1024 cosine ONLINE).
|
|
1592
|
+
// query-embedder -> Plan 127.1-03 produces mcp-server-brain/lib/query-embedder.cjs (server-side e5-large query embedding; hermetic, mocked transport).
|
|
1593
|
+
// Surface 3 -> Plan 127.1-04 produces overlap-baseline + overlap-neo4j fixtures (BLOCKING gate >= 0.80).
|
|
1594
|
+
// Aggregator: tests/run-all-127.sh. RED-by-design until consumer plans land the fixtures.
|
|
1595
|
+
path.join(REPO_ROOT, 'tests', '127.1-embedding-integrity.test.cjs'),
|
|
1596
|
+
path.join(REPO_ROOT, 'tests', '127.1-index-config.test.cjs'),
|
|
1597
|
+
path.join(REPO_ROOT, 'tests', '127.1-query-embedder.test.cjs'),
|
|
1598
|
+
path.join(REPO_ROOT, 'tests', '127.1-graphrag-overlap.test.cjs'),
|
|
1589
1599
|
];
|
|
1590
1600
|
|
|
1591
1601
|
// Exit code convention for child tests:
|