@mindrian_os/install 1.13.0-beta.17 → 1.13.0-beta.19
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/CHANGELOG.md +26 -0
- package/commands/act.md +1 -0
- package/commands/admin.md +1 -0
- package/commands/analyze-needs.md +2 -0
- package/commands/analyze-systems.md +2 -0
- package/commands/analyze-timing.md +2 -0
- package/commands/auto-explore.md +2 -0
- package/commands/beautiful-question.md +2 -0
- package/commands/brain-derive.md +2 -0
- package/commands/build-knowledge.md +2 -0
- package/commands/build-thesis.md +2 -0
- package/commands/causal.md +2 -0
- package/commands/challenge-assumptions.md +2 -0
- package/commands/compare-ventures.md +2 -0
- package/commands/dashboard.md +2 -1
- package/commands/deep-grade.md +2 -0
- package/commands/diagnose.md +21 -1
- package/commands/diagnostics.md +14 -3
- package/commands/doctor.md +4 -1
- package/commands/dogfood-flush.md +92 -0
- package/commands/dominant-designs.md +2 -0
- package/commands/explain-decision.md +2 -0
- package/commands/explore-domains.md +2 -0
- package/commands/explore-futures.md +2 -0
- package/commands/explore-trends.md +2 -0
- package/commands/export.md +1 -0
- package/commands/feynman-timeline-refresh.md +2 -0
- package/commands/file-meeting.md +2 -0
- package/commands/find-analogies.md +1 -0
- package/commands/find-bottlenecks.md +2 -0
- package/commands/find-connections.md +2 -0
- package/commands/funding.md +1 -0
- package/commands/grade.md +2 -0
- package/commands/graph.md +1 -0
- package/commands/hat-briefing.md +1 -0
- package/commands/heal.md +22 -170
- package/commands/help.md +54 -334
- package/commands/hmi-status.md +23 -144
- package/commands/jtbd.md +1 -0
- package/commands/leadership.md +2 -0
- package/commands/lean-canvas.md +2 -0
- package/commands/macro-trends.md +2 -0
- package/commands/map-unknowns.md +2 -0
- package/commands/memory.md +1 -0
- package/commands/models.md +1 -0
- package/commands/mos-reason.md +2 -0
- package/commands/mos.md +139 -0
- package/commands/mullins.md +2 -0
- package/commands/mva-brief.md +2 -0
- package/commands/mva-option.md +2 -0
- package/commands/new-project.md +2 -0
- package/commands/onboard.md +20 -7
- package/commands/operator.md +1 -0
- package/commands/opportunities.md +1 -0
- package/commands/organize.md +22 -469
- package/commands/persona.md +1 -0
- package/commands/pipeline.md +2 -0
- package/commands/present.md +1 -0
- package/commands/publish.md +2 -0
- package/commands/query.md +24 -102
- package/commands/radar.md +2 -0
- package/commands/reanalyze.md +1 -0
- package/commands/research.md +2 -0
- package/commands/room.md +2 -0
- package/commands/rooms.md +1 -0
- package/commands/root-cause.md +2 -0
- package/commands/rs-experts.md +1 -0
- package/commands/rs-explain.md +1 -0
- package/commands/rs-fetch.md +1 -0
- package/commands/rs-thesis.md +1 -0
- package/commands/scenario-plan.md +2 -0
- package/commands/scheduled-tasks.md +1 -0
- package/commands/score-innovation.md +2 -0
- package/commands/scout.md +1 -0
- package/commands/setup.md +2 -0
- package/commands/snapshot.md +2 -0
- package/commands/speakers.md +1 -0
- package/commands/splash.md +5 -2
- package/commands/status.md +1 -0
- package/commands/structure-argument.md +2 -0
- package/commands/suggest-next.md +2 -0
- package/commands/systems-thinking.md +2 -0
- package/commands/think-hats.md +2 -0
- package/commands/update.md +2 -0
- package/commands/user-needs.md +2 -0
- package/commands/validate.md +2 -0
- package/commands/value-proposition.md +2 -0
- package/commands/vault.md +2 -0
- package/commands/visualize.md +24 -29
- package/commands/whitespace.md +2 -1
- package/commands/wiki.md +1 -0
- package/hooks/hooks.json +22 -88
- package/lib/agents/auto-explore-agent.cjs +82 -0
- package/lib/core/breakthrough/canary.cjs +134 -0
- package/lib/core/breakthrough/canary.test.cjs +136 -0
- package/lib/core/breakthrough/detectors.cjs +359 -0
- package/lib/core/breakthrough/detectors.test.cjs +333 -0
- package/lib/core/breakthrough/ethics-fence.cjs +127 -0
- package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
- package/lib/core/breakthrough/resurfacing.cjs +150 -0
- package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
- package/lib/core/breakthrough/review-queue.cjs +154 -0
- package/lib/core/breakthrough/review-queue.test.cjs +160 -0
- package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
- package/lib/core/breakthrough/scanner.cjs +426 -0
- package/lib/core/breakthrough/scanner.test.cjs +267 -0
- package/lib/core/breakthrough/schema.cjs +164 -0
- package/lib/core/breakthrough/schema.test.cjs +256 -0
- package/lib/core/breakthrough/scoring.cjs +293 -0
- package/lib/core/breakthrough/scoring.test.cjs +423 -0
- package/lib/core/breakthrough/verb-dispatch.cjs +221 -0
- package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
- package/lib/core/breakthrough/voice-scaffold.cjs +247 -0
- package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
- package/lib/core/first-touch-version-stamper.cjs +113 -0
- package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
- package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
- package/lib/core/llm-name-suggester.cjs +194 -0
- package/lib/core/llm-name-suggester.test.cjs +132 -0
- package/lib/core/mva-orchestrator.cjs +41 -0
- package/lib/core/mva-telemetry.cjs +31 -143
- package/lib/core/navigation/edges.cjs +35 -0
- package/lib/core/navigation/memory-events.cjs +126 -0
- package/lib/core/room-auto-create.cjs +318 -0
- package/lib/core/room-auto-create.test.cjs +198 -0
- package/lib/core/room-discard-cascade.cjs +225 -0
- package/lib/core/room-discard-cascade.test.cjs +135 -0
- package/lib/core/room-name-validator.cjs +132 -0
- package/lib/core/room-name-validator.test.cjs +156 -0
- package/lib/core/room-naming-selector.cjs +357 -0
- package/lib/core/room-naming-selector.test.cjs +277 -0
- package/lib/core/room-receipt-emit.cjs +63 -0
- package/lib/core/room-skeleton-scaffold.cjs +315 -0
- package/lib/core/room-skeleton-scaffold.test.cjs +291 -0
- package/lib/core/stale-copy-scanner.cjs +190 -0
- package/lib/core/state-aware-router.cjs +78 -0
- package/lib/core/telemetry/schema.cjs +168 -0
- package/lib/core/telemetry/schema.test.cjs +124 -0
- package/lib/core/telemetry/validator.cjs +197 -0
- package/lib/core/telemetry/validator.test.cjs +188 -0
- package/lib/core/telemetry/writer.cjs +141 -0
- package/lib/core/telemetry/writer.test.cjs +331 -0
- package/lib/core/terminal-capability.cjs +88 -0
- package/lib/core/venture-shape-nudge.cjs +163 -0
- package/lib/core/venture-shape-nudge.test.cjs +161 -0
- package/lib/core/visual-ops.cjs +70 -2
- package/lib/hmi/selector-dispatcher.cjs +90 -1
- package/lib/hmi/shape-f7-breakthrough-renderer.cjs +222 -0
- package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
- package/lib/memory/body-shape-coverage.test.cjs +268 -0
- package/lib/memory/doctor-deprecation-surface.test.cjs +185 -0
- package/lib/memory/first-touch-version.test.cjs +198 -0
- package/lib/memory/help-coverage.test.cjs +108 -0
- package/lib/memory/help-renderer.test.cjs +145 -0
- package/lib/memory/palette-consistency.test.cjs +127 -0
- package/lib/memory/pending-tension-store.cjs +80 -0
- package/lib/memory/render-v2-disposition.test.cjs +199 -0
- package/lib/memory/run-feynman-tests.cjs +213 -0
- package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
- package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
- package/lib/memory/soft-alias.test.cjs +144 -0
- package/lib/memory/stale-copy-scanner.test.cjs +291 -0
- package/lib/memory/state-aware-router.test.cjs +90 -0
- package/lib/memory/statusline-two-row.test.cjs +338 -0
- package/lib/memory/terminal-capability.test.cjs +155 -0
- package/lib/render/ROOM.md +74 -22
- package/lib/sessionstart/budget-compressor.cjs +130 -0
- package/lib/sessionstart/contributor-interface.cjs +134 -0
- package/lib/sessionstart/contributor-isolator.cjs +128 -0
- package/lib/sessionstart/precedence-ladder.cjs +47 -0
- package/lib/statusline/governing-thought-truncator.cjs +45 -0
- package/lib/statusline/two-row-renderer.cjs +186 -0
- package/lib/statusline/version-resolver.cjs +81 -0
- package/package.json +1 -1
- package/references/visual/ROOM.md +55 -0
- package/references/visual/palette.json +54 -0
- package/skills/larry-personality/SKILL.md +34 -0
- package/skills/ui-system/SKILL.md +109 -1
- package/skills/ui-system/rules/dual-palette.md +156 -0
- package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
- package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 119-01 -- LLM-suggested room name resolver.
|
|
4
|
+
*
|
|
5
|
+
* One-shot Haiku 4.5 call seeded with LOCAL Phase 117 auto-explore finding +
|
|
6
|
+
* Phase 118 MVA brief sentence. Returns a venture-shaped slug suggestion (e.g.
|
|
7
|
+
* 'acme-robotics', 'quantum-imaging', 'biotech-translation').
|
|
8
|
+
*
|
|
9
|
+
* Canon Part 8 invariant: this module MUST NOT invoke any Brain endpoint.
|
|
10
|
+
* The Brain repository holds GENERIC methodology -- framework chaining rules,
|
|
11
|
+
* phase progressions -- never user-specific content. A LLM call seeded with
|
|
12
|
+
* the user's auto-explore output + MVA brief sentence is purely LOCAL: the
|
|
13
|
+
* model sees the user's content directly via the local Anthropic API path,
|
|
14
|
+
* and the model's response is consumed locally.
|
|
15
|
+
*
|
|
16
|
+
* Canon Part 8 NOTE (REVISION 2026-05-16 Warning 5 fix): this module's fetch
|
|
17
|
+
* carries user content (the auto_explore_finding summary + the mva_brief_sentence)
|
|
18
|
+
* to api.anthropic.com. Per the standard plugin LLM usage pattern (precedent:
|
|
19
|
+
* lib/core/mva-classifier.cjs, lib/agents/mva/*.cjs, lib/chat/fabric-chat.cjs),
|
|
20
|
+
* this is acceptable: the Anthropic API is the LOCAL LLM transport for the
|
|
21
|
+
* plugin. The Canon Part 8 boundary covers ONLY the Mindrian-owned Brain MCP
|
|
22
|
+
* host (the Mindrian-owned methodology repository that must never receive user
|
|
23
|
+
* data) -- NOT api.anthropic.com (the Anthropic LLM transport). The two are
|
|
24
|
+
* distinct: Brain is a Mindrian-owned methodology repository that must never
|
|
25
|
+
* receive user data; api.anthropic.com is a stateless LLM transport.
|
|
26
|
+
*
|
|
27
|
+
* Tripwire: scaffold harness Gate 3 + Test 9 grep this module for any Brain-host
|
|
28
|
+
* substring AND any brain-client require AND any fetch to a brain.* URL; all
|
|
29
|
+
* three must return 0. This module body therefore avoids the literal Brain-host
|
|
30
|
+
* hostname string entirely (the scaffold harness uses literal-grep on the
|
|
31
|
+
* forbidden substring).
|
|
32
|
+
*
|
|
33
|
+
* Cost: ~$0.0005 per first-MVA completion (Haiku 4.5 input ~800 tokens,
|
|
34
|
+
* output ~10 tokens). See CONTEXT.md Architectural Decisions item 1.
|
|
35
|
+
*
|
|
36
|
+
* Em-dash discipline: uses `--` never the U+2014 character per memory
|
|
37
|
+
* feedback_no_emdashes.md.
|
|
38
|
+
*
|
|
39
|
+
* Graceful degradation: on LLM error, returns {ok: false, suggested_name:
|
|
40
|
+
* 'untitled', ...} so the F.1 selector still renders correctly with the
|
|
41
|
+
* fallback label `[name this room: untitled]`.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const FALLBACK_SUGGESTION = 'untitled';
|
|
45
|
+
|
|
46
|
+
// Phase 119-01 REVISION 2026-05-16 (Blocker 2 Option A): HAIKU_MODEL_ID is the
|
|
47
|
+
// project-wide source-of-truth constant. The plan's REVISION text says to
|
|
48
|
+
// import HAIKU_MODEL from lib/core/mva-classifier.cjs::HAIKU_MODEL, BUT
|
|
49
|
+
// inspection of that module's module.exports (verified at lib/core/mva-classifier.cjs
|
|
50
|
+
// line 359-370) shows HAIKU_MODEL is a module-internal const NOT exported. Per
|
|
51
|
+
// Rule 1 deviation, the constant is inlined here with provenance pointing to
|
|
52
|
+
// the source-of-truth declaration at lib/core/mva-classifier.cjs:53. If a
|
|
53
|
+
// future phase exports HAIKU_MODEL, replace the inline literal with a require.
|
|
54
|
+
const HAIKU_MODEL_ID = 'claude-haiku-4-5';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* suggestRoomName({auto_explore_finding, mva_brief_sentence, opts})
|
|
58
|
+
* @returns {Promise<{ok, suggested_name, model_used, latency_ms, error_short?}>}
|
|
59
|
+
*/
|
|
60
|
+
async function suggestRoomName(args) {
|
|
61
|
+
const opts = (args && typeof args === 'object') ? args : {};
|
|
62
|
+
const autoExploreFinding = opts.auto_explore_finding || null;
|
|
63
|
+
const mvaBriefSentence = (typeof opts.mva_brief_sentence === 'string') ? opts.mva_brief_sentence : '';
|
|
64
|
+
const llmClient = opts.llmClient || null;
|
|
65
|
+
|
|
66
|
+
const t0 = Date.now();
|
|
67
|
+
try {
|
|
68
|
+
const client = llmClient || _resolveProductionLlmClient();
|
|
69
|
+
const prompt = _buildLocalPrompt(autoExploreFinding, mvaBriefSentence);
|
|
70
|
+
const response = await client.complete({
|
|
71
|
+
model: HAIKU_MODEL_ID,
|
|
72
|
+
messages: [{ role: 'user', content: prompt }],
|
|
73
|
+
max_tokens: 20,
|
|
74
|
+
});
|
|
75
|
+
const raw = (response && typeof response.content === 'string') ? response.content : '';
|
|
76
|
+
const normalized = _normalizeSlug(raw);
|
|
77
|
+
const latency_ms = Date.now() - t0;
|
|
78
|
+
if (!normalized || normalized.length === 0) {
|
|
79
|
+
return { ok: false, suggested_name: FALLBACK_SUGGESTION, model_used: HAIKU_MODEL_ID, latency_ms, error_short: 'empty_response' };
|
|
80
|
+
}
|
|
81
|
+
return { ok: true, suggested_name: normalized, model_used: HAIKU_MODEL_ID, latency_ms };
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const latency_ms = Date.now() - t0;
|
|
84
|
+
const error_short = String(err && err.message || err).slice(0, 60);
|
|
85
|
+
return { ok: false, suggested_name: FALLBACK_SUGGESTION, model_used: HAIKU_MODEL_ID, latency_ms, error_short };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function _buildLocalPrompt(autoExploreFinding, mvaBriefSentence) {
|
|
90
|
+
// Build a short prompt from LOCAL signals ONLY. The findings array carries
|
|
91
|
+
// the top whitespace + reverse-salient + cross-domain hits from Phase 117;
|
|
92
|
+
// the MVA brief sentence is the user's first conversational turn (already
|
|
93
|
+
// local). NEVER include any Brain-derived suggestion in this prompt.
|
|
94
|
+
const findingsSummary = _summarizeFindings(autoExploreFinding);
|
|
95
|
+
return [
|
|
96
|
+
'You are naming a venture. Suggest a 2-3 word kebab-case slug that captures',
|
|
97
|
+
'the core domain. Return ONLY the slug -- no prose, no quotes, no markdown.',
|
|
98
|
+
'',
|
|
99
|
+
'Brief: ' + (mvaBriefSentence || '(no brief)'),
|
|
100
|
+
'Findings: ' + findingsSummary,
|
|
101
|
+
'',
|
|
102
|
+
'Slug:',
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _summarizeFindings(autoExploreFinding) {
|
|
107
|
+
if (!autoExploreFinding || typeof autoExploreFinding !== 'object') return '(none)';
|
|
108
|
+
const findings = Array.isArray(autoExploreFinding.findings) ? autoExploreFinding.findings : [];
|
|
109
|
+
if (findings.length === 0) return '(empty)';
|
|
110
|
+
return findings.slice(0, 3).map(function (f) {
|
|
111
|
+
const sp = (typeof f.source_pipeline === 'string') ? f.source_pipeline : 'unknown';
|
|
112
|
+
const hsi = (typeof f.hsi_score === 'number') ? f.hsi_score.toFixed(2) : '?';
|
|
113
|
+
return sp + ':' + hsi;
|
|
114
|
+
}).join(', ');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _normalizeSlug(raw) {
|
|
118
|
+
if (typeof raw !== 'string') return '';
|
|
119
|
+
let slug = raw.trim().toLowerCase();
|
|
120
|
+
// Collapse whitespace to single hyphen; drop everything that's not [a-z0-9-].
|
|
121
|
+
slug = slug.replace(/\s+/g, '-');
|
|
122
|
+
slug = slug.replace(/[^a-z0-9-]/g, '');
|
|
123
|
+
slug = slug.replace(/-{2,}/g, '-');
|
|
124
|
+
slug = slug.replace(/^-+|-+$/g, '');
|
|
125
|
+
return slug;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Phase 119-01 REVISION 2026-05-16 (Blocker 2 Option A): mva-agent-contract.cjs
|
|
129
|
+
// exports {runAgent, validateAgentResult, AGENT_RESULT_SHAPE} ONLY -- no
|
|
130
|
+
// createLlmClient factory exists. The project-wide LLM-call idiom is direct
|
|
131
|
+
// fetch to https://api.anthropic.com/v1/messages with x-api-key header
|
|
132
|
+
// (precedent: lib/core/mva-classifier.cjs::_callHaiku same Haiku 4.5 model,
|
|
133
|
+
// same anthropic-version header pattern, same AbortController timeout).
|
|
134
|
+
//
|
|
135
|
+
// This module mirrors that precedent verbatim. No @anthropic-ai/sdk dependency
|
|
136
|
+
// added. Canon Part 8 invariant preserved: api.anthropic.com is the LOCAL
|
|
137
|
+
// Anthropic API endpoint; the Mindrian-owned Brain MCP host is NEVER contacted.
|
|
138
|
+
const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
|
|
139
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
140
|
+
const LLM_TIMEOUT_MS = 5000;
|
|
141
|
+
|
|
142
|
+
function _resolveProductionLlmClient() {
|
|
143
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
144
|
+
if (!apiKey || typeof apiKey !== 'string' || apiKey.length === 0) {
|
|
145
|
+
throw new Error('anthropic_api_key_missing');
|
|
146
|
+
}
|
|
147
|
+
if (typeof fetch !== 'function') {
|
|
148
|
+
throw new Error('global_fetch_unavailable');
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
complete: async function (req) {
|
|
152
|
+
const model = req.model || HAIKU_MODEL_ID;
|
|
153
|
+
const messages = Array.isArray(req.messages) ? req.messages : [];
|
|
154
|
+
const max_tokens = (typeof req.max_tokens === 'number') ? req.max_tokens : 20;
|
|
155
|
+
const ctrl = (typeof AbortController === 'function') ? new AbortController() : null;
|
|
156
|
+
const timer = ctrl ? setTimeout(function () { try { ctrl.abort(); } catch (_e) {} }, LLM_TIMEOUT_MS) : null;
|
|
157
|
+
try {
|
|
158
|
+
const res = await fetch(ANTHROPIC_URL, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: {
|
|
161
|
+
'content-type': 'application/json',
|
|
162
|
+
'x-api-key': apiKey,
|
|
163
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify({ model: model, max_tokens: max_tokens, messages: messages }),
|
|
166
|
+
signal: ctrl ? ctrl.signal : undefined,
|
|
167
|
+
});
|
|
168
|
+
if (!res || !res.ok) {
|
|
169
|
+
throw new Error('anthropic_http_' + (res ? res.status : 'no_response'));
|
|
170
|
+
}
|
|
171
|
+
const j = await res.json();
|
|
172
|
+
let text = '';
|
|
173
|
+
if (j && Array.isArray(j.content)) {
|
|
174
|
+
for (const blk of j.content) {
|
|
175
|
+
if (blk && blk.type === 'text' && typeof blk.text === 'string') {
|
|
176
|
+
text += blk.text;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { content: text };
|
|
181
|
+
} finally {
|
|
182
|
+
if (timer) clearTimeout(timer);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
suggestRoomName: suggestRoomName,
|
|
190
|
+
FALLBACK_SUGGESTION: FALLBACK_SUGGESTION,
|
|
191
|
+
HAIKU_MODEL_ID: HAIKU_MODEL_ID,
|
|
192
|
+
_normalizeSlug: _normalizeSlug,
|
|
193
|
+
_buildLocalPrompt: _buildLocalPrompt,
|
|
194
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Phase 119-01 Task 1 tests for lib/core/llm-name-suggester.cjs.
|
|
3
|
+
// Validates HAIKU_MODEL_ID + FALLBACK_SUGGESTION + suggestRoomName happy path +
|
|
4
|
+
// graceful degradation + Canon Part 8 LOCAL invariant (zero brain.mindrian
|
|
5
|
+
// substrings in module source) + EVENT_TYPES floor + room_discard_partial_failure
|
|
6
|
+
// membership.
|
|
7
|
+
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const os = require('node:os');
|
|
13
|
+
const crypto = require('node:crypto');
|
|
14
|
+
|
|
15
|
+
const suggester = require('./llm-name-suggester.cjs');
|
|
16
|
+
const memoryEvents = require('./navigation/memory-events.cjs');
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// EVENT_TYPES tests (floor + named membership)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
test('Test 1: EVENT_TYPES Set size grows by at least 1 above the Plan 119-00 baseline (floor >= 42)', function () {
|
|
23
|
+
assert.ok(memoryEvents.EVENT_TYPES.size >= 42,
|
|
24
|
+
'EVENT_TYPES.size must be >= 42 (38 baseline + 3 from Plan 119-00 + 1 from this plan); got ' + memoryEvents.EVENT_TYPES.size);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Test 2: room_discard_partial_failure is in EVENT_TYPES; regression check on Plan 119-00 strings preserved', function () {
|
|
28
|
+
assert.ok(memoryEvents.EVENT_TYPES.has('room_discard_partial_failure'), 'room_discard_partial_failure missing');
|
|
29
|
+
assert.ok(memoryEvents.EVENT_TYPES.has('room_auto_created'), 'Plan 119-00 string room_auto_created regressed');
|
|
30
|
+
assert.ok(memoryEvents.EVENT_TYPES.has('room_naming_decided'), 'Plan 119-00 string room_naming_decided regressed');
|
|
31
|
+
assert.ok(memoryEvents.EVENT_TYPES.has('room_discarded'), 'Plan 119-00 string room_discarded regressed');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Test 3: EVENT_TYPES object is Object.frozen (own-properties invariant)', function () {
|
|
35
|
+
assert.ok(Object.isFrozen(memoryEvents.EVENT_TYPES),
|
|
36
|
+
'EVENT_TYPES Set must be Object.frozen (own-properties; the internal Set slot is documentation-only frozen per ECMAScript spec)');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('Test 4: logEvent acceptance round-trip for room_discard_partial_failure', function () {
|
|
40
|
+
// Use a real tmp room.db via room-db.cjs::openRoomDb (the canonical opener).
|
|
41
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'lln-suggester-test-'));
|
|
42
|
+
try {
|
|
43
|
+
const { openRoomDb, closeRoomDb } = require('./room-db.cjs');
|
|
44
|
+
const db = openRoomDb(tmpRoot);
|
|
45
|
+
try {
|
|
46
|
+
const result = memoryEvents.logEvent(db, 'room_discard_partial_failure', {
|
|
47
|
+
previous_slug: 'untitled-2026-05-16-1845',
|
|
48
|
+
partial_state: { fs_removed: true, registry_purged: false },
|
|
49
|
+
error_short: 'EBUSY',
|
|
50
|
+
source_path: 'system:room-discard-cascade',
|
|
51
|
+
created_by: 'system',
|
|
52
|
+
});
|
|
53
|
+
assert.strictEqual(result.ok, true, 'logEvent must accept room_discard_partial_failure as a valid event_type');
|
|
54
|
+
assert.match(result.eventId, /^memory_event:room_discard_partial_failure:\d+:[0-9a-f]{8}$/);
|
|
55
|
+
} finally {
|
|
56
|
+
closeRoomDb(db);
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
try { fs.rmSync(tmpRoot, { recursive: true, force: true }); } catch (_e) {}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// suggestRoomName tests
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
test('Test 5: HAIKU_MODEL_ID constant equals claude-haiku-4-5 (mirrors lib/core/mva-classifier.cjs:53)', function () {
|
|
68
|
+
assert.strictEqual(suggester.HAIKU_MODEL_ID, 'claude-haiku-4-5');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Test 6: FALLBACK_SUGGESTION constant equals untitled (verbatim)', function () {
|
|
72
|
+
assert.strictEqual(suggester.FALLBACK_SUGGESTION, 'untitled');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('Test 7: suggestRoomName happy path returns normalized slug', async function () {
|
|
76
|
+
const stub = { complete: async function () { return { content: 'acme-robotics' }; } };
|
|
77
|
+
const r = await suggester.suggestRoomName({
|
|
78
|
+
auto_explore_finding: { findings: [{ source_pipeline: 'whitespace', hsi_score: 0.83 }] },
|
|
79
|
+
mva_brief_sentence: 'Robotic precision for industrial automation.',
|
|
80
|
+
llmClient: stub,
|
|
81
|
+
});
|
|
82
|
+
assert.strictEqual(r.ok, true);
|
|
83
|
+
assert.strictEqual(r.suggested_name, 'acme-robotics');
|
|
84
|
+
assert.strictEqual(r.model_used, 'claude-haiku-4-5');
|
|
85
|
+
assert.ok(typeof r.latency_ms === 'number' && r.latency_ms >= 0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('Test 8: suggestRoomName graceful degradation on LLM error -> FALLBACK_SUGGESTION', async function () {
|
|
89
|
+
const stub = { complete: async function () { throw new Error('timeout'); } };
|
|
90
|
+
const r = await suggester.suggestRoomName({
|
|
91
|
+
auto_explore_finding: null,
|
|
92
|
+
mva_brief_sentence: '',
|
|
93
|
+
llmClient: stub,
|
|
94
|
+
});
|
|
95
|
+
assert.strictEqual(r.ok, false);
|
|
96
|
+
assert.strictEqual(r.suggested_name, 'untitled');
|
|
97
|
+
assert.strictEqual(r.model_used, 'claude-haiku-4-5');
|
|
98
|
+
assert.ok(typeof r.latency_ms === 'number' && r.latency_ms >= 0);
|
|
99
|
+
assert.ok(typeof r.error_short === 'string' && r.error_short.length > 0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('Test 9: Canon Part 8 LOCAL invariant -- grep tripwire on llm-name-suggester source', function () {
|
|
103
|
+
const src = fs.readFileSync(require.resolve('./llm-name-suggester.cjs'), 'utf8');
|
|
104
|
+
// The forbidden Brain coupling regex (from the plan scaffold harness Gate 3):
|
|
105
|
+
// brain.mindrian | require.+brain-client | fetch.+brain
|
|
106
|
+
assert.ok(!/brain\.mindrian/.test(src), 'source contains brain.mindrian substring (Canon Part 8 breach)');
|
|
107
|
+
assert.ok(!/require\([^)]*brain-client[^)]*\)/.test(src), 'source requires a brain-client module (Canon Part 8 breach)');
|
|
108
|
+
// The fetch.+brain regex would match commentary about "brain" near a fetch
|
|
109
|
+
// discussion; we narrow to actual fetch() invocations to brain.* hosts:
|
|
110
|
+
assert.ok(!/fetch\([^)]*['\"][^'\"]*brain[^'\"]*['\"]/.test(src), 'source fetches a brain.* URL (Canon Part 8 breach)');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('Test 10: Canon Part 8 LOCAL invariant -- payload audit (constructed prompt contains no Brain handle)', function () {
|
|
114
|
+
const prompt = suggester._buildLocalPrompt({
|
|
115
|
+
findings: [{ source_pipeline: 'whitespace', hsi_score: 0.5 }],
|
|
116
|
+
}, 'AI-assisted protein folding for drug discovery.');
|
|
117
|
+
const serialized = JSON.stringify(prompt);
|
|
118
|
+
assert.ok(serialized.indexOf('brain.mindrian') === -1, 'prompt must not contain brain.mindrian');
|
|
119
|
+
assert.ok(serialized.indexOf('brain-client') === -1, 'prompt must not contain brain-client handle');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Em-dash invariant on the test file itself + module source
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
test('Test 11: zero em-dashes in llm-name-suggester source + this test file', function () {
|
|
127
|
+
const EMDASH = String.fromCharCode(0x2014);
|
|
128
|
+
const src = fs.readFileSync(require.resolve('./llm-name-suggester.cjs'), 'utf8');
|
|
129
|
+
const testSrc = fs.readFileSync(__filename, 'utf8');
|
|
130
|
+
assert.ok(src.indexOf(EMDASH) === -1, 'em-dash present in llm-name-suggester.cjs (HARD RULE)');
|
|
131
|
+
assert.ok(testSrc.indexOf(EMDASH) === -1, 'em-dash present in llm-name-suggester.test.cjs (HARD RULE)');
|
|
132
|
+
});
|
|
@@ -303,6 +303,47 @@ async function runPipeline(opts) {
|
|
|
303
303
|
});
|
|
304
304
|
} catch (_e) { /* best-effort */ }
|
|
305
305
|
|
|
306
|
+
// phase-119-01-naming-selector-hook: spawn the post-MVA retroactive-naming F.1
|
|
307
|
+
// selector as a detached child. The selector runs entirely LOCAL (Canon Part 8);
|
|
308
|
+
// it surfaces an F.1 selector with the four locked labels from CONTEXT.md D-06
|
|
309
|
+
// (name this room: <LLM-suggested> / type your own name / keep as untitled /
|
|
310
|
+
// discard room), validates user choice through the Phase 88.2 dispatcher, and
|
|
311
|
+
// emits room_naming_decided + room_discarded memory_events via the navigation.cjs
|
|
312
|
+
// chokepoint.
|
|
313
|
+
//
|
|
314
|
+
// Phase 119-01 failure NEVER regresses Phase 118: the spawn is detached + unref'd;
|
|
315
|
+
// any failure (missing shim, require failure, etc.) is swallowed; runPipeline
|
|
316
|
+
// returns immediately. The state.json write below is unaffected.
|
|
317
|
+
try {
|
|
318
|
+
const cp = require('node:child_process');
|
|
319
|
+
const hookPath = require('node:path');
|
|
320
|
+
const hookFs = require('node:fs');
|
|
321
|
+
const hookOs = require('node:os');
|
|
322
|
+
const shimPath = hookPath.join(__dirname, '..', '..', 'scripts', 'room-naming-selector.cjs');
|
|
323
|
+
// Resolve the current room dir via the rooms registry to honor Plan 119-00's
|
|
324
|
+
// reassignment of the active slug.
|
|
325
|
+
const roomsHome = process.env.MINDRIAN_ROOMS_HOME ||
|
|
326
|
+
hookPath.join(process.env.HOME || hookOs.homedir(), 'MindrianRooms');
|
|
327
|
+
const registryPath = hookPath.join(roomsHome, '.rooms', 'registry.json');
|
|
328
|
+
let activeRoomDir = null;
|
|
329
|
+
if (hookFs.existsSync(registryPath)) {
|
|
330
|
+
const reg = JSON.parse(hookFs.readFileSync(registryPath, 'utf8'));
|
|
331
|
+
if (reg && typeof reg.active === 'string' && reg.active.length > 0) {
|
|
332
|
+
activeRoomDir = hookPath.join(roomsHome, reg.active);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (activeRoomDir && hookFs.existsSync(shimPath)) {
|
|
336
|
+
const child = cp.spawn('node', [shimPath, '--room-dir', activeRoomDir, '--sentence-sha256', sha256], {
|
|
337
|
+
detached: true,
|
|
338
|
+
stdio: 'ignore',
|
|
339
|
+
});
|
|
340
|
+
if (typeof child.unref === 'function') child.unref();
|
|
341
|
+
}
|
|
342
|
+
} catch (_e) {
|
|
343
|
+
// Phase 119-01 failure NEVER regresses Phase 118. Swallow silently; the user's
|
|
344
|
+
// MVA brief still rendered + deployed + state.json still writes below.
|
|
345
|
+
}
|
|
346
|
+
|
|
306
347
|
// CRITICAL-3 wire: atomic state.json manifest after mva_brief_rendered.
|
|
307
348
|
// Only on the rendered path (not on Hebrew short-circuit which returned earlier).
|
|
308
349
|
// Plan 118-04 carries the deck_url into the manifest atomically.
|
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* ~/.mindrian/telemetry/v1.13/mva.jsonl. Schema-enforced (rejects events
|
|
8
|
-
* with raw user content; per-event ALLOWED_FIELDS is the source of truth
|
|
9
|
-
* for Plan 118-06's Dror harness grep test).
|
|
10
|
-
*
|
|
11
|
-
* Per Plan 118-03 OQ8 (resolved): the 6 event types are
|
|
12
|
-
* mva_pipeline_started, mva_agent_returned, mva_brief_rendered,
|
|
13
|
-
* mva_option_selected, mva_brief_deployed, mva_pipeline_failed.
|
|
14
|
-
*
|
|
15
|
-
* CRITICAL invariant (WARN-2 from iteration 2): the mva_brief_rendered
|
|
16
|
-
* event uses `total_duration_ms` (NOT `duration_ms`). Plan 118-06's
|
|
17
|
-
* Dror harness greps ALLOWED_FIELDS.mva_brief_rendered to assert this.
|
|
18
|
-
*
|
|
19
|
-
* Canon Part 8 (LOCAL telemetry):
|
|
20
|
-
* - Sentence-related identifier is sentence_sha256 ONLY (one-way hash).
|
|
21
|
-
* - Every string field is capped (sentence_sha256=64 exact, error_short<=60,
|
|
22
|
-
* other strings<=64) to prevent raw user content from sneaking through.
|
|
23
|
-
* - Fields not in ALLOWED_FIELDS for the event type are rejected.
|
|
24
|
-
*
|
|
25
|
-
* Atomic append: fs.appendFileSync writes a single short line. POSIX
|
|
26
|
-
* append semantics guarantee atomicity for writes within PIPE_BUF (4096
|
|
27
|
-
* bytes on Linux). Our lines are < 512 bytes, well below the limit.
|
|
28
|
-
*
|
|
29
|
-
* Pure CJS, node built-ins only.
|
|
3
|
+
* Phase 121-01 -- mva-telemetry shim. Delegates to lib/core/telemetry/writer.cjs
|
|
4
|
+
* (the Canon Part 9 chokepoint), with a legacy dual-write to the historical
|
|
5
|
+
* ~/.mindrian/telemetry/v1.13/mva.jsonl path so existing readers keep working.
|
|
6
|
+
* TODO(v1.14.0): delete this shim; callers must import lib/core/telemetry/writer.cjs directly.
|
|
30
7
|
*/
|
|
31
8
|
'use strict';
|
|
32
9
|
|
|
@@ -34,137 +11,48 @@ const fs = require('node:fs');
|
|
|
34
11
|
const path = require('node:path');
|
|
35
12
|
const os = require('node:os');
|
|
36
13
|
|
|
37
|
-
|
|
14
|
+
const writer = require('./telemetry/writer.cjs');
|
|
15
|
+
const validator = require('./telemetry/validator.cjs');
|
|
16
|
+
const schema = require('./telemetry/schema.cjs');
|
|
38
17
|
|
|
39
18
|
const EVENT_TYPES = Object.freeze([
|
|
40
|
-
'mva_pipeline_started',
|
|
41
|
-
'
|
|
42
|
-
'mva_brief_rendered',
|
|
43
|
-
'mva_option_selected',
|
|
44
|
-
'mva_brief_deployed',
|
|
45
|
-
'mva_pipeline_failed'
|
|
19
|
+
'mva_pipeline_started', 'mva_agent_returned', 'mva_brief_rendered',
|
|
20
|
+
'mva_option_selected', 'mva_brief_deployed', 'mva_pipeline_failed',
|
|
46
21
|
]);
|
|
47
22
|
|
|
48
|
-
// Per-event scalar field schema. Source-of-truth for Plan 118-06 Dror harness.
|
|
49
|
-
// CRITICAL: mva_brief_rendered uses 'total_duration_ms' (NOT 'duration_ms').
|
|
50
23
|
const ALLOWED_FIELDS = Object.freeze({
|
|
51
|
-
mva_pipeline_started:
|
|
52
|
-
mva_agent_returned:
|
|
53
|
-
mva_brief_rendered:
|
|
54
|
-
mva_option_selected:
|
|
55
|
-
mva_brief_deployed:
|
|
56
|
-
mva_pipeline_failed:
|
|
24
|
+
mva_pipeline_started: schema.ALLOWED_FIELDS.mva_pipeline_started,
|
|
25
|
+
mva_agent_returned: schema.ALLOWED_FIELDS.mva_agent_returned,
|
|
26
|
+
mva_brief_rendered: schema.ALLOWED_FIELDS.mva_brief_rendered,
|
|
27
|
+
mva_option_selected: schema.ALLOWED_FIELDS.mva_option_selected,
|
|
28
|
+
mva_brief_deployed: schema.ALLOWED_FIELDS.mva_brief_deployed,
|
|
29
|
+
mva_pipeline_failed: schema.ALLOWED_FIELDS.mva_pipeline_failed,
|
|
57
30
|
});
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
// sentence_sha256 must be exactly 64 hex chars.
|
|
61
|
-
const MAX_STRING_LEN = 64;
|
|
62
|
-
const MAX_ERROR_SHORT_LEN = 60;
|
|
63
|
-
const SHA256_LEN = 64;
|
|
64
|
-
|
|
65
|
-
// ---------- Path resolvers (env-aware for hermetic testing) ----------
|
|
66
|
-
|
|
67
|
-
function homeDir() {
|
|
32
|
+
function _homeDir() {
|
|
68
33
|
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
69
34
|
}
|
|
70
35
|
|
|
71
|
-
function
|
|
72
|
-
return path.join(
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function telemetryFile() {
|
|
76
|
-
return path.join(telemetryDir(), 'mva.jsonl');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ---------- Validation ----------
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* validateEventPayload(event, payload) -> { ok: boolean, error?: string }
|
|
83
|
-
*
|
|
84
|
-
* Returns ok:false if:
|
|
85
|
-
* - event is not in EVENT_TYPES
|
|
86
|
-
* - any key in payload is not in ALLOWED_FIELDS[event]
|
|
87
|
-
* - any string value violates the per-field length cap
|
|
88
|
-
*
|
|
89
|
-
* Numeric fields are not length-checked. The session_id field is added
|
|
90
|
-
* by emit() (not the caller), so it is not validated against ALLOWED_FIELDS.
|
|
91
|
-
*/
|
|
92
|
-
function validateEventPayload(event, payload) {
|
|
93
|
-
if (!EVENT_TYPES.includes(event)) {
|
|
94
|
-
return { ok: false, error: 'unknown_event' };
|
|
95
|
-
}
|
|
96
|
-
if (!payload || typeof payload !== 'object') {
|
|
97
|
-
return { ok: false, error: 'payload_not_object' };
|
|
98
|
-
}
|
|
99
|
-
const allowed = ALLOWED_FIELDS[event];
|
|
100
|
-
for (const key of Object.keys(payload)) {
|
|
101
|
-
if (!allowed.includes(key)) {
|
|
102
|
-
return { ok: false, error: 'unknown_field:' + key };
|
|
103
|
-
}
|
|
104
|
-
const v = payload[key];
|
|
105
|
-
if (typeof v === 'string') {
|
|
106
|
-
if (key === 'sentence_sha256') {
|
|
107
|
-
if (v.length !== SHA256_LEN) {
|
|
108
|
-
return { ok: false, error: 'sha256_length_invalid' };
|
|
109
|
-
}
|
|
110
|
-
} else if (key === 'error_short') {
|
|
111
|
-
if (v.length > MAX_ERROR_SHORT_LEN) {
|
|
112
|
-
return { ok: false, error: 'error_short_too_long' };
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
if (v.length > MAX_STRING_LEN) {
|
|
116
|
-
return { ok: false, error: 'string_too_long:' + key };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return { ok: true };
|
|
36
|
+
function _legacyMvaPath() {
|
|
37
|
+
return path.join(_homeDir(), '.mindrian', 'telemetry', 'v1.13', 'mva.jsonl');
|
|
122
38
|
}
|
|
123
39
|
|
|
124
|
-
// ---------- Public emit() ----------
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* emit(event, payload) -> appends one JSONL line to ~/.mindrian/telemetry/v1.13/mva.jsonl
|
|
128
|
-
*
|
|
129
|
-
* Validates first. On invalid payload throws a ValidationError (so callers
|
|
130
|
-
* cannot silently leak). On disk error returns silently (best-effort: the
|
|
131
|
-
* pipeline must not crash because telemetry is unavailable).
|
|
132
|
-
*/
|
|
133
40
|
function emit(event, payload) {
|
|
134
|
-
|
|
135
|
-
if (!v.ok) {
|
|
136
|
-
const e = new Error('telemetry validation failed: ' + v.error);
|
|
137
|
-
e.code = 'TELEMETRY_VALIDATION';
|
|
138
|
-
throw e;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const record = Object.assign(
|
|
142
|
-
{
|
|
143
|
-
event: event,
|
|
144
|
-
timestamp: new Date().toISOString(),
|
|
145
|
-
session_id: (typeof process.env.CLAUDE_SESSION_ID === 'string' && process.env.CLAUDE_SESSION_ID.length > 0)
|
|
146
|
-
? process.env.CLAUDE_SESSION_ID.slice(0, MAX_STRING_LEN)
|
|
147
|
-
: 'default'
|
|
148
|
-
},
|
|
149
|
-
payload
|
|
150
|
-
);
|
|
151
|
-
|
|
41
|
+
writer.emit(event, payload); // Canon Part 8 gate + unified events-YYYY-WNN.jsonl append.
|
|
152
42
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
43
|
+
const sid = (typeof process.env.CLAUDE_SESSION_ID === 'string' && process.env.CLAUDE_SESSION_ID.length > 0)
|
|
44
|
+
? process.env.CLAUDE_SESSION_ID.slice(0, schema.MAX_STRING_LEN) : 'default';
|
|
45
|
+
const record = Object.assign({ event: event, timestamp: new Date().toISOString(), session_id: sid }, payload);
|
|
46
|
+
fs.mkdirSync(path.dirname(_legacyMvaPath()), { recursive: true });
|
|
47
|
+
fs.appendFileSync(_legacyMvaPath(), JSON.stringify(record) + '\n', 'utf8');
|
|
48
|
+
} catch (_) { /* best-effort legacy dual-write */ }
|
|
160
49
|
}
|
|
161
50
|
|
|
162
51
|
module.exports = {
|
|
163
|
-
emit,
|
|
164
|
-
validateEventPayload,
|
|
165
|
-
EVENT_TYPES,
|
|
166
|
-
ALLOWED_FIELDS,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
telemetryFile,
|
|
52
|
+
emit: emit,
|
|
53
|
+
validateEventPayload: validator.validateEventPayload,
|
|
54
|
+
EVENT_TYPES: EVENT_TYPES,
|
|
55
|
+
ALLOWED_FIELDS: ALLOWED_FIELDS,
|
|
56
|
+
telemetryDir: writer.telemetryDir,
|
|
57
|
+
telemetryFile: writer.telemetryFile,
|
|
170
58
|
};
|
|
@@ -33,6 +33,41 @@ const ALLOWED_EDGE_TYPES = Object.freeze(new Set([
|
|
|
33
33
|
// Phase 125 D7 -- F-selector decision edges (LOCKED LOCAL per Canon Part 8).
|
|
34
34
|
'DEFERRED',
|
|
35
35
|
'REJECTED',
|
|
36
|
+
// Phase 120-00 Wave 1 extension (Breakthrough Scan / Category G; D-18 HARD FLOOR enforcement
|
|
37
|
+
// + D-20 Cypher-provable principle). DERIVED_FROM is the structural enforcement: a
|
|
38
|
+
// Breakthrough node CANNOT surface without at least one DERIVED_FROM edge to an
|
|
39
|
+
// Artifact node. Mirrors the Phase 125-00 DEFERRED + REJECTED additive idiom.
|
|
40
|
+
//
|
|
41
|
+
// Canon Part 4: every choice is graph data. The Breakthrough node + its DERIVED_FROM
|
|
42
|
+
// edges are the graph-native artifact of pattern detection.
|
|
43
|
+
//
|
|
44
|
+
// Canon Part 8: writeEdge takes (db, params) over a LOCAL room.db handle; DERIVED_FROM
|
|
45
|
+
// never crosses to Brain. Cross-room aggregation forbidden (Phase 8 cross-room fence).
|
|
46
|
+
//
|
|
47
|
+
// D-20 enforcement: lib/core/breakthrough/schema.cjs::writeBreakthrough wraps the
|
|
48
|
+
// Breakthrough node insert + N DERIVED_FROM edge inserts in a single SQLite transaction.
|
|
49
|
+
// If any step fails, the transaction rolls back -- partial Breakthrough state CANNOT
|
|
50
|
+
// land. The Cypher invariant `MATCH (b:Breakthrough)-[:DERIVED_FROM]->(a:Artifact)
|
|
51
|
+
// RETURN count(a)` is guaranteed >= 1 by construction.
|
|
52
|
+
'DERIVED_FROM',
|
|
53
|
+
// Phase 120-02 Wave 2 extension (Breakthrough Scan / Category G; D-09 file-as-decision
|
|
54
|
+
// bridge). FILED_AS_DECISION is the typed edge that promotes a Breakthrough node into
|
|
55
|
+
// the Phase 88 decision-log machinery when the user picks the [File as decision]
|
|
56
|
+
// verb on F.7. Mirrors the Phase 120-00 DERIVED_FROM additive idiom verbatim.
|
|
57
|
+
//
|
|
58
|
+
// Canon Part 4: every choice is graph data. The Breakthrough -> Decision edge is the
|
|
59
|
+
// graph-native bridge that lets future related breakthroughs reference the filed
|
|
60
|
+
// decision via ENABLES edges (per CONTEXT.md D-15 "may be referenced as ENABLES in
|
|
61
|
+
// future related breakthroughs").
|
|
62
|
+
//
|
|
63
|
+
// Canon Part 8: writeEdge takes (db, params) over a LOCAL room.db handle;
|
|
64
|
+
// FILED_AS_DECISION never crosses to Brain. Cross-room aggregation forbidden.
|
|
65
|
+
//
|
|
66
|
+
// Emitted by: lib/core/breakthrough/verb-dispatch.cjs::handleFileAsDecision.
|
|
67
|
+
// The destination Decision node id is 'decision:' + breakthroughId by convention;
|
|
68
|
+
// Phase 88 decision-log machinery (or a future Phase 121 housekeeping pass) is
|
|
69
|
+
// responsible for materializing the Decision node body when one does not yet exist.
|
|
70
|
+
'FILED_AS_DECISION',
|
|
36
71
|
]));
|
|
37
72
|
|
|
38
73
|
function isPlainObject(v) {
|