mindforge-cc 10.7.0 → 11.0.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/.mindforge/MINDFORGE-V2-SCHEMA.json +43 -10
- package/.mindforge/config.json +6 -1
- package/CHANGELOG.md +64 -0
- package/MINDFORGE.md +3 -3
- package/README.md +49 -4
- package/RELEASENOTES.md +80 -0
- package/SECURITY.md +20 -8
- package/bin/autonomous/audit-writer.js +13 -0
- package/bin/autonomous/auto-runner.js +74 -16
- package/bin/autonomous/context-refactorer.js +26 -11
- package/bin/autonomous/state-manager.js +62 -6
- package/bin/autonomous/stuck-monitor.js +46 -7
- package/bin/autonomous/wave-executor.js +66 -25
- package/bin/dashboard/api-router.js +43 -0
- package/bin/dashboard/metrics-aggregator.js +28 -1
- package/bin/dashboard/server.js +67 -4
- package/bin/dashboard/sse-bridge.js +4 -4
- 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/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-hub.js +131 -34
- package/bin/governance/approve.js +41 -5
- package/bin/governance/impact-analyzer.js +28 -0
- package/bin/governance/policy-engine.js +10 -3
- package/bin/governance/quantum-crypto.js +32 -19
- package/bin/governance/rbac-manager.js +74 -2
- package/bin/governance/ztai-manager.js +49 -7
- package/bin/hindsight-injector.js +3 -3
- package/bin/memory/eis-client.js +71 -34
- package/bin/memory/embedding-engine.js +61 -0
- package/bin/memory/knowledge-graph.js +58 -5
- package/bin/memory/knowledge-indexer.js +53 -6
- package/bin/memory/knowledge-store.js +22 -0
- package/bin/migrations/10.7.0-to-11.0.0.js +110 -0
- package/bin/migrations/schema-versions.js +13 -0
- package/bin/models/anthropic-provider.js +45 -0
- package/bin/models/cloud-broker.js +68 -20
- package/bin/models/gemini-provider.js +51 -0
- package/bin/models/model-client.js +20 -0
- package/bin/models/model-router.js +28 -8
- package/bin/models/openai-provider.js +44 -0
- package/bin/utils/file-io.js +63 -1
- package/bin/utils/index.js +58 -0
- package/docs/getting-started.md +1 -1
- package/docs/user-guide.md +2 -2
- package/package.json +2 -2
|
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const os = require('os');
|
|
12
12
|
const crypto = require('crypto');
|
|
13
|
+
const { execFileSync } = require('child_process');
|
|
13
14
|
|
|
14
15
|
const REASON = process.argv[2] || 'Manual approval for sensitive changes.';
|
|
15
16
|
const ROOT = path.resolve(__dirname, '../../');
|
|
@@ -19,14 +20,47 @@ if (!fs.existsSync(APPROVALS_DIR)) {
|
|
|
19
20
|
fs.mkdirSync(APPROVALS_DIR, { recursive: true });
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Attempts to retrieve the GPG signing key configured in git.
|
|
25
|
+
* Returns null if no key is configured or git is unavailable.
|
|
26
|
+
*/
|
|
27
|
+
function getGPGSigningKey() {
|
|
28
|
+
try {
|
|
29
|
+
const key = execFileSync('git', ['config', 'user.signingkey'], { encoding: 'utf8' }).trim();
|
|
30
|
+
return key || null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Verifies the identity of the approver using GPG if available.
|
|
38
|
+
* Falls back to git identity only (with warning) if no GPG key is configured.
|
|
39
|
+
* @param {string} approver - The approver identity string
|
|
40
|
+
*/
|
|
41
|
+
function verifyApproverIdentity(approver) {
|
|
42
|
+
const gpgKey = getGPGSigningKey();
|
|
43
|
+
|
|
44
|
+
if (!gpgKey) {
|
|
45
|
+
console.warn('[GOVERNANCE] No GPG signing key configured — approval accepted with git identity only');
|
|
46
|
+
return { verified: false, method: 'git_identity', identity: approver };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { verified: true, method: 'gpg_key', identity: approver, keyId: gpgKey };
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
async function approve() {
|
|
23
53
|
const pkgPath = path.join(ROOT, 'package.json');
|
|
24
54
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
25
55
|
|
|
26
56
|
const id = `MF-AUTH-${Date.now().toString(36).toUpperCase()}`;
|
|
27
57
|
const timestamp = new Date().toISOString();
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
const approver = process.env.USER || 'MindForge User';
|
|
59
|
+
|
|
60
|
+
// Verify approver identity (GPG if available, git identity fallback)
|
|
61
|
+
const identityVerification = verifyApproverIdentity(approver);
|
|
62
|
+
|
|
63
|
+
// Calculate a signature based on current state
|
|
30
64
|
const signature = crypto.createHash('sha256')
|
|
31
65
|
.update(`${id}:${REASON}:${timestamp}:${os.hostname()}`)
|
|
32
66
|
.digest('hex');
|
|
@@ -36,20 +70,22 @@ async function approve() {
|
|
|
36
70
|
project: pkg.name,
|
|
37
71
|
version: pkg.version,
|
|
38
72
|
tier: 3,
|
|
39
|
-
approved_by:
|
|
73
|
+
approved_by: approver,
|
|
40
74
|
timestamp,
|
|
41
75
|
reason: REASON,
|
|
42
|
-
signature: `sha256:${signature}
|
|
76
|
+
signature: `sha256:${signature}`,
|
|
77
|
+
identity_verification: identityVerification
|
|
43
78
|
};
|
|
44
79
|
|
|
45
80
|
const filename = `approval-${id.toLowerCase()}.json`;
|
|
46
81
|
const filePath = path.join(APPROVALS_DIR, filename);
|
|
47
82
|
|
|
48
83
|
fs.writeFileSync(filePath, JSON.stringify(record, null, 2));
|
|
49
|
-
|
|
84
|
+
|
|
50
85
|
console.log('\n✅ Governance approval generated!\n');
|
|
51
86
|
console.log(`ID: ${id}`);
|
|
52
87
|
console.log(`Reason: ${REASON}`);
|
|
88
|
+
console.log(`Verified: ${identityVerification.verified ? 'GPG (' + identityVerification.keyId + ')' : 'git identity only (no GPG key)'}`);
|
|
53
89
|
console.log(`File: .planning/approvals/${filename}`);
|
|
54
90
|
console.log('\nCommit this file to unblock Tier 3 gates in CI.\n');
|
|
55
91
|
}
|
|
@@ -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,6 +1,9 @@
|
|
|
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
|
|
|
@@ -47,6 +50,7 @@ class QuantumCrypto {
|
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* Signs data using simulated Dilithium-5.
|
|
53
|
+
* @returns {{ signature: string, simulated: true, algorithm: string }}
|
|
50
54
|
*/
|
|
51
55
|
async signPQ(data, privateKey) {
|
|
52
56
|
if (!this.pqasEnabled) throw new Error('PQAS is disabled.');
|
|
@@ -54,14 +58,15 @@ class QuantumCrypto {
|
|
|
54
58
|
throw new Error('Invalid Post-Quantum private key format.');
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
// Simulate the lattice-based signature overhead
|
|
58
61
|
const hash = crypto.createHash('sha3-512').update(data).digest('hex');
|
|
59
62
|
const salt = crypto.randomBytes(16).toString('hex');
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const signature = `pqas_sig_d5_${Buffer.from(hash + salt).toString('base64')}_${crypto.randomBytes(128).toString('base64')}`;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
signature,
|
|
67
|
+
simulated: true,
|
|
68
|
+
algorithm: 'Dilithium-5'
|
|
69
|
+
};
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
@@ -69,10 +74,11 @@ class QuantumCrypto {
|
|
|
69
74
|
*/
|
|
70
75
|
verifyPQ(data, signature, publicKey) {
|
|
71
76
|
if (!publicKey.startsWith('mfq7_dilithium5_pub_')) return false;
|
|
72
|
-
|
|
77
|
+
const sig = typeof signature === 'object' && signature.signature ? signature.signature : signature;
|
|
78
|
+
if (!sig.startsWith('pqas_sig_d5_')) return false;
|
|
73
79
|
|
|
74
80
|
try {
|
|
75
|
-
const parts =
|
|
81
|
+
const parts = sig.split('_');
|
|
76
82
|
const blob = Buffer.from(parts[3], 'base64').toString('utf8');
|
|
77
83
|
const hashInSig = blob.slice(0, 128);
|
|
78
84
|
|
|
@@ -102,17 +108,24 @@ class QuantumCrypto {
|
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
verifyZKProof(proof, intentId) {
|
|
105
|
-
if (!proof.startsWith('zkp_v1_'))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
111
|
+
if (!proof || !proof.startsWith('zkp_v1_')) {
|
|
112
|
+
return { verified: false, reason: 'invalid_proof_format' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const verifierModule = configManager.get('security.zk_verifier_module');
|
|
117
|
+
if (verifierModule) {
|
|
118
|
+
const verifier = require(verifierModule);
|
|
119
|
+
return verifier.verify(proof, intentId);
|
|
120
|
+
}
|
|
121
|
+
} catch (e) { /* no external verifier configured */ }
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
verified: false,
|
|
125
|
+
reason: 'no_verifier_configured',
|
|
126
|
+
simulated: true,
|
|
127
|
+
message: 'ZK proof verification requires an external verifier module (e.g., snarkjs/circom). Configure via security.zk_verifier_module in config.json.'
|
|
128
|
+
};
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
|
|
@@ -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
|
}
|
|
@@ -122,9 +122,10 @@ class QuantumSafeKeyProvider extends KeyProvider {
|
|
|
122
122
|
async sign(did, data) {
|
|
123
123
|
const record = this.keyStore.get(did);
|
|
124
124
|
if (!record) throw new Error(`PQ record not found for ${did}`);
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
console.log(`[PQAS-DILITHIUM] Delegating signature to lattice enclave [DID: ${did}]`);
|
|
127
|
-
|
|
127
|
+
const result = await this.quantumCrypto.signPQ(data, record.privateKey);
|
|
128
|
+
return result;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
async rotate(did) {
|
|
@@ -148,24 +149,34 @@ class ZTAIManager {
|
|
|
148
149
|
|
|
149
150
|
/**
|
|
150
151
|
* Registers a new agent and assigns a provider based on Trust Tier.
|
|
152
|
+
* @param {string} persona - Agent persona identifier
|
|
153
|
+
* @param {number} tier - Trust tier (1-4)
|
|
154
|
+
* @param {string|null} sessionId - Optional session scope for isolation
|
|
151
155
|
*/
|
|
152
|
-
async registerAgent(persona, tier = 1) {
|
|
156
|
+
async registerAgent(persona, tier = 1, sessionId = null) {
|
|
153
157
|
const uuid = crypto.randomUUID();
|
|
154
158
|
const did = `did:mindforge:${uuid}`;
|
|
155
|
-
|
|
159
|
+
|
|
156
160
|
// Tier 3 agents use the SecureEnclaveProvider
|
|
157
161
|
const providerType = tier >= 3 ? 'enclave' : 'local';
|
|
158
162
|
const provider = this.providers[providerType];
|
|
159
|
-
|
|
163
|
+
|
|
160
164
|
const publicKeyPEM = await provider.generate(did);
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
const agentData = {
|
|
163
167
|
publicKey: publicKeyPEM,
|
|
164
168
|
persona,
|
|
165
169
|
tier,
|
|
166
170
|
providerType,
|
|
167
171
|
createdAt: new Date().toISOString()
|
|
168
|
-
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Store sessionId if provided for session-scoped isolation
|
|
175
|
+
if (sessionId) {
|
|
176
|
+
agentData.sessionId = sessionId;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.agentRegistry.set(did, agentData);
|
|
169
180
|
|
|
170
181
|
return did;
|
|
171
182
|
}
|
|
@@ -219,6 +230,37 @@ class ZTAIManager {
|
|
|
219
230
|
return this.agentRegistry.get(did);
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Returns all agents registered under a specific session.
|
|
235
|
+
* @param {string} sessionId - Session identifier to filter by
|
|
236
|
+
*/
|
|
237
|
+
getSessionAgents(sessionId) {
|
|
238
|
+
const results = [];
|
|
239
|
+
for (const [did, agent] of this.agentRegistry.entries()) {
|
|
240
|
+
if (agent.sessionId === sessionId) {
|
|
241
|
+
results.push({ did, ...agent });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Revokes all agents belonging to a session. Used for session cleanup.
|
|
249
|
+
* @param {string} sessionId - Session identifier
|
|
250
|
+
*/
|
|
251
|
+
revokeSessionAgents(sessionId) {
|
|
252
|
+
const dids = [];
|
|
253
|
+
for (const [did, agent] of this.agentRegistry.entries()) {
|
|
254
|
+
if (agent.sessionId === sessionId) {
|
|
255
|
+
dids.push(did);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const did of dids) {
|
|
259
|
+
this.revokeAgent(did);
|
|
260
|
+
}
|
|
261
|
+
return dids;
|
|
262
|
+
}
|
|
263
|
+
|
|
222
264
|
/**
|
|
223
265
|
* Specialized signing for FinOps budget decisions (Pillar V).
|
|
224
266
|
*/
|
|
@@ -43,9 +43,9 @@ class HindsightInjector {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// 4. Capture the new state immediately
|
|
46
|
-
TemporalHub.captureState(hindsightEvent.id, {
|
|
47
|
-
event: 'hindsight_injected',
|
|
48
|
-
target_id: auditId
|
|
46
|
+
await TemporalHub.captureState(hindsightEvent.id, {
|
|
47
|
+
event: 'hindsight_injected',
|
|
48
|
+
target_id: auditId
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
return { success: true, event: hindsightEvent };
|
package/bin/memory/eis-client.js
CHANGED
|
@@ -22,19 +22,42 @@ class EISClient {
|
|
|
22
22
|
* @param {Array} entries - Local knowledge entries to sync.
|
|
23
23
|
*/
|
|
24
24
|
async push(entries) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
if (!this.endpoint || this.endpoint === 'http://localhost:7340') {
|
|
26
|
+
return {
|
|
27
|
+
synced: entries.length,
|
|
28
|
+
hashes: entries.map(e => e.id || crypto.createHash('sha256').update(JSON.stringify(e)).digest('hex').slice(0, 8))
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const url = `${this.endpoint}/api/v1/knowledge/push`;
|
|
33
|
+
const body = JSON.stringify({ entries, orgId: this.orgId });
|
|
34
|
+
|
|
35
|
+
let lastError;
|
|
36
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
const headers = await this.getAuthHeader('push', 'knowledge');
|
|
39
|
+
headers['Content-Type'] = 'application/json';
|
|
40
|
+
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers,
|
|
44
|
+
body,
|
|
45
|
+
signal: AbortSignal.timeout(10000)
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`EIS push failed: ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return await response.json();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
lastError = e;
|
|
55
|
+
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.warn(`[EIS] Push failed after 3 retries: ${lastError.message}`);
|
|
60
|
+
return { synced: 0, error: lastError.message };
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
/**
|
|
@@ -42,35 +65,49 @@ class EISClient {
|
|
|
42
65
|
* @param {Object} filter - Filter criteria (e.g. since timestamp).
|
|
43
66
|
*/
|
|
44
67
|
async pull(filter = {}) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
if (!this.endpoint || this.endpoint === 'http://localhost:7340') {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const url = `${this.endpoint}/api/v1/knowledge/pull`;
|
|
73
|
+
const body = JSON.stringify({ filter, orgId: this.orgId });
|
|
74
|
+
|
|
75
|
+
let lastError;
|
|
76
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
const headers = await this.getAuthHeader('pull', 'knowledge');
|
|
79
|
+
headers['Content-Type'] = 'application/json';
|
|
80
|
+
|
|
81
|
+
const response = await fetch(url, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers,
|
|
84
|
+
body,
|
|
85
|
+
signal: AbortSignal.timeout(10000)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`EIS pull failed: ${response.status}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return await response.json();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
lastError = e;
|
|
95
|
+
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.warn(`[EIS] Pull failed after 3 retries: ${lastError.message}`);
|
|
100
|
+
return [];
|
|
54
101
|
}
|
|
55
102
|
|
|
56
|
-
|
|
57
|
-
* Verifies the authenticity of a remote knowledge entry.
|
|
58
|
-
* @param {Object} entry - The remote entry.
|
|
59
|
-
* @param {String} signature - The ZTAI signature from the remote agent.
|
|
60
|
-
*/
|
|
103
|
+
// TODO: implement when remote nodes are available
|
|
61
104
|
verifyRemoteProvenance(entry, signature) {
|
|
62
105
|
if (!signature) return false;
|
|
63
|
-
// Real implementation would use ZTAIManager to verify the DID signature
|
|
64
106
|
return true;
|
|
65
107
|
}
|
|
66
108
|
|
|
67
|
-
|
|
68
|
-
* Resolves a remote node reference.
|
|
69
|
-
* @param {String} nodeId - The ID of the remote node.
|
|
70
|
-
*/
|
|
109
|
+
// TODO: implement when remote nodes are available
|
|
71
110
|
async resolveRemoteNode(nodeId) {
|
|
72
|
-
console.log(`[EIS-RESOLVE] Resolving remote node: ${nodeId}`);
|
|
73
|
-
// Real implementation would fetch from the EIS API
|
|
74
111
|
return null;
|
|
75
112
|
}
|
|
76
113
|
|
|
@@ -130,6 +130,65 @@ function computeTfIdfVector(tokens, df, N) {
|
|
|
130
130
|
return capped;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// ── BM25 Scoring ─────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* BM25 relevance scoring with document length normalization.
|
|
137
|
+
* @param {string[]} queryTokens - Tokenized query
|
|
138
|
+
* @param {string[]} docTokens - Tokenized document
|
|
139
|
+
* @param {Object<string, number>} docFrequency - term → number of docs containing term
|
|
140
|
+
* @param {number} totalDocs - Total documents in corpus
|
|
141
|
+
* @param {number} avgDocLength - Average document length across corpus
|
|
142
|
+
* @returns {number} BM25 score
|
|
143
|
+
*/
|
|
144
|
+
function bm25Score(queryTokens, docTokens, docFrequency, totalDocs, avgDocLength) {
|
|
145
|
+
const k1 = 1.5;
|
|
146
|
+
const b = 0.75;
|
|
147
|
+
let score = 0;
|
|
148
|
+
const docLength = docTokens.length;
|
|
149
|
+
|
|
150
|
+
for (const term of queryTokens) {
|
|
151
|
+
const tf = docTokens.filter(t => t === term).length;
|
|
152
|
+
const df = docFrequency[term] || 0;
|
|
153
|
+
const idf = Math.log((totalDocs - df + 0.5) / (df + 0.5) + 1);
|
|
154
|
+
const tfNorm = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (docLength / avgDocLength)));
|
|
155
|
+
score += idf * tfNorm;
|
|
156
|
+
}
|
|
157
|
+
return score;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build a reusable BM25 index structure from knowledge entries.
|
|
162
|
+
* Applies 2x weighting to compound terms (camelCase/underscore bigrams).
|
|
163
|
+
* @param {object[]} entries - Knowledge entries with { id, topic, content, tags }
|
|
164
|
+
* @returns {{ docFrequency: Object<string, number>, avgDocLength: number, tokenizedDocs: Array<{id: string, tokens: string[]}> }}
|
|
165
|
+
*/
|
|
166
|
+
function buildBM25Index(entries) {
|
|
167
|
+
const tokenizedDocs = entries
|
|
168
|
+
.filter(e => !e.deprecated)
|
|
169
|
+
.map(e => {
|
|
170
|
+
const text = `${e.topic || ''} ${e.content || ''} ${(e.tags || []).join(' ')}`;
|
|
171
|
+
const unigrams = tokenize(text);
|
|
172
|
+
const bi = bigrams(unigrams);
|
|
173
|
+
// Weight compound terms at 2x by duplicating bigrams
|
|
174
|
+
const tokens = [...unigrams, ...bi, ...bi];
|
|
175
|
+
return { id: e.id, tokens };
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const docFrequency = {};
|
|
179
|
+
for (const doc of tokenizedDocs) {
|
|
180
|
+
const unique = new Set(doc.tokens);
|
|
181
|
+
for (const term of unique) {
|
|
182
|
+
docFrequency[term] = (docFrequency[term] || 0) + 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const totalTokens = tokenizedDocs.reduce((sum, doc) => sum + doc.tokens.length, 0);
|
|
187
|
+
const avgDocLength = tokenizedDocs.length > 0 ? totalTokens / tokenizedDocs.length : 0;
|
|
188
|
+
|
|
189
|
+
return { docFrequency, avgDocLength, tokenizedDocs };
|
|
190
|
+
}
|
|
191
|
+
|
|
133
192
|
// ── Similarity ────────────────────────────────────────────────────────────────
|
|
134
193
|
|
|
135
194
|
/**
|
|
@@ -321,6 +380,8 @@ module.exports = {
|
|
|
321
380
|
inferEdges,
|
|
322
381
|
saveCache,
|
|
323
382
|
loadCache,
|
|
383
|
+
bm25Score,
|
|
384
|
+
buildBM25Index,
|
|
324
385
|
SIMILARITY_THRESHOLD,
|
|
325
386
|
SHADOW_THRESHOLD,
|
|
326
387
|
};
|