mindforge-cc 10.7.0 → 11.2.0
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/MINDFORGE-V2-SCHEMA.json +43 -10
- package/.mindforge/config.json +18 -4
- package/CHANGELOG.md +165 -0
- package/MINDFORGE.md +3 -3
- package/README.md +49 -4
- package/RELEASENOTES.md +81 -1
- package/SECURITY.md +20 -8
- package/bin/autonomous/audit-writer.js +105 -70
- package/bin/autonomous/auto-runner.js +377 -34
- package/bin/autonomous/context-refactorer.js +26 -11
- package/bin/autonomous/dependency-dag.js +59 -0
- package/bin/autonomous/state-manager.js +62 -6
- package/bin/autonomous/stuck-monitor.js +46 -7
- package/bin/autonomous/wave-executor.js +86 -26
- package/bin/council-cli.js +161 -0
- package/bin/dashboard/api-router.js +43 -0
- package/bin/dashboard/approval-handler.js +3 -1
- package/bin/dashboard/metrics-aggregator.js +28 -1
- package/bin/dashboard/server.js +68 -5
- package/bin/dashboard/sse-bridge.js +10 -13
- package/bin/engine/council-runtime.js +124 -0
- package/bin/engine/feedback-loop.js +8 -0
- package/bin/engine/intelligence-interlock.js +32 -15
- package/bin/engine/logic-drift-detector.js +2 -1
- package/bin/engine/nexus-tracer.js +3 -2
- package/bin/engine/otel-exporter.js +123 -0
- package/bin/engine/remediation-engine.js +155 -32
- package/bin/engine/self-corrective-synthesizer.js +84 -10
- package/bin/engine/sre-manager.js +12 -4
- package/bin/engine/temporal-cli.js +4 -2
- package/bin/engine/temporal-hub.js +131 -34
- 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/approve.js +41 -5
- package/bin/governance/audit-hash.js +12 -0
- package/bin/governance/audit-verifier.js +60 -0
- package/bin/governance/impact-analyzer.js +28 -0
- package/bin/governance/policy-engine.js +10 -3
- package/bin/governance/quantum-crypto.js +95 -28
- package/bin/governance/rbac-manager.js +74 -2
- package/bin/governance/ztai-manager.js +79 -9
- package/bin/hindsight-injector.js +8 -9
- package/bin/hooks/instinct-capture-hook.js +186 -0
- package/bin/memory/auto-shadow.js +32 -3
- package/bin/memory/eis-client.js +71 -34
- package/bin/memory/embedding-engine.js +61 -0
- package/bin/memory/identity-synthesizer.js +2 -2
- package/bin/memory/knowledge-graph.js +58 -5
- package/bin/memory/knowledge-indexer.js +53 -6
- package/bin/memory/knowledge-store.js +52 -6
- package/bin/memory/retrieval-fusion.js +58 -0
- package/bin/memory/semantic-hub.js +2 -2
- package/bin/memory/vector-hub.js +111 -6
- package/bin/migrations/10.7.0-to-11.0.0.js +110 -0
- package/bin/migrations/schema-versions.js +13 -0
- package/bin/mindforge-cli.js +4 -5
- package/bin/models/anthropic-provider.js +58 -4
- package/bin/models/cloud-broker.js +68 -20
- package/bin/models/cost-tracker.js +3 -1
- package/bin/models/difficulty-scorer.js +54 -0
- package/bin/models/gemini-provider.js +57 -2
- package/bin/models/model-client.js +20 -0
- package/bin/models/model-router.js +59 -26
- package/bin/models/openai-provider.js +50 -3
- package/bin/models/pricing-registry.js +128 -0
- package/bin/review/ads-engine.js +1 -1
- package/bin/security/trust-boundaries.js +102 -0
- package/bin/security/trust-gate-hook.js +39 -0
- package/bin/skill-registry.js +3 -2
- 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/utils/append-queue.js +55 -0
- package/bin/utils/file-io.js +90 -38
- package/bin/utils/index.js +58 -0
- package/bin/utils/version-check.js +59 -0
- package/bin/verify-audit.js +12 -0
- package/bin/wizard/theme.js +1 -2
- package/docs/getting-started.md +1 -1
- package/docs/user-guide.md +2 -2
- package/package.json +2 -2
- package/bin/dashboard/team-tracker.js +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* MindForge — Audit chain verifier (UC-04). Fail-closed: any break => valid:false.
|
|
4
|
+
*
|
|
5
|
+
* Walks the JSONL audit log line-by-line and re-derives each entry's hash from its
|
|
6
|
+
* content plus the prior entry's hash. The chain is valid only if EVERY link holds:
|
|
7
|
+
* 1. entry.previous_hash matches the running previousHash (null for the first entry)
|
|
8
|
+
* 2. entry._hash equals sha256({...entry-without-_hash, previous_hash})
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL INVARIANT: hashEntry() here MUST produce byte-identical material to the
|
|
11
|
+
* writer (bin/autonomous/audit-writer.js). The writer hashes {...e, previous_hash}
|
|
12
|
+
* where `e` has no `_hash`. The stored entry is {...e, previous_hash, _hash}; stripping
|
|
13
|
+
* `_hash` via destructuring yields {...e, previous_hash} with the SAME key insertion
|
|
14
|
+
* order, so {...rest, previous_hash} reproduces the writer's material exactly.
|
|
15
|
+
*/
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const { hashAuditEntry } = require('./audit-hash');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Re-derives an entry's chained hash. Excludes `_hash` from the material by
|
|
21
|
+
* stripping it, then delegating to the canonical {@link hashAuditEntry} so the
|
|
22
|
+
* writer and verifier share ONE hasher (no material drift possible).
|
|
23
|
+
* @param {object} entry — stored entry (may include `_hash`, which is stripped)
|
|
24
|
+
* @param {string|null} previousHash — prior entry's `_hash` (null for the first link)
|
|
25
|
+
* @returns {string} hex-encoded SHA-256 digest
|
|
26
|
+
*/
|
|
27
|
+
function hashEntry(entry, previousHash) {
|
|
28
|
+
const { _hash, ...rest } = entry; // exclude _hash from material
|
|
29
|
+
return hashAuditEntry(rest, previousHash);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Verifies the integrity of a hash-chained audit log. Fail-closed.
|
|
34
|
+
* @param {string} auditPath — path to the AUDIT.jsonl file
|
|
35
|
+
* @returns {{ valid: boolean, count: number, brokenAt?: number, reason?: string }}
|
|
36
|
+
*/
|
|
37
|
+
function verifyAuditChain(auditPath) {
|
|
38
|
+
let lines;
|
|
39
|
+
try { lines = fs.readFileSync(auditPath, 'utf8').split('\n').filter(Boolean); }
|
|
40
|
+
catch (e) { return { valid: false, count: 0, brokenAt: 0, reason: `unreadable: ${e.message}` }; }
|
|
41
|
+
|
|
42
|
+
let previousHash = null;
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
let entry;
|
|
45
|
+
try { entry = JSON.parse(lines[i]); }
|
|
46
|
+
catch { return { valid: false, count: lines.length, brokenAt: i, reason: 'unparseable line' }; }
|
|
47
|
+
|
|
48
|
+
if (entry.previous_hash !== previousHash) {
|
|
49
|
+
return { valid: false, count: lines.length, brokenAt: i, reason: 'previous_hash mismatch' };
|
|
50
|
+
}
|
|
51
|
+
const expected = hashEntry(entry, previousHash);
|
|
52
|
+
if (entry._hash !== expected) {
|
|
53
|
+
return { valid: false, count: lines.length, brokenAt: i, reason: 'hash mismatch (entry mutated)' };
|
|
54
|
+
}
|
|
55
|
+
previousHash = entry._hash;
|
|
56
|
+
}
|
|
57
|
+
return { valid: true, count: lines.length };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { verifyAuditChain };
|
|
@@ -137,6 +137,34 @@ class ImpactAnalyzer {
|
|
|
137
137
|
static resetSession(sessionId) {
|
|
138
138
|
this.sessionState.delete(sessionId);
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Returns the current entropy count for a session without incrementing.
|
|
143
|
+
* Useful for diagnostics and monitoring.
|
|
144
|
+
*/
|
|
145
|
+
static getSessionEntropy(sessionId) {
|
|
146
|
+
return this.sessionState.get(sessionId) || 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clears all session state entries. Use during process cleanup or testing.
|
|
151
|
+
*/
|
|
152
|
+
static clearAllSessions() {
|
|
153
|
+
this.sessionState.clear();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clears sessions that have exceeded a given entropy threshold.
|
|
158
|
+
* Prevents unbounded memory growth from abandoned sessions.
|
|
159
|
+
* @param {number} maxEntropy - Sessions above this count are purged.
|
|
160
|
+
*/
|
|
161
|
+
static clearStaleSessions(maxEntropy = 50) {
|
|
162
|
+
for (const [sessionId, count] of this.sessionState.entries()) {
|
|
163
|
+
if (count > maxEntropy) {
|
|
164
|
+
this.sessionState.delete(sessionId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
module.exports = ImpactAnalyzer;
|
|
@@ -95,14 +95,21 @@ class PolicyEngine {
|
|
|
95
95
|
// [ENTERPRISE] Tier 3 Reasoning/PQ Proof Bypass
|
|
96
96
|
if (intent.tier >= 3 && (intent.reasoning_proof || intent.pq_proof)) {
|
|
97
97
|
const quantumCrypto = require('./quantum-crypto');
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
let isProofValid = true;
|
|
99
|
+
|
|
100
|
+
if (intent.pq_proof) {
|
|
101
|
+
const zkResult = quantumCrypto.verifyZKProof(intent.pq_proof, intent.id);
|
|
102
|
+
isProofValid = zkResult.verified === true;
|
|
103
|
+
if (!isProofValid) {
|
|
104
|
+
console.log(`[APO-ZK] [${requestId}] ZK proof denied: ${zkResult.reason}${zkResult.simulated ? ' (simulated)' : ''}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
100
107
|
|
|
101
108
|
if (isProofValid) {
|
|
102
109
|
console.log(`[APO-BYPASS] [${requestId}] Tier 3 'Sovereign Proof' verified (${intent.pq_proof ? 'ZK-PQ' : 'Standard'}). Overriding Blast Radius limit.`);
|
|
103
110
|
// Continue to permit check
|
|
104
111
|
} else {
|
|
105
|
-
verdict = { verdict: 'DENY', reason: '
|
|
112
|
+
verdict = { verdict: 'DENY', reason: 'ZK proof verification failed. Configure a verifier module or provide a valid proof.', requestId };
|
|
106
113
|
this.logAudit(intent, impactScore, verdict);
|
|
107
114
|
return verdict;
|
|
108
115
|
}
|
|
@@ -1,26 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MindForge v7 — Post-Quantum Agentic Security (PQAS)
|
|
3
3
|
* Simulated Lattice-Based Cryptography (Dilithium-5 / Kyber-1024)
|
|
4
|
+
*
|
|
5
|
+
* @typedef {Object} ZKVerifierProvider
|
|
6
|
+
* @property {(proof: string, intentId: string) => {verified: boolean, reason?: string}} verify
|
|
4
7
|
*/
|
|
5
8
|
'use strict';
|
|
6
9
|
|
|
7
10
|
const crypto = require('node:crypto');
|
|
8
11
|
const configManager = require('./config-manager');
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Honest-disclosure guard message. The signatures produced by this module are
|
|
15
|
+
* SIMULATED Dilithium-5 (base64(SHA3 + random) — NOT real ML-DSA/FIPS-204
|
|
16
|
+
* lattice crypto). They must NEVER sit on the live trust path silently. The
|
|
17
|
+
* simulated implementation is preserved for demonstration only and is gated
|
|
18
|
+
* behind an explicit, opt-in flag.
|
|
19
|
+
*/
|
|
20
|
+
const PQC_DEMO_DISABLED_MSG =
|
|
21
|
+
'PQC demo disabled — set experimental.pqc_demo=true to use simulated lattice crypto (NOT for production trust)';
|
|
22
|
+
|
|
10
23
|
class QuantumCrypto {
|
|
11
24
|
constructor() {
|
|
12
25
|
this.providerId = configManager.get('security.provider', 'simulated-lattice');
|
|
13
|
-
|
|
26
|
+
// UC-24: simulated PQC is OFF the live trust path by default. Real PQC has
|
|
27
|
+
// not shipped. The simulated path is gated SOLELY behind the explicit
|
|
28
|
+
// experimental.pqc_demo opt-in (read fresh via isPqcDemoEnabled), so there
|
|
29
|
+
// is exactly one switch controlling the simulated minter. The legacy
|
|
30
|
+
// security.pqas_enabled flag is retained for provider metadata only and
|
|
31
|
+
// MUST NOT independently gate minting (see getProvider).
|
|
32
|
+
this.pqasEnabled = configManager.get('security.pqas_enabled', false);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns true only when the operator has explicitly opted into the SIMULATED
|
|
37
|
+
* post-quantum demo. Read fresh from config so a runtime toggle is honored.
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
isPqcDemoEnabled() {
|
|
41
|
+
return configManager.get('experimental.pqc_demo', false) === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Hard gate: throws unless the operator has explicitly enabled the simulated
|
|
46
|
+
* PQC demo. Prevents simulated (false-assurance) signatures from silently
|
|
47
|
+
* landing on the live trust path.
|
|
48
|
+
*/
|
|
49
|
+
_assertPqcDemoEnabled() {
|
|
50
|
+
if (!this.isPqcDemoEnabled()) {
|
|
51
|
+
throw new Error(PQC_DEMO_DISABLED_MSG);
|
|
52
|
+
}
|
|
14
53
|
}
|
|
15
54
|
|
|
16
55
|
/**
|
|
17
56
|
* Returns the current active crypto provider.
|
|
18
57
|
*/
|
|
19
58
|
getProvider() {
|
|
20
|
-
// In v7, this would resolve to a real provider like 'oqs-provider.js'
|
|
59
|
+
// In v7, this would resolve to a real provider like 'oqs-provider.js'.
|
|
60
|
+
// Read pqas_enabled FRESH from config (not the constructor cache) for
|
|
61
|
+
// symmetry with the demo gate. This is metadata only — it does NOT gate
|
|
62
|
+
// minting; experimental.pqc_demo is the single source of truth for that.
|
|
21
63
|
return {
|
|
22
64
|
id: this.providerId,
|
|
23
|
-
pqas_enabled:
|
|
65
|
+
pqas_enabled: configManager.get('security.pqas_enabled', false),
|
|
24
66
|
algorithm: 'Dilithium-5'
|
|
25
67
|
};
|
|
26
68
|
}
|
|
@@ -29,7 +71,10 @@ class QuantumCrypto {
|
|
|
29
71
|
* Generates a key pair using the configured PQ provider.
|
|
30
72
|
*/
|
|
31
73
|
async generateLatticeKeyPair() {
|
|
32
|
-
|
|
74
|
+
// UC-24: simulated keys are demo-only. The SINGLE gate is the fresh-read
|
|
75
|
+
// experimental.pqc_demo flag — enabling it is sufficient to mint, and
|
|
76
|
+
// disabling it fails closed. security.pqas_enabled does NOT gate this.
|
|
77
|
+
this._assertPqcDemoEnabled();
|
|
33
78
|
|
|
34
79
|
// Simulate high-entropy lattice seeds
|
|
35
80
|
const seed = crypto.randomBytes(64).toString('hex');
|
|
@@ -47,21 +92,27 @@ class QuantumCrypto {
|
|
|
47
92
|
|
|
48
93
|
/**
|
|
49
94
|
* Signs data using simulated Dilithium-5.
|
|
95
|
+
* @returns {{ signature: string, simulated: true, algorithm: string }}
|
|
50
96
|
*/
|
|
51
97
|
async signPQ(data, privateKey) {
|
|
52
|
-
|
|
98
|
+
// UC-24: never produce a simulated (false-assurance) signature on the live
|
|
99
|
+
// trust path. The SINGLE gate is the fresh-read experimental.pqc_demo flag
|
|
100
|
+
// (same flag _selectProvider routes on) — security.pqas_enabled does NOT
|
|
101
|
+
// independently block signing.
|
|
102
|
+
this._assertPqcDemoEnabled();
|
|
53
103
|
if (!privateKey.startsWith('mfq7_dilithium5_priv_')) {
|
|
54
104
|
throw new Error('Invalid Post-Quantum private key format.');
|
|
55
105
|
}
|
|
56
106
|
|
|
57
|
-
// Simulate the lattice-based signature overhead
|
|
58
107
|
const hash = crypto.createHash('sha3-512').update(data).digest('hex');
|
|
59
108
|
const salt = crypto.randomBytes(16).toString('hex');
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
109
|
+
const signature = `pqas_sig_d5_${Buffer.from(hash + salt).toString('base64')}_${crypto.randomBytes(128).toString('base64')}`;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
signature,
|
|
113
|
+
simulated: true,
|
|
114
|
+
algorithm: 'Dilithium-5'
|
|
115
|
+
};
|
|
65
116
|
}
|
|
66
117
|
|
|
67
118
|
/**
|
|
@@ -69,10 +120,11 @@ class QuantumCrypto {
|
|
|
69
120
|
*/
|
|
70
121
|
verifyPQ(data, signature, publicKey) {
|
|
71
122
|
if (!publicKey.startsWith('mfq7_dilithium5_pub_')) return false;
|
|
72
|
-
|
|
123
|
+
const sig = typeof signature === 'object' && signature.signature ? signature.signature : signature;
|
|
124
|
+
if (!sig.startsWith('pqas_sig_d5_')) return false;
|
|
73
125
|
|
|
74
126
|
try {
|
|
75
|
-
const parts =
|
|
127
|
+
const parts = sig.split('_');
|
|
76
128
|
const blob = Buffer.from(parts[3], 'base64').toString('utf8');
|
|
77
129
|
const hashInSig = blob.slice(0, 128);
|
|
78
130
|
|
|
@@ -86,33 +138,48 @@ class QuantumCrypto {
|
|
|
86
138
|
}
|
|
87
139
|
|
|
88
140
|
/**
|
|
89
|
-
* Generates a
|
|
90
|
-
* This mimics a SNARK where the agent proves it ran the PolicyEngine rules
|
|
141
|
+
* Generates a SIMULATED ZK-Proof of policy adherence.
|
|
142
|
+
* This mimics a SNARK where the agent proves it ran the PolicyEngine rules,
|
|
143
|
+
* but it is NOT a real zero-knowledge proof. The returned token is explicitly
|
|
144
|
+
* stamped with a `sim` marker (zkp_v1_sim_...) so downstream code and audit
|
|
145
|
+
* logs can never mistake it for a real SNARK. It remains inert by default —
|
|
146
|
+
* verifyZKProof DENYs unless an external verifier module is configured.
|
|
147
|
+
* @returns {string} A simulated ZK-proof token prefixed `zkp_v1_sim_`.
|
|
91
148
|
*/
|
|
92
149
|
generateZKProof(intent, result) {
|
|
93
150
|
const proofPayload = JSON.stringify({
|
|
94
151
|
intent: intent.id,
|
|
95
152
|
verdict: result.verdict,
|
|
96
153
|
timestamp: Date.now(),
|
|
97
|
-
entropy: crypto.randomBytes(16).toString('hex')
|
|
154
|
+
entropy: crypto.randomBytes(16).toString('hex'),
|
|
155
|
+
simulated: true
|
|
98
156
|
});
|
|
99
157
|
|
|
100
158
|
const hash = crypto.createHash('sha256').update(proofPayload).digest('hex');
|
|
101
|
-
|
|
159
|
+
// `sim` marker is embedded AFTER the `zkp_v1_` prefix so the existing
|
|
160
|
+
// format check (startsWith 'zkp_v1_') and any external verifier still work.
|
|
161
|
+
return `zkp_v1_sim_${hash}_${crypto.randomBytes(32).toString('base64')}`;
|
|
102
162
|
}
|
|
103
163
|
|
|
104
164
|
verifyZKProof(proof, intentId) {
|
|
105
|
-
if (!proof.startsWith('zkp_v1_'))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
165
|
+
if (!proof || !proof.startsWith('zkp_v1_')) {
|
|
166
|
+
return { verified: false, reason: 'invalid_proof_format' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const verifierModule = configManager.get('security.zk_verifier_module');
|
|
171
|
+
if (verifierModule) {
|
|
172
|
+
const verifier = require(verifierModule);
|
|
173
|
+
return verifier.verify(proof, intentId);
|
|
174
|
+
}
|
|
175
|
+
} catch (e) { /* no external verifier configured */ }
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
verified: false,
|
|
179
|
+
reason: 'no_verifier_configured',
|
|
180
|
+
simulated: true,
|
|
181
|
+
message: 'ZK proof verification requires an external verifier module (e.g., snarkjs/circom). Configure via security.zk_verifier_module in config.json.'
|
|
182
|
+
};
|
|
116
183
|
}
|
|
117
184
|
}
|
|
118
185
|
|
|
@@ -18,6 +18,7 @@ class RBACManager {
|
|
|
18
18
|
'did:mindforge:researcher': ['knowledge-detective'],
|
|
19
19
|
'did:mindforge:tool': ['system-operator']
|
|
20
20
|
};
|
|
21
|
+
this.temporaryElevations = new Map(); // key: `${did}:${role}`, value: { timer, expiresAt }
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -84,10 +85,72 @@ class RBACManager {
|
|
|
84
85
|
fs.writeFileSync(this.rolesPath, JSON.stringify(current, null, 2));
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Temporarily elevates an agent to a role for a limited duration.
|
|
90
|
+
* The elevation auto-expires after ttlMs milliseconds.
|
|
91
|
+
* @param {string} did - Agent DID
|
|
92
|
+
* @param {string} role - Role to temporarily grant
|
|
93
|
+
* @param {number} ttlMs - Time-to-live in milliseconds (default: 1 hour)
|
|
94
|
+
*/
|
|
95
|
+
elevateRole(did, role, ttlMs = 3600000) {
|
|
96
|
+
const key = `${did}:${role}`;
|
|
97
|
+
|
|
98
|
+
// Clear existing elevation if any
|
|
99
|
+
if (this.temporaryElevations.has(key)) {
|
|
100
|
+
clearTimeout(this.temporaryElevations.get(key).timer);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const timer = setTimeout(() => {
|
|
104
|
+
this.temporaryElevations.delete(key);
|
|
105
|
+
}, ttlMs);
|
|
106
|
+
|
|
107
|
+
// Prevent timer from keeping process alive
|
|
108
|
+
if (timer.unref) timer.unref();
|
|
109
|
+
|
|
110
|
+
this.temporaryElevations.set(key, {
|
|
111
|
+
timer,
|
|
112
|
+
expiresAt: Date.now() + ttlMs
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return { did, role, expiresAt: Date.now() + ttlMs };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if an agent currently has a temporary role elevation.
|
|
120
|
+
* @param {string} did - Agent DID
|
|
121
|
+
* @param {string} role - Role to check
|
|
122
|
+
*/
|
|
123
|
+
hasTemporaryElevation(did, role) {
|
|
124
|
+
const key = `${did}:${role}`;
|
|
125
|
+
const elevation = this.temporaryElevations.get(key);
|
|
126
|
+
if (!elevation) return false;
|
|
127
|
+
if (Date.now() > elevation.expiresAt) {
|
|
128
|
+
clearTimeout(elevation.timer);
|
|
129
|
+
this.temporaryElevations.delete(key);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Revokes a temporary elevation before its TTL expires.
|
|
137
|
+
* @param {string} did - Agent DID
|
|
138
|
+
* @param {string} role - Role to revoke
|
|
139
|
+
*/
|
|
140
|
+
revokeElevation(did, role) {
|
|
141
|
+
const key = `${did}:${role}`;
|
|
142
|
+
const elevation = this.temporaryElevations.get(key);
|
|
143
|
+
if (elevation) {
|
|
144
|
+
clearTimeout(elevation.timer);
|
|
145
|
+
this.temporaryElevations.delete(key);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
87
149
|
/**
|
|
88
150
|
* Checks if an agent has a specific permission based on their roles.
|
|
89
|
-
*
|
|
90
|
-
* @param {string}
|
|
151
|
+
* Also checks temporary elevations.
|
|
152
|
+
* @param {string} did
|
|
153
|
+
* @param {string} permission
|
|
91
154
|
*/
|
|
92
155
|
hasPermission(did, permission) {
|
|
93
156
|
const roles = this.getRoles(did);
|
|
@@ -99,9 +162,18 @@ class RBACManager {
|
|
|
99
162
|
'guest-agent': ['read_src']
|
|
100
163
|
};
|
|
101
164
|
|
|
165
|
+
// Check static roles first
|
|
102
166
|
for (const role of roles) {
|
|
103
167
|
if (PERMISSION_MAP[role]?.includes(permission)) return true;
|
|
104
168
|
}
|
|
169
|
+
|
|
170
|
+
// Check temporary elevations
|
|
171
|
+
for (const [role, permissions] of Object.entries(PERMISSION_MAP)) {
|
|
172
|
+
if (permissions.includes(permission) && this.hasTemporaryElevation(did, role)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
105
177
|
return false;
|
|
106
178
|
}
|
|
107
179
|
}
|
|
@@ -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
|
|
|
@@ -122,9 +123,10 @@ class QuantumSafeKeyProvider extends KeyProvider {
|
|
|
122
123
|
async sign(did, data) {
|
|
123
124
|
const record = this.keyStore.get(did);
|
|
124
125
|
if (!record) throw new Error(`PQ record not found for ${did}`);
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
console.log(`[PQAS-DILITHIUM] Delegating signature to lattice enclave [DID: ${did}]`);
|
|
127
|
-
|
|
128
|
+
const result = await this.quantumCrypto.signPQ(data, record.privateKey);
|
|
129
|
+
return result;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
async rotate(did) {
|
|
@@ -146,26 +148,63 @@ class ZTAIManager {
|
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
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
|
+
|
|
149
179
|
/**
|
|
150
180
|
* Registers a new agent and assigns a provider based on Trust Tier.
|
|
181
|
+
* @param {string} persona - Agent persona identifier
|
|
182
|
+
* @param {number} tier - Trust tier (1-4)
|
|
183
|
+
* @param {string|null} sessionId - Optional session scope for isolation
|
|
151
184
|
*/
|
|
152
|
-
async registerAgent(persona, tier = 1) {
|
|
185
|
+
async registerAgent(persona, tier = 1, sessionId = null) {
|
|
153
186
|
const uuid = crypto.randomUUID();
|
|
154
187
|
const did = `did:mindforge:${uuid}`;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const providerType = tier >= 3 ? 'enclave' : 'local';
|
|
188
|
+
|
|
189
|
+
const providerType = this._selectProvider(tier);
|
|
158
190
|
const provider = this.providers[providerType];
|
|
159
|
-
|
|
191
|
+
|
|
160
192
|
const publicKeyPEM = await provider.generate(did);
|
|
161
193
|
|
|
162
|
-
|
|
194
|
+
const agentData = {
|
|
163
195
|
publicKey: publicKeyPEM,
|
|
164
196
|
persona,
|
|
165
197
|
tier,
|
|
166
198
|
providerType,
|
|
167
199
|
createdAt: new Date().toISOString()
|
|
168
|
-
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Store sessionId if provided for session-scoped isolation
|
|
203
|
+
if (sessionId) {
|
|
204
|
+
agentData.sessionId = sessionId;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.agentRegistry.set(did, agentData);
|
|
169
208
|
|
|
170
209
|
return did;
|
|
171
210
|
}
|
|
@@ -219,6 +258,37 @@ class ZTAIManager {
|
|
|
219
258
|
return this.agentRegistry.get(did);
|
|
220
259
|
}
|
|
221
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Returns all agents registered under a specific session.
|
|
263
|
+
* @param {string} sessionId - Session identifier to filter by
|
|
264
|
+
*/
|
|
265
|
+
getSessionAgents(sessionId) {
|
|
266
|
+
const results = [];
|
|
267
|
+
for (const [did, agent] of this.agentRegistry.entries()) {
|
|
268
|
+
if (agent.sessionId === sessionId) {
|
|
269
|
+
results.push({ did, ...agent });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Revokes all agents belonging to a session. Used for session cleanup.
|
|
277
|
+
* @param {string} sessionId - Session identifier
|
|
278
|
+
*/
|
|
279
|
+
revokeSessionAgents(sessionId) {
|
|
280
|
+
const dids = [];
|
|
281
|
+
for (const [did, agent] of this.agentRegistry.entries()) {
|
|
282
|
+
if (agent.sessionId === sessionId) {
|
|
283
|
+
dids.push(did);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const did of dids) {
|
|
287
|
+
this.revokeAgent(did);
|
|
288
|
+
}
|
|
289
|
+
return dids;
|
|
290
|
+
}
|
|
291
|
+
|
|
222
292
|
/**
|
|
223
293
|
* Specialized signing for FinOps budget decisions (Pillar V).
|
|
224
294
|
*/
|
|
@@ -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');
|
|
@@ -43,9 +42,9 @@ class HindsightInjector {
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
// 4. Capture the new state immediately
|
|
46
|
-
TemporalHub.captureState(hindsightEvent.id, {
|
|
47
|
-
event: 'hindsight_injected',
|
|
48
|
-
target_id: auditId
|
|
45
|
+
await TemporalHub.captureState(hindsightEvent.id, {
|
|
46
|
+
event: 'hindsight_injected',
|
|
47
|
+
target_id: auditId
|
|
49
48
|
});
|
|
50
49
|
|
|
51
50
|
return { success: true, event: hindsightEvent };
|