@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,426 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Phase 120-02 Wave 2 Task 3 -- The breakthrough scanner orchestrator. Wires:
|
|
4
|
+
* - Plan 120-00 detectors.cjs (the 4 pattern types)
|
|
5
|
+
* - Plan 120-00 schema.writeBreakthrough (the D-20 HARD FLOOR atomic writer)
|
|
6
|
+
* - Plan 120-01 scoring.pickTopWithAffordance (the D-11 + D-12 ranker)
|
|
7
|
+
* - Plan 120-02 resurfacing.isEligibleForSurfacing (D-13..D-15)
|
|
8
|
+
* - Plan 120-02 canary.computeDismissalRate (D-19)
|
|
9
|
+
* - Plan 120-01 selector-dispatcher.pickShape (F.7 surfacing)
|
|
10
|
+
*
|
|
11
|
+
* Per CONTEXT.md D-16 empty-state lock: if no eligible candidates, return
|
|
12
|
+
* top=null. The hook script then emits continue:true with NO additionalContext
|
|
13
|
+
* (silence; no placeholder; trust users to notice presence vs absence).
|
|
14
|
+
*
|
|
15
|
+
* Per CONTEXT.md D-20 third structural enforcement point: surfaceBreakthrough
|
|
16
|
+
* verifies the Breakthrough node has at least one DERIVED_FROM edge BEFORE
|
|
17
|
+
* dispatching F.7. The other two enforcement points are schema.cjs::
|
|
18
|
+
* validateProvenance + writeBreakthrough transaction (Plan 120-00) and
|
|
19
|
+
* shape-f7-breakthrough-renderer.cjs::renderShapeF7Breakthrough's artifact_ids
|
|
20
|
+
* check (Plan 120-01). Defense in depth.
|
|
21
|
+
*
|
|
22
|
+
* Canon Part 8 + Part 9 + Part 10 sub-claim 5: pure LOCAL; chokepoint-routed;
|
|
23
|
+
* the math IS the surface; variable reward fires automatically at session
|
|
24
|
+
* start. Zero Brain coupling; zero cross-room aggregation.
|
|
25
|
+
*
|
|
26
|
+
* Em-dash HARD RULE (CLAUDE.md feedback_no_emdashes): use "--" not U+2014.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const detectors = require('./detectors.cjs');
|
|
31
|
+
const schema = require('./schema.cjs');
|
|
32
|
+
const scoring = require('./scoring.cjs');
|
|
33
|
+
const resurfacing = require('./resurfacing.cjs');
|
|
34
|
+
const canary = require('./canary.cjs');
|
|
35
|
+
// Plan 120-03 wires: voice-scaffold (D-17 4-rule auditor) + ethics-fence (D-18
|
|
36
|
+
// 4-tier hybrid fence). These are pure modules with zero Brain coupling.
|
|
37
|
+
const voiceScaffold = require('./voice-scaffold.cjs');
|
|
38
|
+
const ethicsFence = require('./ethics-fence.cjs');
|
|
39
|
+
const navigation = require('../navigation.cjs');
|
|
40
|
+
const roomDb = require('../room-db.cjs');
|
|
41
|
+
const crypto = require('node:crypto');
|
|
42
|
+
let selectorDispatcher = null;
|
|
43
|
+
try {
|
|
44
|
+
selectorDispatcher = require('../../hmi/selector-dispatcher.cjs');
|
|
45
|
+
} catch (_e) {
|
|
46
|
+
selectorDispatcher = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Phase 121-02 D-07: lazy-require the unified telemetry writer at first emit.
|
|
50
|
+
// A missing writer module (stripped-down install) must not crash the scanner,
|
|
51
|
+
// so failures of require() degrade to a soft skip (no emit).
|
|
52
|
+
function _sha256Hex(s) {
|
|
53
|
+
return crypto.createHash('sha256').update(String(s || '')).digest('hex');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// applyThrottleFilter -- D-19 per-detector dismissal-rate canary filter. For
|
|
57
|
+
// every detector kind whose dismissal rate exceeds the D-19 threshold over the
|
|
58
|
+
// rolling 100-fire window, drops all candidates of that kind from the hard-fire
|
|
59
|
+
// ranking AND emits a breakthrough_throttled memory_event so /mos:doctor can
|
|
60
|
+
// surface the throttled detector. Returns { kept, throttled_kinds }.
|
|
61
|
+
function applyThrottleFilter(candidates, db) {
|
|
62
|
+
const throttledKinds = [];
|
|
63
|
+
if (!Array.isArray(candidates) || !db) {
|
|
64
|
+
return { kept: Array.isArray(candidates) ? candidates : [], throttled_kinds: throttledKinds };
|
|
65
|
+
}
|
|
66
|
+
for (const kind of detectors.DETECTOR_TYPES) {
|
|
67
|
+
const rateResult = canary.computeDismissalRate(kind, db);
|
|
68
|
+
if (rateResult.throttled) {
|
|
69
|
+
throttledKinds.push(kind);
|
|
70
|
+
// Emit breakthrough_throttled event so /mos:doctor can surface throttled
|
|
71
|
+
// detectors at session-start. Best-effort: never throws on infra hiccup.
|
|
72
|
+
try { canary.emitThrottleEvent(kind, db, rateResult); } catch (_e) { /* swallow */ }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const kept = throttledKinds.length === 0
|
|
76
|
+
? candidates
|
|
77
|
+
: candidates.filter(function (c) {
|
|
78
|
+
return c && typeof c.kind === 'string' && throttledKinds.indexOf(c.kind) === -1;
|
|
79
|
+
});
|
|
80
|
+
return { kept: kept, throttled_kinds: throttledKinds };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// applyResurfacingFilter -- D-13..D-15 resurfacing rules filter. Drops any
|
|
84
|
+
// candidate whose breakthrough_id has been confirmed (D-14 once-only),
|
|
85
|
+
// filed-as-decision (D-15 never-resurfaces), or dismissed-and-still-in-cooldown
|
|
86
|
+
// (D-13 BOTH-condition: 7-day cooldown AND new-artifacts-accumulated).
|
|
87
|
+
function applyResurfacingFilter(candidates, db) {
|
|
88
|
+
if (!Array.isArray(candidates) || !db) {
|
|
89
|
+
return Array.isArray(candidates) ? candidates : [];
|
|
90
|
+
}
|
|
91
|
+
return candidates.filter(function (c) {
|
|
92
|
+
if (!c || typeof c.id !== 'string') return false;
|
|
93
|
+
const elig = resurfacing.isEligibleForSurfacing(c.id, db, {
|
|
94
|
+
current_artifact_count: Array.isArray(c.artifact_ids) ? c.artifact_ids.length : 0,
|
|
95
|
+
});
|
|
96
|
+
return elig && elig.eligible === true;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// scanForBreakthroughs -- the session-start orchestrator. Runs the 4 detectors,
|
|
101
|
+
// applies the D-19 throttle filter + D-13..D-15 resurfacing filter, ranks
|
|
102
|
+
// surviving candidates via the Plan 120-01 scoring formula, persists the top
|
|
103
|
+
// via writeBreakthrough (D-20 HARD FLOOR), and returns
|
|
104
|
+
// { top, more_count, throttled_kinds, queued, reason? }.
|
|
105
|
+
//
|
|
106
|
+
// D-16 empty-state silence: returns top=null whenever no eligible hard-fire
|
|
107
|
+
// candidate survives the filters. The caller hook then emits continue:true with
|
|
108
|
+
// NO additionalContext.
|
|
109
|
+
function scanForBreakthroughs(roomDir, opts) {
|
|
110
|
+
const options = opts || {};
|
|
111
|
+
const nowMs = (typeof options.now === 'number' && Number.isFinite(options.now)) ? options.now : Date.now();
|
|
112
|
+
|
|
113
|
+
if (typeof roomDir !== 'string' || roomDir.length === 0) {
|
|
114
|
+
return { top: null, more_count: 0, throttled_kinds: [], reason: 'invalid_room_dir' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Open db via the canonical room-db chokepoint. openRoomDb returns the bare
|
|
118
|
+
// DatabaseSync handle per the Phase 109-02 contract. If room.db cannot be
|
|
119
|
+
// opened (e.g., missing or corrupt), degrade to D-16 silence.
|
|
120
|
+
let db;
|
|
121
|
+
try {
|
|
122
|
+
db = roomDb.openRoomDb(roomDir);
|
|
123
|
+
} catch (_e) {
|
|
124
|
+
return { top: null, more_count: 0, throttled_kinds: [], reason: 'no_room_db' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const roomState = { roomDir: roomDir, db: db, now: nowMs };
|
|
128
|
+
|
|
129
|
+
// Run all 4 detectors. Each returns { hits, soft_fires }. Failures degrade
|
|
130
|
+
// gracefully -- a broken Phase 117 math output file should not block the
|
|
131
|
+
// surface; the failing detector returns empty and the others still surface.
|
|
132
|
+
let allHits = [];
|
|
133
|
+
const allSoftFires = [];
|
|
134
|
+
try {
|
|
135
|
+
const c1 = detectors.detectConvergence(roomState, options);
|
|
136
|
+
const c2 = detectors.detectContradictionResolved(roomState, options);
|
|
137
|
+
const c3 = detectors.detectCrossDomainAnalogy(roomState, options);
|
|
138
|
+
const c4 = detectors.detectReverseSalientClosed(roomState, options);
|
|
139
|
+
allHits = [].concat(c1.hits || [], c2.hits || [], c3.hits || [], c4.hits || []);
|
|
140
|
+
allSoftFires.push(
|
|
141
|
+
...(c1.soft_fires || []),
|
|
142
|
+
...(c2.soft_fires || []),
|
|
143
|
+
...(c3.soft_fires || []),
|
|
144
|
+
...(c4.soft_fires || [])
|
|
145
|
+
);
|
|
146
|
+
} catch (_e) {
|
|
147
|
+
return { top: null, more_count: 0, throttled_kinds: [], reason: 'detector_error' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// D-19: apply the canary throttle filter. Drops any kind whose dismissal rate
|
|
151
|
+
// exceeds 30% over the rolling 100-fire window. Reports throttled_kinds for
|
|
152
|
+
// /mos:doctor traceability.
|
|
153
|
+
const throttleResult = applyThrottleFilter(allHits, db);
|
|
154
|
+
let candidates = throttleResult.kept;
|
|
155
|
+
|
|
156
|
+
// D-13..D-15: apply the resurfacing filter. Drops confirmed / filed-as-decision /
|
|
157
|
+
// in-cooldown dismissed candidates.
|
|
158
|
+
candidates = applyResurfacingFilter(candidates, db);
|
|
159
|
+
|
|
160
|
+
// Plan 120-03: D-18 4-tier ethics fence. Partition candidates into the 4 bands.
|
|
161
|
+
// HARD_CEILING -> proceeds to ranking (auto-surface)
|
|
162
|
+
// SOFT_BAND -> routed to .rooms/breakthrough-review-queue.db + memory_event
|
|
163
|
+
// breakthrough_in_review_queue (no surface, no ranking)
|
|
164
|
+
// HARD_FLOOR -> structurally impossible at this point (writeBreakthrough
|
|
165
|
+
// upstream refuses provenance-less inputs) BUT we apply
|
|
166
|
+
// defense in depth and silently drop the candidate.
|
|
167
|
+
// BELOW_FLOOR -> soft-fire territory already handled by detectors.classifyFireTier;
|
|
168
|
+
// silently drop here (no double-buffer).
|
|
169
|
+
// The HARD_CEILING partition is the only set that proceeds to scoring + surface.
|
|
170
|
+
const hardCeilingHits = [];
|
|
171
|
+
if (Array.isArray(candidates) && candidates.length > 0) {
|
|
172
|
+
// Resolve a rooms-home for SOFT_BAND queue persistence. Prefer the explicit
|
|
173
|
+
// option, then MINDRIAN_ROOMS_HOME env, then ~/MindrianRooms default.
|
|
174
|
+
const roomsHome = options.roomsHome
|
|
175
|
+
|| (typeof process.env.MINDRIAN_ROOMS_HOME === 'string' && process.env.MINDRIAN_ROOMS_HOME.length > 0
|
|
176
|
+
? process.env.MINDRIAN_ROOMS_HOME
|
|
177
|
+
: path.join(process.env.HOME || '', 'MindrianRooms'));
|
|
178
|
+
for (const c of candidates) {
|
|
179
|
+
const band = ethicsFence.classifyEthicsBand(c);
|
|
180
|
+
if (band === 'HARD_CEILING') {
|
|
181
|
+
hardCeilingHits.push(c);
|
|
182
|
+
} else if (band === 'SOFT_BAND') {
|
|
183
|
+
// Best-effort queue insert + memory_event mirror. Failure does NOT block
|
|
184
|
+
// the scanner -- the SOFT_BAND candidate is silently dropped if persistence
|
|
185
|
+
// fails (per the Plan 120-03 graceful-failure invariant).
|
|
186
|
+
try {
|
|
187
|
+
ethicsFence.queueForReview(c, roomsHome, { db: db, roomSlug: options.roomSlug });
|
|
188
|
+
} catch (_e) { /* swallow */ }
|
|
189
|
+
}
|
|
190
|
+
// HARD_FLOOR + BELOW_FLOOR: silent drop. Defense in depth at the ethics layer.
|
|
191
|
+
}
|
|
192
|
+
candidates = hardCeilingHits;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Soft-fire telemetry per CONTEXT.md D-01..D-02. Emit a breakthrough_detected_soft
|
|
196
|
+
// event for every soft-tier candidate -- this populates the retraining signal
|
|
197
|
+
// mentioned in the discuss-phase D-01 verbatim. Best-effort writes.
|
|
198
|
+
for (const sf of allSoftFires) {
|
|
199
|
+
if (!sf || typeof sf.id !== 'string') continue;
|
|
200
|
+
try {
|
|
201
|
+
navigation.logMemoryEvent(db, 'breakthrough_detected_soft', {
|
|
202
|
+
breakthrough_id: sf.id,
|
|
203
|
+
kind: typeof sf.kind === 'string' ? sf.kind : 'unknown',
|
|
204
|
+
confidence: typeof sf.confidence === 'number' ? sf.confidence : 0,
|
|
205
|
+
artifact_count: Array.isArray(sf.artifact_ids) ? sf.artifact_ids.length : 0,
|
|
206
|
+
source_path: 'system:breakthrough-scanner',
|
|
207
|
+
created_by: 'system',
|
|
208
|
+
});
|
|
209
|
+
} catch (_e) { /* swallow */ }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
213
|
+
// D-16 empty-state silence: no eligible candidates survived the filters.
|
|
214
|
+
return {
|
|
215
|
+
top: null,
|
|
216
|
+
more_count: 0,
|
|
217
|
+
throttled_kinds: throttleResult.throttled_kinds,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// D-11 + D-12: rank by the 5-component scoring formula; pick top-1; expose
|
|
222
|
+
// the rest via the "More breakthroughs (N)" affordance.
|
|
223
|
+
const pick = scoring.pickTopWithAffordance(candidates, roomState, nowMs);
|
|
224
|
+
if (!pick || !pick.top) {
|
|
225
|
+
return {
|
|
226
|
+
top: null,
|
|
227
|
+
more_count: 0,
|
|
228
|
+
throttled_kinds: throttleResult.throttled_kinds,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// D-20 HARD FLOOR: persist the top breakthrough via writeBreakthrough. The
|
|
233
|
+
// atomic transaction inserts the Breakthrough node AND its DERIVED_FROM edges.
|
|
234
|
+
// If the write is refused (provenance-less), do not surface; degrade silently.
|
|
235
|
+
const writeResult = schema.writeBreakthrough(db, pick.top);
|
|
236
|
+
if (!writeResult || !writeResult.ok) {
|
|
237
|
+
return {
|
|
238
|
+
top: null,
|
|
239
|
+
more_count: 0,
|
|
240
|
+
throttled_kinds: throttleResult.throttled_kinds,
|
|
241
|
+
reason: 'writeBreakthrough_refused:' + (writeResult ? writeResult.reason : 'unknown'),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
top: pick.top,
|
|
247
|
+
more_count: pick.more_count,
|
|
248
|
+
throttled_kinds: throttleResult.throttled_kinds,
|
|
249
|
+
queued: pick.queued,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// surfaceBreakthrough -- the third D-20 structural enforcement point. Reads the
|
|
254
|
+
// canonical SQL invariant (SELECT COUNT(*) FROM edges WHERE source=? AND
|
|
255
|
+
// type='DERIVED_FROM') BEFORE dispatching F.7. If the count is < 1, emits a
|
|
256
|
+
// breakthrough_surface_blocked event for /mos:doctor traceability and refuses
|
|
257
|
+
// to surface. This catches constitutional bypass attempts (a fake Breakthrough
|
|
258
|
+
// node inserted outside the writeBreakthrough chokepoint).
|
|
259
|
+
//
|
|
260
|
+
// On success: dispatches F.7 via selector-dispatcher.pickShape, emits a
|
|
261
|
+
// breakthrough_surfaced memory_event, and flips the Breakthrough node's
|
|
262
|
+
// properties.surfaced = true.
|
|
263
|
+
function surfaceBreakthrough(breakthrough, options) {
|
|
264
|
+
const opts = options || {};
|
|
265
|
+
const db = opts.db;
|
|
266
|
+
if (!db || !breakthrough || typeof breakthrough.id !== 'string' || breakthrough.id.length === 0) {
|
|
267
|
+
return { ok: false, reason: 'invalid_input' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// D-20 third structural enforcement: provenance MUST exist on disk before
|
|
271
|
+
// F.7 dispatch. Defense in depth on top of schema.cjs::validateProvenance
|
|
272
|
+
// and the renderer's artifact_ids check.
|
|
273
|
+
let provenanceCount = 0;
|
|
274
|
+
try {
|
|
275
|
+
const row = db.prepare(
|
|
276
|
+
"SELECT COUNT(*) AS c FROM edges WHERE source = ? AND type = 'DERIVED_FROM'"
|
|
277
|
+
).get(breakthrough.id);
|
|
278
|
+
provenanceCount = (row && typeof row.c === 'number') ? row.c : 0;
|
|
279
|
+
} catch (_e) {
|
|
280
|
+
provenanceCount = 0;
|
|
281
|
+
}
|
|
282
|
+
if (provenanceCount < 1) {
|
|
283
|
+
// Emit breakthrough_surface_blocked so /mos:doctor can find the refusal.
|
|
284
|
+
// Best-effort: never throws.
|
|
285
|
+
try {
|
|
286
|
+
navigation.logMemoryEvent(db, 'breakthrough_surface_blocked', {
|
|
287
|
+
breakthrough_id: breakthrough.id,
|
|
288
|
+
kind: typeof breakthrough.kind === 'string' ? breakthrough.kind : 'unknown',
|
|
289
|
+
reason: 'provenance_required',
|
|
290
|
+
source_path: 'system:breakthrough-scanner',
|
|
291
|
+
created_by: 'system',
|
|
292
|
+
});
|
|
293
|
+
} catch (_e) { /* swallow */ }
|
|
294
|
+
return { ok: false, reason: 'provenance_required' };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Plan 120-03: D-17 voice scaffold composition + audit gate.
|
|
298
|
+
//
|
|
299
|
+
// Compose the conversational opener via the 4-rule scaffold. If the composed
|
|
300
|
+
// line fails the D-17 auditor (rule 1-4 violation), replace it with the
|
|
301
|
+
// structural default (roomState=null variant of composeBreakthroughVoiceLine
|
|
302
|
+
// which is auditor-safe BY CONSTRUCTION -- see voice-scaffold Test 4).
|
|
303
|
+
//
|
|
304
|
+
// This is defense in depth: no D-17-violating voice line EVER reaches the F.7
|
|
305
|
+
// surface, regardless of caller-supplied mechanism_phrase content. The auditor
|
|
306
|
+
// IS the structural enforcement (D-20 meta-principle).
|
|
307
|
+
let voiceLine = '';
|
|
308
|
+
try {
|
|
309
|
+
const composed = voiceScaffold.composeBreakthroughVoiceLine(breakthrough, opts.roomState || null);
|
|
310
|
+
const audit = voiceScaffold.auditVoiceLine(composed);
|
|
311
|
+
voiceLine = audit.ok
|
|
312
|
+
? composed
|
|
313
|
+
: voiceScaffold.composeBreakthroughVoiceLine(breakthrough, null);
|
|
314
|
+
} catch (_e) {
|
|
315
|
+
voiceLine = '';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Dispatch F.7 via the Phase 88.2 selector dispatcher (Plan 120-01 registration).
|
|
319
|
+
// If the dispatcher is unavailable (e.g., during isolated unit testing of the
|
|
320
|
+
// surface gate), degrade gracefully -- we still emit the breakthrough_surfaced
|
|
321
|
+
// event and flip the node property because those are the load-bearing side
|
|
322
|
+
// effects per the test contract.
|
|
323
|
+
let rendered = null;
|
|
324
|
+
if (selectorDispatcher && typeof selectorDispatcher.pickShape === 'function') {
|
|
325
|
+
try {
|
|
326
|
+
rendered = selectorDispatcher.pickShape({
|
|
327
|
+
requestedShape: 'F.7',
|
|
328
|
+
roomDir: opts.roomDir,
|
|
329
|
+
tier: typeof opts.tier === 'number' ? opts.tier : 1,
|
|
330
|
+
payload: {
|
|
331
|
+
breakthrough: breakthrough,
|
|
332
|
+
more_count: typeof opts.more_count === 'number' ? opts.more_count : 0,
|
|
333
|
+
voice_line: voiceLine,
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
} catch (_e) {
|
|
337
|
+
rendered = null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Emit breakthrough_surfaced memory_event.
|
|
342
|
+
try {
|
|
343
|
+
navigation.logMemoryEvent(db, 'breakthrough_surfaced', {
|
|
344
|
+
breakthrough_id: breakthrough.id,
|
|
345
|
+
kind: typeof breakthrough.kind === 'string' ? breakthrough.kind : 'unknown',
|
|
346
|
+
confidence: typeof breakthrough.confidence === 'number' ? breakthrough.confidence : 0,
|
|
347
|
+
score: typeof breakthrough.score === 'number' ? breakthrough.score : null,
|
|
348
|
+
more_count: typeof opts.more_count === 'number' ? opts.more_count : 0,
|
|
349
|
+
source_path: 'system:breakthrough-scanner',
|
|
350
|
+
created_by: 'system',
|
|
351
|
+
});
|
|
352
|
+
} catch (_e) { /* swallow */ }
|
|
353
|
+
|
|
354
|
+
// Phase 121-02 D-07: capture breakthrough surfacing + F.7 verb + ethics tier
|
|
355
|
+
// + voice audit pass/fail into the unified events-YYYY-WNN.jsonl stream
|
|
356
|
+
// (Plan 121-00 writer chokepoint). The dismissal signal is valuable
|
|
357
|
+
// regardless of whether the voice audit passed (Test 3). Throttled-by-
|
|
358
|
+
// canary and provenance-blocked surfaces NEVER reach this point so they
|
|
359
|
+
// structurally cannot emit (Test 4 + scanForBreakthroughs upstream
|
|
360
|
+
// applyThrottleFilter).
|
|
361
|
+
//
|
|
362
|
+
// verb_chosen is populated from opts.verb_chosen when the caller pre-resolves
|
|
363
|
+
// the F.7 choice (the typical end-to-end path); falls back to '' when the
|
|
364
|
+
// surface is dispatched without a pre-selection. The dismissal vs accept
|
|
365
|
+
// outcome is later captured by selector_pick (D-04) when the F.7 user pick
|
|
366
|
+
// resolves. This event records the surfacing context + the proposed verb at
|
|
367
|
+
// the dispatch boundary.
|
|
368
|
+
//
|
|
369
|
+
// Non-blocking: try/catch swallows writer breaches; the scanner still
|
|
370
|
+
// returns ok:true to its caller.
|
|
371
|
+
try {
|
|
372
|
+
let writer = null;
|
|
373
|
+
try {
|
|
374
|
+
writer = require('../telemetry/writer.cjs');
|
|
375
|
+
} catch (_e) {
|
|
376
|
+
writer = null;
|
|
377
|
+
}
|
|
378
|
+
if (writer && typeof writer.emit === 'function') {
|
|
379
|
+
const detector = (typeof breakthrough.detector_type === 'string' && breakthrough.detector_type.length > 0)
|
|
380
|
+
? breakthrough.detector_type
|
|
381
|
+
: (typeof breakthrough.kind === 'string' ? breakthrough.kind : 'unknown');
|
|
382
|
+
const ethicsTier = (typeof breakthrough.ethics_tier === 'string' && breakthrough.ethics_tier.length > 0)
|
|
383
|
+
? breakthrough.ethics_tier
|
|
384
|
+
: 'NEUTRAL';
|
|
385
|
+
const voiceAudit = Boolean(breakthrough.voice_audit_pass);
|
|
386
|
+
const verbChosen = (opts && typeof opts.verb_chosen === 'string') ? opts.verb_chosen : '';
|
|
387
|
+
const slugSrc = (opts && typeof opts.roomDir === 'string' && opts.roomDir.length > 0)
|
|
388
|
+
? String(opts.roomDir).split('/').filter(Boolean).pop() || opts.roomDir
|
|
389
|
+
: (String(process.cwd()).split('/').filter(Boolean).pop() || 'default-room');
|
|
390
|
+
writer.emit('breakthrough_dismissed', {
|
|
391
|
+
detector_type: String(detector).slice(0, 64),
|
|
392
|
+
verb_chosen: String(verbChosen).slice(0, 64),
|
|
393
|
+
ethics_tier: String(ethicsTier).slice(0, 64),
|
|
394
|
+
voice_audit_pass: voiceAudit,
|
|
395
|
+
room_slug_sha256: _sha256Hex(slugSrc),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
} catch (_e) {
|
|
399
|
+
// Telemetry MUST never crash the surfacing pipeline.
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Flip Breakthrough node properties.surfaced = true (best-effort update).
|
|
403
|
+
try {
|
|
404
|
+
const row = db.prepare(
|
|
405
|
+
"SELECT properties FROM nodes WHERE id = ? AND type = 'breakthrough'"
|
|
406
|
+
).get(breakthrough.id);
|
|
407
|
+
if (row) {
|
|
408
|
+
let props = {};
|
|
409
|
+
try { props = JSON.parse(row.properties); } catch (_e) { props = {}; }
|
|
410
|
+
props.surfaced = true;
|
|
411
|
+
props.surfaced_at = Date.now();
|
|
412
|
+
db.prepare(
|
|
413
|
+
"UPDATE nodes SET properties = ?, last_seen_at = ? WHERE id = ?"
|
|
414
|
+
).run(JSON.stringify(props), Date.now(), breakthrough.id);
|
|
415
|
+
}
|
|
416
|
+
} catch (_e) { /* swallow */ }
|
|
417
|
+
|
|
418
|
+
return { ok: true, rendered: rendered, voice_line: voiceLine };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
module.exports = {
|
|
422
|
+
scanForBreakthroughs: scanForBreakthroughs,
|
|
423
|
+
surfaceBreakthrough: surfaceBreakthrough,
|
|
424
|
+
applyResurfacingFilter: applyResurfacingFilter,
|
|
425
|
+
applyThrottleFilter: applyThrottleFilter,
|
|
426
|
+
};
|