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
|
@@ -1,67 +1,197 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MindForge v7 — Neural Drift Remediation (NDR)
|
|
3
3
|
* Component: Logic Validator
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Performs high-level semantic validation on agent reasoning traces.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* Strategy: real-when-available, else honest heuristic.
|
|
8
|
+
* - By DEFAULT this validator uses a local Self-Reflective Heuristic
|
|
9
|
+
* (`_reflectiveHeuristic`). This is the standard path and runs everywhere,
|
|
10
|
+
* with no external dependency.
|
|
11
|
+
* - OPTIONALLY, if a local Ollama model is actually reachable at the
|
|
12
|
+
* configured endpoint, validation is upgraded to a real model call
|
|
13
|
+
* (`_modelValidation`). Reachability is determined by a real, fail-fast
|
|
14
|
+
* network probe — never a hardcoded flag. When Ollama is absent (the
|
|
15
|
+
* normal case) the probe fails fast and we fall back to the heuristic.
|
|
16
|
+
*
|
|
17
|
+
* The return shape is stable: { is_valid, confidence, critique, method }.
|
|
18
|
+
* Consumers (nexus-tracer) read `is_valid` and `critique`.
|
|
7
19
|
*/
|
|
8
20
|
'use strict';
|
|
9
21
|
|
|
10
22
|
const configManager = require('../governance/config-manager');
|
|
11
23
|
|
|
24
|
+
// Fail-fast budget for the reachability probe and the model call. Ollama is
|
|
25
|
+
// usually absent, so this must time out quickly to avoid hanging CI/production.
|
|
26
|
+
const PROBE_TIMEOUT_MS = 400;
|
|
27
|
+
const MODEL_TIMEOUT_MS = 4000;
|
|
28
|
+
|
|
12
29
|
class LogicValidator {
|
|
13
30
|
constructor() {
|
|
14
31
|
this.endpoint = configManager.get('governance.local_model_endpoint', 'localhost:11434');
|
|
15
|
-
this.
|
|
32
|
+
this.model = configManager.get('governance.local_model_name', 'llama3');
|
|
33
|
+
// Reflects reality: set by probeModel(), not hardcoded. Unknown until probed.
|
|
34
|
+
this.isModelAvailable = false;
|
|
35
|
+
this._probed = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Normalises the configured endpoint into a base URL (adds scheme if absent).
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
_baseUrl() {
|
|
43
|
+
const ep = String(this.endpoint || 'localhost:11434').trim();
|
|
44
|
+
return /^https?:\/\//i.test(ep) ? ep.replace(/\/+$/, '') : `http://${ep.replace(/\/+$/, '')}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resets cached probe state (used by tests to re-probe after changing endpoint).
|
|
49
|
+
*/
|
|
50
|
+
resetProbe() {
|
|
51
|
+
this._probed = false;
|
|
52
|
+
this.isModelAvailable = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Real, fail-fast reachability check for a local Ollama instance.
|
|
57
|
+
* Performs a short GET to the Ollama tags endpoint. On ANY error or timeout
|
|
58
|
+
* (the normal case when Ollama is absent) it resolves `false` — never throws,
|
|
59
|
+
* never hangs. Sets `this.isModelAvailable` from the actual result.
|
|
60
|
+
* @returns {Promise<boolean>}
|
|
61
|
+
*/
|
|
62
|
+
async probeModel() {
|
|
63
|
+
let reachable = false;
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(`${this._baseUrl()}/api/tags`, {
|
|
66
|
+
method: 'GET',
|
|
67
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
68
|
+
});
|
|
69
|
+
reachable = res.ok;
|
|
70
|
+
} catch {
|
|
71
|
+
// ECONNREFUSED / timeout / DNS / abort — Ollama not reachable. Stay quiet.
|
|
72
|
+
reachable = false;
|
|
73
|
+
}
|
|
74
|
+
this.isModelAvailable = reachable;
|
|
75
|
+
this._probed = true;
|
|
76
|
+
return reachable;
|
|
16
77
|
}
|
|
17
78
|
|
|
18
79
|
/**
|
|
19
80
|
* Validates a reasoning trace using the best available method.
|
|
81
|
+
* Probes for a local model on first call (lazy); falls back to the heuristic
|
|
82
|
+
* when unreachable.
|
|
20
83
|
* @param {string} thought - The agent's thought string
|
|
21
84
|
* @param {Object} context - Optional metadata (span attributes, etc.)
|
|
22
85
|
*/
|
|
23
86
|
async validate(thought, context = {}) {
|
|
24
|
-
|
|
87
|
+
const spanTag = context && context.span_id ? ` span=${context.span_id}` : '';
|
|
88
|
+
console.log(`[LogicValidator] Validating trace segment (Length: ${thought.length})${spanTag}`);
|
|
89
|
+
|
|
90
|
+
if (!this._probed) {
|
|
91
|
+
await this.probeModel();
|
|
92
|
+
}
|
|
25
93
|
|
|
26
|
-
// In a real v7 deployment, we would perform an asynchronous fetch to Ollama/Llama-CPP
|
|
27
|
-
// For this simulation, we simulate a "Reflective Heuristic" analysis.
|
|
28
|
-
|
|
29
94
|
if (this.isModelAvailable) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
95
|
+
try {
|
|
96
|
+
return await this._modelValidation(thought);
|
|
97
|
+
} catch {
|
|
98
|
+
// Model became unreachable mid-flight — degrade honestly to heuristic.
|
|
99
|
+
this.isModelAvailable = false;
|
|
100
|
+
return this._reflectiveHeuristic(thought);
|
|
101
|
+
}
|
|
33
102
|
}
|
|
103
|
+
return this._reflectiveHeuristic(thought);
|
|
34
104
|
}
|
|
35
105
|
|
|
36
106
|
/**
|
|
37
|
-
*
|
|
107
|
+
* Real Local Model Validation via Ollama's /api/generate.
|
|
108
|
+
* Asks the model whether the thought is logical and grounded, then derives a
|
|
109
|
+
* real is_valid/confidence from the response — no fabricated fixed values.
|
|
38
110
|
*/
|
|
39
|
-
async _modelValidation(thought
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
confidence: 0
|
|
44
|
-
critique: '
|
|
45
|
-
|
|
111
|
+
async _modelValidation(thought) {
|
|
112
|
+
const prompt =
|
|
113
|
+
'You are a reasoning-trace auditor. Decide whether the following agent ' +
|
|
114
|
+
'thought is logical and grounded (consistent, on-task, no self-contradiction).\n' +
|
|
115
|
+
'Reply with ONLY a JSON object: {"valid": <true|false>, "confidence": <0..1>, ' +
|
|
116
|
+
'"critique": "<short reason>"}.\n\n' +
|
|
117
|
+
`Thought: """${thought}"""`;
|
|
118
|
+
|
|
119
|
+
const res = await fetch(`${this._baseUrl()}/api/generate`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({ model: this.model, prompt, stream: false }),
|
|
123
|
+
signal: AbortSignal.timeout(MODEL_TIMEOUT_MS)
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
throw new Error(`Ollama responded ${res.status}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const payload = await res.json();
|
|
131
|
+
const parsed = this._parseModelResponse(payload && payload.response);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
is_valid: parsed.valid,
|
|
135
|
+
confidence: parsed.confidence,
|
|
136
|
+
critique: parsed.critique,
|
|
137
|
+
method: `ollama:${this.model}`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Robustly parses the model's textual response into a verdict. Falls back to
|
|
143
|
+
* conservative defaults derived from the raw text when JSON is unavailable —
|
|
144
|
+
* never invents a fixed high-confidence pass.
|
|
145
|
+
* @param {string} raw
|
|
146
|
+
*/
|
|
147
|
+
_parseModelResponse(raw) {
|
|
148
|
+
const text = String(raw || '');
|
|
149
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
150
|
+
if (match) {
|
|
151
|
+
try {
|
|
152
|
+
const obj = JSON.parse(match[0]);
|
|
153
|
+
const valid = obj.valid === true || obj.valid === 'true';
|
|
154
|
+
let confidence = Number(obj.confidence);
|
|
155
|
+
if (!Number.isFinite(confidence)) confidence = valid ? 0.6 : 0.4;
|
|
156
|
+
confidence = Math.min(1, Math.max(0, confidence));
|
|
157
|
+
const critique = typeof obj.critique === 'string' && obj.critique.trim()
|
|
158
|
+
? obj.critique.trim()
|
|
159
|
+
: (valid ? 'Model judged the thought logical and grounded.'
|
|
160
|
+
: 'Model flagged the thought as illogical or ungrounded.');
|
|
161
|
+
return { valid, confidence, critique };
|
|
162
|
+
} catch {
|
|
163
|
+
// fall through to text heuristic below
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// No parseable JSON: derive a conservative verdict from the raw text.
|
|
168
|
+
const lowered = text.toLowerCase();
|
|
169
|
+
const valid = !/(invalid|illogical|not\s+grounded|inconsistent|"valid"\s*:\s*false)/.test(lowered)
|
|
170
|
+
&& /(valid|logical|grounded|consistent)/.test(lowered);
|
|
171
|
+
return {
|
|
172
|
+
valid,
|
|
173
|
+
confidence: valid ? 0.55 : 0.45,
|
|
174
|
+
critique: 'Model response was unstructured; verdict derived from text.'
|
|
46
175
|
};
|
|
47
|
-
return result;
|
|
48
176
|
}
|
|
49
177
|
|
|
50
178
|
/**
|
|
51
|
-
*
|
|
179
|
+
* Local Self-Reflective Heuristic — the default validation path. More
|
|
180
|
+
* intensive than the DriftDetector; uses self-doubt and goal-misalignment
|
|
181
|
+
* markers. Honestly labelled as a heuristic (no model is involved here).
|
|
52
182
|
*/
|
|
53
|
-
async _reflectiveHeuristic(thought
|
|
54
|
-
const t = thought.toLowerCase();
|
|
55
|
-
|
|
183
|
+
async _reflectiveHeuristic(thought) {
|
|
184
|
+
const t = String(thought || '').toLowerCase();
|
|
185
|
+
|
|
56
186
|
// Check for "Self-Doubt" markers that might indicate drift
|
|
57
187
|
const doubtMarkers = ['i am not sure', 'maybe i should wait', 'actually, i forgot', 'i will instead try to just'];
|
|
58
188
|
const doubtCount = doubtMarkers.filter(m => t.includes(m)).length;
|
|
59
189
|
|
|
60
|
-
// Check for "Goal Misalignment"
|
|
190
|
+
// Check for "Goal Misalignment"
|
|
61
191
|
const goalMismatch = t.includes('ignoring current goal') || t.includes('outside scope');
|
|
62
192
|
|
|
63
193
|
const score = 1.0 - (doubtCount * 0.2) - (goalMismatch ? 0.5 : 0);
|
|
64
|
-
|
|
194
|
+
|
|
65
195
|
return {
|
|
66
196
|
is_valid: score > 0.6,
|
|
67
197
|
confidence: parseFloat(score.toFixed(2)),
|
|
@@ -35,26 +35,43 @@ class OrbitalGuardian {
|
|
|
35
35
|
throw new Error(`[ORBITAL-GUARDIAN] DID ${did} has insufficient Trust Tier for Orbital Attestation.`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// 1.
|
|
39
|
-
|
|
38
|
+
// 1. Build the EXACT canonical message and sign it with the agent's key.
|
|
39
|
+
// UC-22: this canonical string is persisted verbatim alongside the DID so
|
|
40
|
+
// verify() can re-verify the signature later. We must store the precise
|
|
41
|
+
// bytes that were signed — recomputing them (e.g. with a fresh timestamp)
|
|
42
|
+
// would never verify — so capture the message once, here, and reuse it.
|
|
43
|
+
const signedMessage = JSON.stringify({
|
|
40
44
|
requestId,
|
|
41
45
|
payload,
|
|
42
46
|
timestamp: new Date().toISOString()
|
|
43
|
-
})
|
|
47
|
+
});
|
|
48
|
+
const signature = await ztaiManager.signData(did, signedMessage);
|
|
44
49
|
|
|
45
50
|
const attestation = {
|
|
46
51
|
id: `att_${crypto.randomBytes(4).toString('hex')}`,
|
|
47
52
|
request_id: requestId,
|
|
48
53
|
status: 'APPROVED',
|
|
49
|
-
|
|
54
|
+
did,
|
|
55
|
+
signed_message: signedMessage,
|
|
56
|
+
attestation_payload: signature,
|
|
50
57
|
timestamp: new Date().toISOString()
|
|
51
58
|
};
|
|
52
59
|
|
|
53
|
-
// 2. Persist to SQLite (Source of truth for v8 Governance Dashboard)
|
|
60
|
+
// 2. Persist to SQLite (Source of truth for v8 Governance Dashboard).
|
|
61
|
+
// did + signed_message + signature together let verify() re-check the
|
|
62
|
+
// cryptographic signature; status='APPROVED' alone is NOT trusted.
|
|
54
63
|
vectorHub.run(
|
|
55
|
-
`INSERT INTO attestations (id, request_id, status, attestation_payload, timestamp)
|
|
56
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
57
|
-
[
|
|
64
|
+
`INSERT INTO attestations (id, request_id, status, did, signed_message, attestation_payload, timestamp)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
66
|
+
[
|
|
67
|
+
attestation.id,
|
|
68
|
+
attestation.request_id,
|
|
69
|
+
attestation.status,
|
|
70
|
+
attestation.did,
|
|
71
|
+
attestation.signed_message,
|
|
72
|
+
attestation.attestation_payload,
|
|
73
|
+
attestation.timestamp
|
|
74
|
+
]
|
|
58
75
|
);
|
|
59
76
|
|
|
60
77
|
console.log(`[ORBITAL-GUARDIAN] Attestation SUCCESS: ${attestation.id}`);
|
|
@@ -63,9 +80,16 @@ class OrbitalGuardian {
|
|
|
63
80
|
|
|
64
81
|
/**
|
|
65
82
|
* Verifies if a request has a valid hardware bypass.
|
|
83
|
+
*
|
|
84
|
+
* UC-22 (audit finding #2): an APPROVED row is NOT trusted on its own. The
|
|
85
|
+
* stored signature is re-verified against the signer's registered public key
|
|
86
|
+
* over the EXACT canonical message that attest() signed. Anyone who forges an
|
|
87
|
+
* APPROVED row but cannot produce a valid signature is rejected. The check is
|
|
88
|
+
* fail-closed: a missing field, an unregistered/revoked DID, or any thrown
|
|
89
|
+
* error all resolve to { verified:false }.
|
|
66
90
|
*/
|
|
67
91
|
async verify(requestId) {
|
|
68
|
-
if (!requestId) return { verified: false };
|
|
92
|
+
if (!requestId) return { verified: false, reason: 'missing requestId' };
|
|
69
93
|
await this.ensureInit();
|
|
70
94
|
|
|
71
95
|
const results = vectorHub.query(
|
|
@@ -74,7 +98,29 @@ class OrbitalGuardian {
|
|
|
74
98
|
);
|
|
75
99
|
|
|
76
100
|
const record = results[0];
|
|
77
|
-
if (!record) return { verified: false };
|
|
101
|
+
if (!record) return { verified: false, reason: 'no APPROVED attestation found' };
|
|
102
|
+
|
|
103
|
+
// Re-verify the cryptographic signature. Without a DID, the canonical signed
|
|
104
|
+
// message, AND a signature we cannot prove the row was produced by attest().
|
|
105
|
+
if (!record.did || !record.signed_message || !record.attestation_payload) {
|
|
106
|
+
return { verified: false, reason: 'attestation missing signature material' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let signatureValid = false;
|
|
110
|
+
try {
|
|
111
|
+
signatureValid = ztaiManager.verifySignature(
|
|
112
|
+
record.did,
|
|
113
|
+
record.signed_message,
|
|
114
|
+
record.attestation_payload
|
|
115
|
+
);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
// Unregistered/revoked DID or malformed signature → fail closed.
|
|
118
|
+
return { verified: false, reason: `signature verification error: ${err.message}` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!signatureValid) {
|
|
122
|
+
return { verified: false, reason: 'signature verification failed' };
|
|
123
|
+
}
|
|
78
124
|
|
|
79
125
|
return {
|
|
80
126
|
verified: true,
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* MindForge — OTel GenAI Exporter (UC-18).
|
|
4
|
+
* Translates NexusTracer spans to OpenTelemetry GenAI semantic conventions.
|
|
5
|
+
* Active only when OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
|
6
|
+
*
|
|
7
|
+
* NexusTracer span shape (from nexus-tracer.js startSpan/endSpan):
|
|
8
|
+
* {
|
|
9
|
+
* id: 'sp_<hex>',
|
|
10
|
+
* trace_id: 'tr_<hex>',
|
|
11
|
+
* parent_id: string|null,
|
|
12
|
+
* name: string,
|
|
13
|
+
* status: 'active'|'success'|'error',
|
|
14
|
+
* start_time: ISO-8601,
|
|
15
|
+
* end_time: ISO-8601,
|
|
16
|
+
* attributes: {
|
|
17
|
+
* service: string,
|
|
18
|
+
* host: string,
|
|
19
|
+
* pid: number,
|
|
20
|
+
* model_id?: string,
|
|
21
|
+
* skill?: string,
|
|
22
|
+
* input_tokens?: number,
|
|
23
|
+
* output_tokens?: number,
|
|
24
|
+
* ...
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* Mapping to OTel GenAI semantic conventions:
|
|
29
|
+
* span.name → name
|
|
30
|
+
* span.attributes.model_id → gen_ai.request.model, gen_ai.response.model
|
|
31
|
+
* span.attributes.input_tokens → gen_ai.usage.input_tokens
|
|
32
|
+
* span.attributes.output_tokens → gen_ai.usage.output_tokens
|
|
33
|
+
* span.name → gen_ai.operation.name
|
|
34
|
+
* 'mindforge' → gen_ai.system (or span.attributes.provider if present)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const crypto = require('crypto');
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if the OTel exporter is enabled (env var gate).
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isEnabled() {
|
|
44
|
+
return !!process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Translate a NexusTracer span to OTel GenAI-compatible format.
|
|
49
|
+
* Produces a valid 16-byte hex traceId and 8-byte hex spanId.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} nexusSpan - A span object from NexusTracer
|
|
52
|
+
* @returns {object} OTel-compatible span object
|
|
53
|
+
*/
|
|
54
|
+
function toOtelSpan(nexusSpan) {
|
|
55
|
+
const attrs = nexusSpan.attributes || {};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
traceId: crypto.randomBytes(16).toString('hex'),
|
|
59
|
+
spanId: crypto.randomBytes(8).toString('hex'),
|
|
60
|
+
parentSpanId: nexusSpan.parent_id || '',
|
|
61
|
+
name: nexusSpan.name || 'unknown',
|
|
62
|
+
kind: 1, // SPAN_KIND_INTERNAL
|
|
63
|
+
startTimeUnixNano: nexusSpan.start_time
|
|
64
|
+
? BigInt(new Date(nexusSpan.start_time).getTime()) * 1_000_000n
|
|
65
|
+
: 0n,
|
|
66
|
+
endTimeUnixNano: nexusSpan.end_time
|
|
67
|
+
? BigInt(new Date(nexusSpan.end_time).getTime()) * 1_000_000n
|
|
68
|
+
: 0n,
|
|
69
|
+
status: nexusSpan.status === 'success' ? { code: 1 } : { code: 2 },
|
|
70
|
+
attributes: {
|
|
71
|
+
'gen_ai.system': attrs.provider || 'mindforge',
|
|
72
|
+
'gen_ai.request.model': attrs.model_id || '',
|
|
73
|
+
'gen_ai.response.model': attrs.model_id || '',
|
|
74
|
+
'gen_ai.usage.input_tokens': attrs.input_tokens || 0,
|
|
75
|
+
'gen_ai.usage.output_tokens': attrs.output_tokens || 0,
|
|
76
|
+
'gen_ai.operation.name': nexusSpan.name || '',
|
|
77
|
+
'service.name': attrs.service || 'mindforge-nexus',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Serialize BigInt values to strings for JSON compatibility.
|
|
84
|
+
* @param {object} otelSpan
|
|
85
|
+
* @returns {object}
|
|
86
|
+
*/
|
|
87
|
+
function toJsonSafe(otelSpan) {
|
|
88
|
+
return {
|
|
89
|
+
...otelSpan,
|
|
90
|
+
startTimeUnixNano: String(otelSpan.startTimeUnixNano),
|
|
91
|
+
endTimeUnixNano: String(otelSpan.endTimeUnixNano),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Export a NexusTracer span to the OTel-compatible local file.
|
|
97
|
+
* In production, this would POST to OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces.
|
|
98
|
+
* For now, appends to .mindforge/metrics/otel-spans.jsonl for verification.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} nexusSpan - A span from NexusTracer
|
|
101
|
+
*/
|
|
102
|
+
async function exportSpan(nexusSpan) {
|
|
103
|
+
if (!isEnabled()) return;
|
|
104
|
+
|
|
105
|
+
const otelSpan = toOtelSpan(nexusSpan);
|
|
106
|
+
const jsonSafe = toJsonSafe(otelSpan);
|
|
107
|
+
|
|
108
|
+
const fs = require('fs');
|
|
109
|
+
const path = require('path');
|
|
110
|
+
const outPath = path.join(process.cwd(), '.mindforge', 'metrics', 'otel-spans.jsonl');
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const dir = path.dirname(outPath);
|
|
114
|
+
if (!fs.existsSync(dir)) {
|
|
115
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
fs.appendFileSync(outPath, JSON.stringify(jsonSafe) + '\n');
|
|
118
|
+
} catch {
|
|
119
|
+
// Non-fatal: observability export should never break the main flow
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { isEnabled, toOtelSpan, toJsonSafe, exportSpan };
|
|
@@ -45,7 +45,17 @@ class ReasonSourceAligner {
|
|
|
45
45
|
* @returns {Object} - Alignment results.
|
|
46
46
|
*/
|
|
47
47
|
checkAlignment(thought) {
|
|
48
|
-
|
|
48
|
+
// Fail-safe stable: when no requirements are loaded we CANNOT assess
|
|
49
|
+
// alignment, so we honestly decline rather than assert perfect alignment.
|
|
50
|
+
// Returning the SAME shape as the normal branch means the sole caller
|
|
51
|
+
// (auto-runner.checkMissionFidelity) reads a real boolean instead of
|
|
52
|
+
// `undefined`, so the mission-fidelity gate is no longer silently disabled.
|
|
53
|
+
// is_aligned:false is the safe direction — the caller only injects a
|
|
54
|
+
// correction when is_aligned is truthy, so an honest "can't assess" simply
|
|
55
|
+
// does nothing (no false correction, no silent shape mismatch).
|
|
56
|
+
if (!this.initialized) {
|
|
57
|
+
return { is_aligned: false, best_match_id: null, confidence: 0, status: 'uninitialized' };
|
|
58
|
+
}
|
|
49
59
|
|
|
50
60
|
const alignmentScores = this.registry.map(req => {
|
|
51
61
|
const score = this._calculateSimilarity(thought, req.summary + ' ' + req.description);
|
|
@@ -58,6 +68,7 @@ class ReasonSourceAligner {
|
|
|
58
68
|
is_aligned: bestMatch ? bestMatch.score > 0.25 : false, // Sparse mapping allowed
|
|
59
69
|
best_match_id: bestMatch ? bestMatch.id : null,
|
|
60
70
|
confidence: bestMatch ? parseFloat(bestMatch.score.toFixed(4)) : 0,
|
|
71
|
+
status: 'assessed',
|
|
61
72
|
};
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -82,19 +93,21 @@ class ReasonSourceAligner {
|
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
|
-
*
|
|
96
|
+
* Keyword-based overlap heuristic (Jaccard similarity).
|
|
97
|
+
* NOTE: This is a token-overlap heuristic, NOT semantic embeddings.
|
|
98
|
+
* Returns |A ∩ B| / |A ∪ B| in [0, 1].
|
|
86
99
|
*/
|
|
87
100
|
_calculateSimilarity(a, b) {
|
|
88
101
|
const getTokens = (str) => new Set(str.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/).filter(t => t.length > 3));
|
|
89
102
|
const tokensA = getTokens(a);
|
|
90
103
|
const tokensB = getTokens(b);
|
|
91
|
-
|
|
104
|
+
|
|
92
105
|
if (tokensA.size === 0 || tokensB.size === 0) return 0;
|
|
93
|
-
|
|
106
|
+
|
|
94
107
|
const intersection = new Set([...tokensA].filter(x => tokensB.has(x)));
|
|
95
108
|
const union = new Set([...tokensA, ...tokensB]);
|
|
96
|
-
|
|
97
|
-
return intersection.size /
|
|
109
|
+
|
|
110
|
+
return intersection.size / union.size; // Jaccard: overlap over combined vocabulary
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
/**
|
|
@@ -47,12 +47,22 @@ class SREManager {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Sanitizes a thought chain and generates
|
|
51
|
-
*
|
|
50
|
+
* Sanitizes a thought chain and generates an HMAC integrity certificate.
|
|
51
|
+
*
|
|
52
|
+
* IMPORTANT — HONEST LABELING: This is NOT a zero-knowledge proof. The
|
|
53
|
+
* artifact is an HMAC-SHA256 tag computed with a process-local shared secret
|
|
54
|
+
* (EPHEMERAL_ENCLAVE_KEY). It provides tamper-evidence/integrity over the
|
|
55
|
+
* proof payload, but:
|
|
56
|
+
* - any party holding the key can forge it (symmetric MAC, not asymmetric),
|
|
57
|
+
* - the payload carries the plaintext sha256(thoughtChain) digest, so it is
|
|
58
|
+
* not "zero-visibility".
|
|
59
|
+
* The enclave is simulated (no hardware TEE). Consumers must treat the
|
|
60
|
+
* returned object as an integrity tag, not a cryptographic ZK proof.
|
|
61
|
+
*
|
|
52
62
|
* @param {string} thoughtChain - The raw agentic thought chain.
|
|
53
63
|
* @param {string} enclaveId - The active enclave ID.
|
|
54
64
|
* @param {Object} policyResult - Whether the content passed internal policy checks.
|
|
55
|
-
* @returns {Object} -
|
|
65
|
+
* @returns {Object} - HMAC integrity certificate (simulated enclave).
|
|
56
66
|
*/
|
|
57
67
|
sanitizeThoughtChain(thoughtChain, enclaveId, policyResult = { passed: true }) {
|
|
58
68
|
if (!this.activeEnclaves.has(enclaveId)) {
|
|
@@ -64,7 +74,7 @@ class SREManager {
|
|
|
64
74
|
const prevHash = enclaveData.cumulativeHash;
|
|
65
75
|
const digest = crypto.createHash('sha256').update(thoughtChain).digest('hex');
|
|
66
76
|
|
|
67
|
-
// Generate a simulated ZK
|
|
77
|
+
// Generate a simulated-enclave HMAC integrity certificate (NOT a ZK proof)
|
|
68
78
|
const proofPayload = {
|
|
69
79
|
enclaveId: enclaveId,
|
|
70
80
|
digest: digest,
|
|
@@ -85,19 +95,36 @@ class SREManager {
|
|
|
85
95
|
|
|
86
96
|
const certificate = {
|
|
87
97
|
status: 'SRE-ISOLATED',
|
|
98
|
+
// Honest labeling: this is a symmetric HMAC integrity tag, not a ZK proof.
|
|
99
|
+
type: 'hmac-integrity-certificate',
|
|
100
|
+
method: 'hmac-sha256-integrity',
|
|
101
|
+
simulated: true,
|
|
102
|
+
zeroKnowledge: false,
|
|
103
|
+
disclosure: 'HMAC integrity tag (simulated enclave; NOT a zero-knowledge proof). '
|
|
104
|
+
+ 'Forgeable by any holder of the shared enclave key; payload carries the plaintext sha256(thought) digest.',
|
|
88
105
|
proof: proofPayload,
|
|
89
106
|
signature: signature,
|
|
90
107
|
proofHash: proofHash,
|
|
91
108
|
verificationDid: SYSTEM_DID,
|
|
92
|
-
message: `[SRE-
|
|
109
|
+
message: `[SRE-HMAC] HMAC integrity tag for confidential reasoning (sha256:${digest.substring(0, 8)}...) `
|
|
110
|
+
+ 'in simulated enclave — NOT a zero-knowledge proof.'
|
|
93
111
|
};
|
|
94
112
|
|
|
95
113
|
return certificate;
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
/**
|
|
99
|
-
* Verifies an SRE
|
|
117
|
+
* Verifies an SRE integrity certificate's HMAC tag and policy flag.
|
|
118
|
+
*
|
|
119
|
+
* NOTE — HONEST LABELING: this recomputes the HMAC-SHA256 tag using the
|
|
120
|
+
* shared enclave key and compares it. It is symmetric MAC verification, NOT
|
|
121
|
+
* zero-knowledge verification. The method name is retained for API
|
|
122
|
+
* compatibility with existing callers; see verifyIntegrityCertificate alias.
|
|
100
123
|
*/
|
|
124
|
+
verifyIntegrityCertificate(certificate) {
|
|
125
|
+
return this.verifyZKProof(certificate);
|
|
126
|
+
}
|
|
127
|
+
|
|
101
128
|
verifyZKProof(certificate) {
|
|
102
129
|
if (certificate.status !== 'SRE-ISOLATED') return false;
|
|
103
130
|
|
|
@@ -12,7 +12,7 @@ const SUBCOMMAND = ARGS[0];
|
|
|
12
12
|
|
|
13
13
|
async function main() {
|
|
14
14
|
switch (SUBCOMMAND) {
|
|
15
|
-
case 'status':
|
|
15
|
+
case 'status': {
|
|
16
16
|
const history = TemporalHub.getHistory();
|
|
17
17
|
console.log('\n⏳ MindForge Temporal Status');
|
|
18
18
|
console.log(` Snapshots: ${history.length}`);
|
|
@@ -20,6 +20,7 @@ async function main() {
|
|
|
20
20
|
console.log(` Latest: ${history[0].id} (${history[0].timestamp})`);
|
|
21
21
|
}
|
|
22
22
|
break;
|
|
23
|
+
}
|
|
23
24
|
|
|
24
25
|
case 'cleanup':
|
|
25
26
|
console.log('🧹 Cleaning up old temporal snapshots...');
|
|
@@ -27,7 +28,7 @@ async function main() {
|
|
|
27
28
|
console.log('✅ Cleanup complete.');
|
|
28
29
|
break;
|
|
29
30
|
|
|
30
|
-
case 'inject':
|
|
31
|
+
case 'inject': {
|
|
31
32
|
const auditId = ARGS[1];
|
|
32
33
|
const fix = ARGS.slice(2).join(' ');
|
|
33
34
|
if (!auditId || !fix) {
|
|
@@ -42,6 +43,7 @@ async function main() {
|
|
|
42
43
|
process.exit(1);
|
|
43
44
|
}
|
|
44
45
|
break;
|
|
46
|
+
}
|
|
45
47
|
|
|
46
48
|
default:
|
|
47
49
|
console.log('Usage: /mindforge:temporal <status|cleanup|inject>');
|