mindforge-cc 11.0.0 → 11.2.1
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/.agent/hooks/mindforge-statusline.js +2 -2
- package/.mindforge/config.json +14 -4
- package/CHANGELOG.md +137 -0
- package/MINDFORGE.md +5 -5
- package/RELEASENOTES.md +1 -1
- package/bin/autonomous/audit-writer.js +108 -86
- package/bin/autonomous/auto-runner.js +304 -19
- package/bin/autonomous/dependency-dag.js +59 -0
- package/bin/autonomous/mesh-self-healer.js +101 -28
- package/bin/autonomous/wave-executor.js +20 -1
- package/bin/browser/regression-writer.js +45 -3
- package/bin/browser/session-manager.js +21 -17
- package/bin/council-cli.js +161 -0
- package/bin/dashboard/approval-handler.js +3 -1
- package/bin/dashboard/server.js +1 -1
- package/bin/dashboard/sse-bridge.js +9 -12
- package/bin/engine/council-runtime.js +124 -0
- package/bin/engine/logic-drift-detector.js +14 -6
- package/bin/engine/logic-validator.js +155 -25
- package/bin/engine/orbital-guardian.js +56 -10
- package/bin/engine/otel-exporter.js +123 -0
- package/bin/engine/reason-source-aligner.js +19 -6
- package/bin/engine/remediation-engine.js +1 -1
- package/bin/engine/self-corrective-synthesizer.js +1 -1
- package/bin/engine/sre-manager.js +33 -6
- package/bin/engine/temporal-cli.js +4 -2
- package/bin/engine/verification-runner.js +131 -0
- package/bin/engine/verify-cli.js +34 -0
- package/bin/eval/eval-harness.js +82 -0
- package/bin/eval/golden-set-retrieval.json +46 -0
- package/bin/governance/audit-hash.js +12 -0
- package/bin/governance/audit-verifier.js +60 -0
- package/bin/governance/policy-engine.js +17 -4
- package/bin/governance/quantum-crypto.js +63 -9
- package/bin/governance/ztai-archiver.js +74 -9
- package/bin/governance/ztai-manager.js +33 -5
- package/bin/hindsight-injector.js +5 -6
- package/bin/hooks/instinct-capture-hook.js +186 -0
- package/bin/installer-core.js +31 -2
- package/bin/memory/auto-shadow.js +32 -3
- package/bin/memory/eis-client.js +45 -4
- package/bin/memory/identity-synthesizer.js +2 -2
- package/bin/memory/knowledge-store.js +30 -6
- package/bin/memory/retrieval-fusion.js +58 -0
- package/bin/memory/semantic-hub.js +2 -2
- package/bin/memory/vector-hub.js +143 -6
- package/bin/mindforge-cli.js +4 -5
- package/bin/models/anthropic-provider.js +13 -4
- package/bin/models/cost-tracker.js +3 -1
- package/bin/models/difficulty-scorer.js +54 -0
- package/bin/models/gemini-provider.js +6 -2
- package/bin/models/model-router.js +31 -18
- package/bin/models/openai-provider.js +6 -3
- package/bin/models/pricing-registry.js +128 -0
- package/bin/review/ads-engine.js +1 -1
- package/bin/review/finding-synthesizer.js +35 -6
- package/bin/security/trust-boundaries.js +194 -0
- package/bin/security/trust-gate-hook.js +49 -0
- package/bin/skill-registry.js +34 -22
- package/bin/skills-builder/marketplace-cli.js +5 -3
- package/bin/skills-builder/skill-registrar.js +4 -6
- package/bin/sre/sentinel.js +7 -5
- package/bin/sre/shadow-mirror.js +90 -40
- package/bin/utils/append-queue.js +67 -0
- package/bin/utils/file-io.js +29 -80
- package/bin/utils/version-check.js +75 -0
- package/bin/verify-audit.js +12 -0
- package/bin/wizard/theme.js +1 -2
- package/package.json +1 -1
- package/bin/dashboard/team-tracker.js +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const crypto = require('node:crypto');
|
|
7
7
|
const { promisify } = require('node:util');
|
|
8
|
+
const configManager = require('./config-manager');
|
|
8
9
|
|
|
9
10
|
const generateKeyPair = promisify(crypto.generateKeyPair);
|
|
10
11
|
|
|
@@ -64,7 +65,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
async generate(did) {
|
|
67
|
-
console.log(`[ZTAI-HSM] Provisioning
|
|
68
|
+
console.log(`[ZTAI-HSM-SIM] Provisioning simulated (in-process) identity enclave for ${did}...`);
|
|
68
69
|
const { publicKey, privateKey } = await generateKeyPair('ed25519');
|
|
69
70
|
const pubPEM = publicKey.export({ type: 'spki', format: 'pem' });
|
|
70
71
|
|
|
@@ -81,7 +82,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
81
82
|
const record = this.enclaveStore.get(did);
|
|
82
83
|
if (!record) throw new Error(`Enclave record not found for ${did}`);
|
|
83
84
|
|
|
84
|
-
console.log(`[ZTAI-HSM]
|
|
85
|
+
console.log(`[ZTAI-HSM-SIM] Signing via simulated in-process enclave (NOT a hardware HSM/TPM) [DID: ${did}]`);
|
|
85
86
|
|
|
86
87
|
// Simulate enclave "wrapping" or "sealing" logic
|
|
87
88
|
const signature = crypto.sign(null, Buffer.from(data), record.privateKey);
|
|
@@ -92,7 +93,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
async rotate(did) {
|
|
95
|
-
console.log(`[ZTAI-HSM] Rotating enclave keys for ${did}...`);
|
|
96
|
+
console.log(`[ZTAI-HSM-SIM] Rotating simulated enclave keys for ${did}...`);
|
|
96
97
|
return this.generate(did);
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -147,6 +148,34 @@ class ZTAIManager {
|
|
|
147
148
|
};
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Selects a key provider for a given trust tier.
|
|
153
|
+
*
|
|
154
|
+
* UC-24: All tiers route to REAL crypto by default — Tier 1-2 use the
|
|
155
|
+
* in-memory Ed25519 provider, Tier 3+ use the (simulated-HSM) enclave
|
|
156
|
+
* provider which also signs with REAL Ed25519. The SIMULATED post-quantum
|
|
157
|
+
* lattice provider ('quantum') is NEVER selected on the live trust path
|
|
158
|
+
* unless the operator has explicitly opted into the demo via
|
|
159
|
+
* experimental.pqc_demo. This keeps false-assurance signatures off the
|
|
160
|
+
* default trust path while preserving the demo for explicit exploration.
|
|
161
|
+
*
|
|
162
|
+
* @param {number} tier - Trust tier (1-4)
|
|
163
|
+
* @returns {'local'|'enclave'|'quantum'}
|
|
164
|
+
*/
|
|
165
|
+
_selectProvider(tier) {
|
|
166
|
+
const pqcDemo = configManager.get('experimental.pqc_demo', false) === true;
|
|
167
|
+
|
|
168
|
+
// Tier 4+ MAY use the simulated lattice provider, but only when the
|
|
169
|
+
// operator has explicitly enabled the PQC demo. Otherwise it falls back to
|
|
170
|
+
// the real-Ed25519 enclave provider so the trust path stays verifiable.
|
|
171
|
+
if (tier >= 4) {
|
|
172
|
+
return pqcDemo ? 'quantum' : 'enclave';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Tier 3 agents use the SecureEnclaveProvider (real Ed25519).
|
|
176
|
+
return tier >= 3 ? 'enclave' : 'local';
|
|
177
|
+
}
|
|
178
|
+
|
|
150
179
|
/**
|
|
151
180
|
* Registers a new agent and assigns a provider based on Trust Tier.
|
|
152
181
|
* @param {string} persona - Agent persona identifier
|
|
@@ -157,8 +186,7 @@ class ZTAIManager {
|
|
|
157
186
|
const uuid = crypto.randomUUID();
|
|
158
187
|
const did = `did:mindforge:${uuid}`;
|
|
159
188
|
|
|
160
|
-
|
|
161
|
-
const providerType = tier >= 3 ? 'enclave' : 'local';
|
|
189
|
+
const providerType = this._selectProvider(tier);
|
|
162
190
|
const provider = this.providers[providerType];
|
|
163
191
|
|
|
164
192
|
const publicKeyPEM = await provider.generate(did);
|
|
@@ -21,17 +21,16 @@ class HindsightInjector {
|
|
|
21
21
|
// 1. Rollback .planning directory
|
|
22
22
|
TemporalHub.rollbackTo(auditId);
|
|
23
23
|
|
|
24
|
-
// 2. Append the "Hindsight" event to AUDIT.jsonl
|
|
24
|
+
// 2. Append the "Hindsight" event to AUDIT.jsonl via the unified, hash-chained,
|
|
25
|
+
// durable append (UC-04b) so this entry links into the single verifiable chain.
|
|
26
|
+
const { appendAuditEntrySync } = require('./autonomous/audit-writer');
|
|
25
27
|
const auditPath = path.join(process.cwd(), '.planning', 'AUDIT.jsonl');
|
|
26
|
-
const hindsightEvent = {
|
|
27
|
-
id: require('crypto').randomBytes(8).toString('hex'),
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
28
|
+
const hindsightEvent = appendAuditEntrySync(auditPath, {
|
|
29
29
|
event: 'hindsight_injected',
|
|
30
30
|
target_id: auditId,
|
|
31
31
|
description: fixDescription,
|
|
32
32
|
agent: 'temporal-hub'
|
|
33
|
-
};
|
|
34
|
-
fs.appendFileSync(auditPath, JSON.stringify(hindsightEvent) + '\n');
|
|
33
|
+
});
|
|
35
34
|
|
|
36
35
|
// 3. Mark the state as "ready_for_regeneration"
|
|
37
36
|
const statePath = path.join(process.cwd(), '.planning', 'auto-state.json');
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* MindForge — Instinct Auto-Capture Hook (UC-11)
|
|
5
|
+
* Invoked as a PostToolUse hook. Reads hook event JSON from stdin,
|
|
6
|
+
* detects successful task completions, and appends lightweight instinct
|
|
7
|
+
* entries to the configured store path.
|
|
8
|
+
*
|
|
9
|
+
* Session capture limit is enforced via a temp counter file to avoid
|
|
10
|
+
* flooding the store with low-signal entries.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
// ── Configuration ────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const CONFIG_PATH = path.join(process.cwd(), '.mindforge', 'config.json');
|
|
21
|
+
const SESSION_ID = process.env.MINDFORGE_SESSION_ID || process.ppid || 'default';
|
|
22
|
+
const SESSION_COUNTER_PATH = path.join(
|
|
23
|
+
os.tmpdir(),
|
|
24
|
+
`mindforge-instinct-session-${SESSION_ID}.count`
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
try {
|
|
29
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getSessionCount() {
|
|
37
|
+
try {
|
|
38
|
+
const raw = fs.readFileSync(SESSION_COUNTER_PATH, 'utf8');
|
|
39
|
+
return parseInt(raw, 10) || 0;
|
|
40
|
+
} catch {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function incrementSessionCount() {
|
|
46
|
+
const current = getSessionCount();
|
|
47
|
+
fs.writeFileSync(SESSION_COUNTER_PATH, String(current + 1));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Success Detection ────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function isSuccessfulCompletion(payload) {
|
|
53
|
+
const tool = (payload.tool_name || payload.tool || '').toLowerCase();
|
|
54
|
+
|
|
55
|
+
// Bash tool with exit code 0
|
|
56
|
+
if (tool === 'bash') {
|
|
57
|
+
const exitCode = payload.exit_code ?? payload.result?.exit_code ?? null;
|
|
58
|
+
if (exitCode === 0) return true;
|
|
59
|
+
// If no explicit exit code but has output and no error marker
|
|
60
|
+
if (exitCode === null && payload.output && !payload.error) return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Task tool with completed status
|
|
65
|
+
if (tool === 'task') {
|
|
66
|
+
const status = (payload.status || payload.result?.status || '').toLowerCase();
|
|
67
|
+
return status === 'completed' || status === 'done';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Pattern Extraction ───────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
function extractPattern(payload) {
|
|
76
|
+
const tool = (payload.tool_name || payload.tool || '').toLowerCase();
|
|
77
|
+
|
|
78
|
+
if (tool === 'bash') {
|
|
79
|
+
const command = payload.command || payload.input?.command || payload.tool_input?.command || '';
|
|
80
|
+
if (!command || command.length < 5) return null;
|
|
81
|
+
// Skip trivial commands
|
|
82
|
+
if (/^(ls|pwd|echo|cat|cd)\b/.test(command.trim())) return null;
|
|
83
|
+
return {
|
|
84
|
+
observation: `Bash command succeeded: ${command.slice(0, 200)}`,
|
|
85
|
+
behavior: `Use pattern: ${command.slice(0, 200)}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (tool === 'task') {
|
|
90
|
+
const description = payload.description || payload.task_description || payload.name || '';
|
|
91
|
+
if (!description) return null;
|
|
92
|
+
return {
|
|
93
|
+
observation: `Task completed successfully: ${description.slice(0, 200)}`,
|
|
94
|
+
behavior: `Reuse approach for similar tasks: ${description.slice(0, 200)}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
function main() {
|
|
104
|
+
const config = loadConfig();
|
|
105
|
+
if (!config || !config.instincts) {
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { mode, max_capture_per_session, store_path } = config.instincts;
|
|
110
|
+
if (mode !== 'auto-capture') {
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check session limit
|
|
115
|
+
const sessionCount = getSessionCount();
|
|
116
|
+
if (sessionCount >= (max_capture_per_session || 5)) {
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Read stdin (hook payload)
|
|
121
|
+
let input = '';
|
|
122
|
+
try {
|
|
123
|
+
input = fs.readFileSync(0, 'utf8');
|
|
124
|
+
} catch {
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!input.trim()) {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let payload;
|
|
133
|
+
try {
|
|
134
|
+
payload = JSON.parse(input);
|
|
135
|
+
} catch {
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if this is a successful completion
|
|
140
|
+
if (!isSuccessfulCompletion(payload)) {
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Extract pattern
|
|
145
|
+
const pattern = extractPattern(payload);
|
|
146
|
+
if (!pattern) {
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build instinct entry
|
|
151
|
+
const entry = {
|
|
152
|
+
id: `inst-${crypto.randomUUID()}`,
|
|
153
|
+
created_at: new Date().toISOString(),
|
|
154
|
+
updated_at: new Date().toISOString(),
|
|
155
|
+
observation: pattern.observation,
|
|
156
|
+
behavior: pattern.behavior,
|
|
157
|
+
confidence: 0.3,
|
|
158
|
+
times_applied: 0,
|
|
159
|
+
times_succeeded: 0,
|
|
160
|
+
times_failed: 0,
|
|
161
|
+
project: 'mindforge',
|
|
162
|
+
tags: [],
|
|
163
|
+
status: 'active',
|
|
164
|
+
promoted_to_skill: null,
|
|
165
|
+
last_applied_at: null,
|
|
166
|
+
source: 'auto-capture',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Write to store
|
|
170
|
+
const storePath = path.resolve(process.cwd(), store_path);
|
|
171
|
+
const storeDir = path.dirname(storePath);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
if (!fs.existsSync(storeDir)) {
|
|
175
|
+
fs.mkdirSync(storeDir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
fs.appendFileSync(storePath, JSON.stringify(entry) + '\n');
|
|
178
|
+
incrementSessionCount();
|
|
179
|
+
} catch {
|
|
180
|
+
// Non-fatal — hooks must not block
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main();
|
package/bin/installer-core.js
CHANGED
|
@@ -95,6 +95,25 @@ const RUNTIMES = {
|
|
|
95
95
|
},
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Reads the target project's experimental.pqc_demo flag — the SINGLE gate that
|
|
100
|
+
* the engine (bin/governance/quantum-crypto.js) uses to enable the simulated
|
|
101
|
+
* PQAS minter. Defaults to false (engine default) when the config is absent or
|
|
102
|
+
* unreadable, so the installer never over-claims that PQAS is enabled.
|
|
103
|
+
* @param {string} cwd - Target project root being installed into.
|
|
104
|
+
* @returns {boolean} - true only when experimental.pqc_demo === true.
|
|
105
|
+
*/
|
|
106
|
+
function isPqcDemoEnabled(cwd) {
|
|
107
|
+
try {
|
|
108
|
+
const cfgPath = path.join(cwd, '.mindforge', 'config.json');
|
|
109
|
+
if (!fs.existsSync(cfgPath)) return false;
|
|
110
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
111
|
+
return cfg && cfg.experimental && cfg.experimental.pqc_demo === true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
/**
|
|
99
118
|
* Generates runtime-specific entry file content.
|
|
100
119
|
* e.g. replacing "Claude" with "Gemini" in GEMINI.md
|
|
@@ -650,9 +669,19 @@ async function install(runtime, scope, options = {}) {
|
|
|
650
669
|
}
|
|
651
670
|
});
|
|
652
671
|
|
|
653
|
-
// ✨ SOVEREIGN INITIALIZATION:
|
|
672
|
+
// ✨ SOVEREIGN INITIALIZATION: report actual security posture honestly.
|
|
673
|
+
// The PQAS minter is gated SOLELY behind experimental.pqc_demo (see
|
|
674
|
+
// bin/governance/quantum-crypto.js: getProvider/_assertPqcDemoEnabled). When
|
|
675
|
+
// that flag is off (the default) PQAS is inert/simulated — claiming it is
|
|
676
|
+
// "enabled" would contradict the engine and mislead operators (UC-22).
|
|
654
677
|
Theme.printStatus(c.magenta('Sovereign Intelligence v8.2.0 activated'), 'done');
|
|
655
|
-
|
|
678
|
+
if (isPqcDemoEnabled(process.cwd())) {
|
|
679
|
+
Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): SIMULATED demo ENABLED '
|
|
680
|
+
+ '(experimental.pqc_demo=true — simulated lattice crypto, NOT production trust)'), 'info');
|
|
681
|
+
} else {
|
|
682
|
+
Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): available in simulated/experimental '
|
|
683
|
+
+ 'mode (inactive by default — set experimental.pqc_demo=true to enable the simulated demo)'), 'info');
|
|
684
|
+
}
|
|
656
685
|
Theme.printStatus(c.dim(' - Proactive Semantic Intent Harvesting active'), 'info');
|
|
657
686
|
|
|
658
687
|
// bin/ utilities (remaining non-engine scripts)
|
|
@@ -18,6 +18,8 @@ const path = require('path');
|
|
|
18
18
|
const Store = require('./knowledge-store');
|
|
19
19
|
const Graph = require('./knowledge-graph');
|
|
20
20
|
const Embedder = require('./embedding-engine');
|
|
21
|
+
const Indexer = require('./knowledge-indexer');
|
|
22
|
+
const { fuseResults } = require('./retrieval-fusion');
|
|
21
23
|
|
|
22
24
|
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
23
25
|
const MAX_SHADOW_CHARS = 8000; // ~2KB tokens
|
|
@@ -63,13 +65,40 @@ function generateShadowContext(opts = {}) {
|
|
|
63
65
|
|
|
64
66
|
const { vectors, df, N } = Embedder.buildEmbeddings(activeEntries);
|
|
65
67
|
|
|
66
|
-
// 2.
|
|
68
|
+
// 2. Multi-path retrieval with RRF fusion (UC-20)
|
|
69
|
+
// Path 1: Knowledge Graph (embedding + graph traversal)
|
|
70
|
+
// Path 2: Knowledge Indexer (BM25 + confidence)
|
|
71
|
+
// Results are fused via Reciprocal Rank Fusion for scale-free merging.
|
|
67
72
|
const queryText = `${taskDescription} ${techStack.join(' ')}`;
|
|
68
|
-
const
|
|
73
|
+
const fetchK = maxItems * 3; // Over-fetch for filtering headroom
|
|
74
|
+
|
|
75
|
+
const graphResults = Graph.findRelated(queryText, vectors, df, N, {
|
|
69
76
|
maxHops: 2,
|
|
70
|
-
topK:
|
|
77
|
+
topK: fetchK,
|
|
71
78
|
});
|
|
72
79
|
|
|
80
|
+
let indexerResults = [];
|
|
81
|
+
try {
|
|
82
|
+
const rawIndexer = Indexer.search(queryText, { includeGlobal: true }, fetchK);
|
|
83
|
+
indexerResults = rawIndexer.map((entry, rank) => ({
|
|
84
|
+
id: entry.id,
|
|
85
|
+
score: entry.confidence || 0,
|
|
86
|
+
source: 'indexer',
|
|
87
|
+
}));
|
|
88
|
+
} catch {
|
|
89
|
+
// Indexer may fail on empty store — non-fatal
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// RRF fusion: merge both ranked lists by ordinal position
|
|
93
|
+
const fusedResults = fuseResults([graphResults, indexerResults]);
|
|
94
|
+
|
|
95
|
+
// Map fused results back to the legacy shape expected downstream
|
|
96
|
+
const related = fusedResults.map(item => ({
|
|
97
|
+
id: item.id,
|
|
98
|
+
score: item.rrfScore, // RRF score replaces incomparable linear blends
|
|
99
|
+
source: item.source || 'fused',
|
|
100
|
+
}));
|
|
101
|
+
|
|
73
102
|
// 3. Filter and enrich results
|
|
74
103
|
const excludeSet = new Set(excludeIds);
|
|
75
104
|
const enriched = [];
|
package/bin/memory/eis-client.js
CHANGED
|
@@ -100,10 +100,48 @@ class EISClient {
|
|
|
100
100
|
return [];
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Verifies the provenance of a remote knowledge entry by cryptographically
|
|
105
|
+
* checking its signature against the signer DID's registered public key.
|
|
106
|
+
*
|
|
107
|
+
* HONEST / FAIL-CLOSED CONTRACT (UC-22, finding #22): this method NEVER
|
|
108
|
+
* returns true for a signature it has not actually verified. It returns true
|
|
109
|
+
* ONLY when ZTAI.verifySignature confirms the signature against a public key
|
|
110
|
+
* that is resolvable in the local trust registry. Every other case fails
|
|
111
|
+
* closed → false:
|
|
112
|
+
* - no/empty signature,
|
|
113
|
+
* - no signer DID on the entry,
|
|
114
|
+
* - the DID is not resolvable here (e.g. a genuinely remote peer whose key
|
|
115
|
+
* is not in the local registry — there is no remote DID-resolution infra
|
|
116
|
+
* yet, see resolveRemoteNode),
|
|
117
|
+
* - tampered payload or signature (crypto.verify returns false / throws).
|
|
118
|
+
*
|
|
119
|
+
* @param {{did?: string, signedData?: string}} entry - Provenance-bearing entry.
|
|
120
|
+
* `did` is the signer's DID; `signedData` is the exact canonical bytes that
|
|
121
|
+
* were signed (defaults to a deterministic JSON of the entry if absent).
|
|
122
|
+
* @param {string} signature - Base64 signature to verify.
|
|
123
|
+
* @returns {boolean} true only if cryptographically verified; false otherwise.
|
|
124
|
+
*/
|
|
104
125
|
verifyRemoteProvenance(entry, signature) {
|
|
105
|
-
if (!signature) return false;
|
|
106
|
-
return
|
|
126
|
+
if (!signature || typeof signature !== 'string') return false;
|
|
127
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
128
|
+
|
|
129
|
+
const did = entry.did;
|
|
130
|
+
if (!did || typeof did !== 'string') return false;
|
|
131
|
+
|
|
132
|
+
// Canonical signed bytes: prefer an explicit signedData field, else a
|
|
133
|
+
// deterministic JSON of the entry (excluding the signature envelope).
|
|
134
|
+
const signedData = typeof entry.signedData === 'string'
|
|
135
|
+
? entry.signedData
|
|
136
|
+
: JSON.stringify(entry);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// ZTAI.verifySignature throws for an unresolvable (unregistered) DID —
|
|
140
|
+
// treat that as fail-closed rather than asserting verified provenance.
|
|
141
|
+
return ZTAI.verifySignature(did, signedData, signature) === true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
107
145
|
}
|
|
108
146
|
|
|
109
147
|
// TODO: implement when remote nodes are available
|
|
@@ -113,7 +151,10 @@ class EISClient {
|
|
|
113
151
|
|
|
114
152
|
/**
|
|
115
153
|
* [HARDEN] Generates a cryptographically signed auth header using the agent's DID.
|
|
116
|
-
* This
|
|
154
|
+
* This attaches OUTBOUND provenance to locally-originated requests (it signs
|
|
155
|
+
* what this node sends). It does NOT verify the provenance of inbound remote
|
|
156
|
+
* entries — that is verifyRemoteProvenance's job, which fails closed unless a
|
|
157
|
+
* signature is cryptographically verified against a resolvable public key.
|
|
117
158
|
*/
|
|
118
159
|
async getAuthHeader(action, resource) {
|
|
119
160
|
const manager = new ZTAI();
|
|
@@ -25,7 +25,7 @@ class IdentitySynthesizer {
|
|
|
25
25
|
.replace(/{PROJECT_OBJECTIVE}/g, answers.goal || 'Maximizing engineering leverage');
|
|
26
26
|
|
|
27
27
|
await fs.writeFile(this.soulPath, soulContent);
|
|
28
|
-
console.log(
|
|
28
|
+
console.log('[IDENTITY] SOUL.md bootstrapped successfully from the Grand Blueprint.');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -41,7 +41,7 @@ class IdentitySynthesizer {
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
if (traces.length === 0) {
|
|
44
|
-
console.log(
|
|
44
|
+
console.log('[IDENTITY] No execution traces found in celestial.db. Evolution skipped.');
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -15,6 +15,30 @@ const path = require('path');
|
|
|
15
15
|
const os = require('os');
|
|
16
16
|
const crypto = require('crypto');
|
|
17
17
|
|
|
18
|
+
// ── Durable append (UC-09) ──────────────────────────────────────────────────
|
|
19
|
+
// The knowledge-store public API (add/deprecate/reinforce) is SYNCHRONOUS and
|
|
20
|
+
// callers read-after-write synchronously (e.g. `const id = Store.add(...)`
|
|
21
|
+
// immediately followed by `Store.readAll()`). Routing through the async
|
|
22
|
+
// append-queue would make those reads observe stale data and would require an
|
|
23
|
+
// API change across 9+ consumers — out of scope for UC-09.
|
|
24
|
+
//
|
|
25
|
+
// Instead we centralize every append through one durable, fsync'd, synchronous
|
|
26
|
+
// writer. This delivers UC-09's durability guarantee (acknowledged writes are on
|
|
27
|
+
// disk before the call returns) and a single serialized append path per file,
|
|
28
|
+
// while preserving the synchronous read-after-write contract. appendFileSync's
|
|
29
|
+
// per-call append is atomic on POSIX, so concurrent in-process appends do not
|
|
30
|
+
// interleave at the byte level.
|
|
31
|
+
function appendDurableSync(filePath, line) {
|
|
32
|
+
const record = line.endsWith('\n') ? line : line + '\n';
|
|
33
|
+
const fd = fs.openSync(filePath, 'a');
|
|
34
|
+
try {
|
|
35
|
+
fs.writeSync(fd, record);
|
|
36
|
+
fs.fsyncSync(fd);
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
// ── ID Index for fast lookups (built lazily, invalidated on writes) ───────────
|
|
19
43
|
let _idIndex = null; // Map<id, entry> — latest version per ID
|
|
20
44
|
let _indexDirty = true; // Invalidated whenever entries are appended
|
|
@@ -201,12 +225,12 @@ function add(entry) {
|
|
|
201
225
|
|
|
202
226
|
const filePath = getFilePath(entry.type);
|
|
203
227
|
verifyFileIntegrity(filePath);
|
|
204
|
-
|
|
228
|
+
appendDurableSync(filePath, JSON.stringify(full));
|
|
205
229
|
|
|
206
230
|
// Also append to unified knowledge-base.jsonl for cross-type queries
|
|
207
231
|
if (filePath !== paths.KB_PATH) {
|
|
208
232
|
verifyFileIntegrity(paths.KB_PATH);
|
|
209
|
-
|
|
233
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(full));
|
|
210
234
|
}
|
|
211
235
|
|
|
212
236
|
_invalidateIndex();
|
|
@@ -236,10 +260,10 @@ function deprecate(id, reason, supersededBy = null) {
|
|
|
236
260
|
};
|
|
237
261
|
|
|
238
262
|
verifyFileIntegrity(filePath);
|
|
239
|
-
|
|
263
|
+
appendDurableSync(filePath, JSON.stringify(deprecated));
|
|
240
264
|
if (filePath !== paths.KB_PATH) {
|
|
241
265
|
verifyFileIntegrity(paths.KB_PATH);
|
|
242
|
-
|
|
266
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(deprecated));
|
|
243
267
|
}
|
|
244
268
|
|
|
245
269
|
_invalidateIndex();
|
|
@@ -267,10 +291,10 @@ function reinforce(id) {
|
|
|
267
291
|
|
|
268
292
|
const filePath = getFilePath(entry.type);
|
|
269
293
|
verifyFileIntegrity(filePath);
|
|
270
|
-
|
|
294
|
+
appendDurableSync(filePath, JSON.stringify(reinforced));
|
|
271
295
|
if (filePath !== paths.KB_PATH) {
|
|
272
296
|
verifyFileIntegrity(paths.KB_PATH);
|
|
273
|
-
|
|
297
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(reinforced));
|
|
274
298
|
}
|
|
275
299
|
|
|
276
300
|
_invalidateIndex();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* MindForge — Reciprocal Rank Fusion (UC-20).
|
|
4
|
+
* Merges multiple ranked lists using scale-free RRF scoring.
|
|
5
|
+
*
|
|
6
|
+
* RRF eliminates the need for score normalization across retrieval paths
|
|
7
|
+
* with incomparable scoring functions (embedding similarity, BM25, graph
|
|
8
|
+
* traversal, FTS rank). Only ordinal rank matters, not score magnitude.
|
|
9
|
+
*
|
|
10
|
+
* Formula:
|
|
11
|
+
* rrfScore(item) = SUM( 1 / (K + rank_i) ) for all lists containing the item
|
|
12
|
+
*
|
|
13
|
+
* Where K=60 is the standard constant from the original RRF paper
|
|
14
|
+
* (Cormack, Clarke, Butt — 2009).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const K = 60; // Standard RRF constant — dampens the influence of high ranks
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fuse multiple ranked result lists using Reciprocal Rank Fusion.
|
|
21
|
+
*
|
|
22
|
+
* Each list is an array of objects with at least an `id` field.
|
|
23
|
+
* Items appearing in multiple lists accumulate RRF score and rank higher.
|
|
24
|
+
*
|
|
25
|
+
* @param {Array<Array<{id: string, [key: string]: any}>>} rankedLists
|
|
26
|
+
* Array of ranked lists. Each list is ordered by relevance (index 0 = most relevant).
|
|
27
|
+
* @returns {Array<{id: string, rrfScore: number, [key: string]: any}>}
|
|
28
|
+
* Fused results sorted by RRF score descending. Each item retains its
|
|
29
|
+
* original properties from the first list it appeared in.
|
|
30
|
+
*/
|
|
31
|
+
function fuseResults(rankedLists) {
|
|
32
|
+
if (!rankedLists || rankedLists.length === 0) return [];
|
|
33
|
+
|
|
34
|
+
const scores = new Map(); // id -> merged item with rrfScore
|
|
35
|
+
|
|
36
|
+
for (const list of rankedLists) {
|
|
37
|
+
if (!Array.isArray(list)) continue;
|
|
38
|
+
|
|
39
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
40
|
+
const item = list[rank];
|
|
41
|
+
if (!item || !item.id) continue;
|
|
42
|
+
|
|
43
|
+
const id = item.id;
|
|
44
|
+
const rrfContribution = 1 / (K + rank + 1); // rank is 0-based, +1 makes it 1-based
|
|
45
|
+
|
|
46
|
+
if (scores.has(id)) {
|
|
47
|
+
const existing = scores.get(id);
|
|
48
|
+
existing.rrfScore += rrfContribution;
|
|
49
|
+
} else {
|
|
50
|
+
scores.set(id, { ...item, rrfScore: rrfContribution });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [...scores.values()].sort((a, b) => b.rrfScore - a.rrfScore);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { fuseResults, K };
|
|
@@ -83,7 +83,7 @@ class SemanticHub {
|
|
|
83
83
|
try {
|
|
84
84
|
const data = await fs.readFile(this.syncManifest, 'utf8');
|
|
85
85
|
manifest = JSON.parse(data);
|
|
86
|
-
} catch (e) {}
|
|
86
|
+
} catch (e) { /* intentionally empty */ }
|
|
87
87
|
|
|
88
88
|
manifest[libraryName] = {
|
|
89
89
|
lastSync: new Date().toISOString(),
|
|
@@ -106,7 +106,7 @@ class SemanticHub {
|
|
|
106
106
|
sqliteTraces = await vectorHub.searchTraces(skillFilter);
|
|
107
107
|
} else {
|
|
108
108
|
sqliteTraces = vectorHub.query(
|
|
109
|
-
|
|
109
|
+
'SELECT * FROM traces WHERE event = ? LIMIT 20',
|
|
110
110
|
['reasoning_trace']
|
|
111
111
|
);
|
|
112
112
|
}
|