mindforge-cc 11.2.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/.mindforge/config.json +3 -2
- package/CHANGELOG.md +37 -1
- package/MINDFORGE.md +5 -5
- package/bin/autonomous/mesh-self-healer.js +101 -28
- package/bin/browser/regression-writer.js +45 -3
- package/bin/browser/session-manager.js +21 -17
- 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/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/governance/policy-engine.js +17 -4
- package/bin/governance/ztai-archiver.js +74 -9
- package/bin/governance/ztai-manager.js +3 -3
- package/bin/installer-core.js +31 -2
- package/bin/memory/eis-client.js +45 -4
- package/bin/memory/vector-hub.js +32 -0
- package/bin/review/finding-synthesizer.js +35 -6
- package/bin/security/trust-boundaries.js +96 -4
- package/bin/security/trust-gate-hook.js +13 -3
- package/bin/skill-registry.js +31 -20
- package/bin/sre/shadow-mirror.js +90 -40
- package/bin/utils/append-queue.js +12 -0
- package/bin/utils/file-io.js +4 -45
- package/bin/utils/version-check.js +21 -5
- package/package.json +1 -1
|
@@ -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,
|
|
@@ -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
|
|
|
@@ -92,10 +92,14 @@ class PolicyEngine {
|
|
|
92
92
|
return verdict;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// [ENTERPRISE] Tier 3
|
|
95
|
+
// [ENTERPRISE] Tier 3 Sovereign Proof Bypass (fail-closed).
|
|
96
|
+
// A blast-radius override demands a CRYPTOGRAPHIC proof. Only a
|
|
97
|
+
// pq_proof verified via verifyZKProof may authorize the bypass.
|
|
98
|
+
// intent.reasoning_proof is free-form text validated nowhere, so it
|
|
99
|
+
// MUST NOT, on its own, grant an override (UC-22 authz bypass fix).
|
|
96
100
|
if (intent.tier >= 3 && (intent.reasoning_proof || intent.pq_proof)) {
|
|
97
101
|
const quantumCrypto = require('./quantum-crypto');
|
|
98
|
-
let isProofValid =
|
|
102
|
+
let isProofValid = false; // fail-closed: deny unless a real proof verifies
|
|
99
103
|
|
|
100
104
|
if (intent.pq_proof) {
|
|
101
105
|
const zkResult = quantumCrypto.verifyZKProof(intent.pq_proof, intent.id);
|
|
@@ -106,12 +110,21 @@ class PolicyEngine {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
if (isProofValid) {
|
|
109
|
-
console.log(`[APO-BYPASS] [${requestId}] Tier 3 'Sovereign Proof' verified (
|
|
113
|
+
console.log(`[APO-BYPASS] [${requestId}] Tier 3 'Sovereign Proof' verified (ZK-PQ). Overriding Blast Radius limit.`);
|
|
110
114
|
// Continue to permit check
|
|
111
|
-
} else {
|
|
115
|
+
} else if (intent.pq_proof) {
|
|
112
116
|
verdict = { verdict: 'DENY', reason: 'ZK proof verification failed. Configure a verifier module or provide a valid proof.', requestId };
|
|
113
117
|
this.logAudit(intent, impactScore, verdict);
|
|
114
118
|
return verdict;
|
|
119
|
+
} else {
|
|
120
|
+
// Only a reasoning_proof was supplied — not a cryptographic proof.
|
|
121
|
+
verdict = {
|
|
122
|
+
verdict: 'DENY',
|
|
123
|
+
reason: 'reasoning_proof is not a cryptographic proof; provide a valid pq_proof / Sovereign Proof for blast-radius override.',
|
|
124
|
+
requestId
|
|
125
|
+
};
|
|
126
|
+
this.logAudit(intent, impactScore, verdict);
|
|
127
|
+
return verdict;
|
|
115
128
|
}
|
|
116
129
|
} else {
|
|
117
130
|
verdict = {
|
|
@@ -20,19 +20,35 @@ class ZTAIArchiver {
|
|
|
20
20
|
* @param {string} archiverDid - The DID of the archiver (e.g., Release Manager)
|
|
21
21
|
* @returns {Promise<Object>} - The signed manifest
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Computes the cumulative-hash-chain "Merkle root" for a block of entries.
|
|
25
|
+
*
|
|
26
|
+
* This is the SINGLE source of truth for the root algorithm. Both
|
|
27
|
+
* generateManifest() (write path) and verifyIntegrity() (read/verify path)
|
|
28
|
+
* MUST call this so the two can never drift — a drift would re-introduce the
|
|
29
|
+
* false-assurance defect (audit finding #15 / UC-22).
|
|
30
|
+
*
|
|
31
|
+
* @param {Array<Object>} entries - Ordered block of audit entries.
|
|
32
|
+
* @returns {string} - The cumulative SHA-256 hash chain (hex).
|
|
33
|
+
*/
|
|
34
|
+
_computeMerkleRoot(entries) {
|
|
35
|
+
const blockHashes = entries.map(e =>
|
|
28
36
|
crypto.createHash('sha256').update(JSON.stringify(e)).digest('hex')
|
|
29
37
|
);
|
|
30
|
-
|
|
31
|
-
// Simple cumulative hash chain as a Merkle Root equivalent
|
|
38
|
+
|
|
39
|
+
// Simple cumulative hash chain as a Merkle Root equivalent.
|
|
32
40
|
let cumulativeHash = '';
|
|
33
41
|
for (const h of blockHashes) {
|
|
34
42
|
cumulativeHash = crypto.createHash('sha256').update(cumulativeHash + h).digest('hex');
|
|
35
43
|
}
|
|
44
|
+
return cumulativeHash;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async generateManifest(entries, archiverDid) {
|
|
48
|
+
if (!entries || entries.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
// 1. Calculate the Merkle-like root hash of the block.
|
|
51
|
+
const cumulativeHash = this._computeMerkleRoot(entries);
|
|
36
52
|
|
|
37
53
|
const manifestMetadata = {
|
|
38
54
|
blockStart: entries[0].timestamp,
|
|
@@ -94,8 +110,57 @@ class ZTAIArchiver {
|
|
|
94
110
|
throw new Error(`CRITICAL: Manifest signature invalid for ${manifestPath}`);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
// 2. Recalculate and Verify Merkle Root
|
|
98
|
-
//
|
|
113
|
+
// 2. Recalculate and Verify Merkle Root against the LIVE AUDIT.jsonl.
|
|
114
|
+
//
|
|
115
|
+
// A valid signature only proves the manifest itself is authentic; it says
|
|
116
|
+
// NOTHING about whether the underlying audit log still matches. We must
|
|
117
|
+
// recompute the root from disk and compare — anything less is false
|
|
118
|
+
// assurance (audit finding #15 / UC-22). Fail-closed on every error path:
|
|
119
|
+
// a missing/unreadable log MUST NOT pass.
|
|
120
|
+
let auditData;
|
|
121
|
+
try {
|
|
122
|
+
auditData = await fs.readFile(this.auditPath, 'utf8');
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw new Error(`CRITICAL: Audit log unreadable at ${this.auditPath} — cannot verify integrity (${err.message})`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let allEntries;
|
|
128
|
+
try {
|
|
129
|
+
allEntries = auditData
|
|
130
|
+
.split('\n')
|
|
131
|
+
.filter(l => l.trim() !== '')
|
|
132
|
+
.map(l => JSON.parse(l));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
throw new Error(`CRITICAL: Audit log corrupted / unparseable for ${manifestPath} (${err.message})`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Select the block this manifest covers: entries whose timestamp falls
|
|
138
|
+
// within [blockStart, blockEnd] inclusive.
|
|
139
|
+
const start = Date.parse(manifest.blockStart);
|
|
140
|
+
const end = Date.parse(manifest.blockEnd);
|
|
141
|
+
const blockEntries = allEntries.filter(e => {
|
|
142
|
+
const ts = Date.parse(e.timestamp);
|
|
143
|
+
return ts >= start && ts <= end;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Sanity-check the selected count against the manifest. A mismatch means
|
|
147
|
+
// entries were added or deleted within the covered window.
|
|
148
|
+
if (blockEntries.length !== manifest.entryCount) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
'CRITICAL: Audit log tampering detected — block entry count mismatch ' +
|
|
151
|
+
`(manifest=${manifest.entryCount}, found=${blockEntries.length}) for ${manifestPath}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recompute the root with the SAME algorithm used at archive time.
|
|
156
|
+
const recomputedRoot = this._computeMerkleRoot(blockEntries);
|
|
157
|
+
if (recomputedRoot !== manifest.merkleRoot) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`CRITICAL: Audit log tampering detected — Merkle root mismatch for ${manifestPath} ` +
|
|
160
|
+
`(expected=${manifest.merkleRoot}, recomputed=${recomputedRoot}).`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
99
164
|
console.log(`[ZTAI-ARCHIVER] Integrity Verified for block ending ${manifest.blockEnd}`);
|
|
100
165
|
return true;
|
|
101
166
|
}
|
|
@@ -65,7 +65,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
async generate(did) {
|
|
68
|
-
console.log(`[ZTAI-HSM] Provisioning
|
|
68
|
+
console.log(`[ZTAI-HSM-SIM] Provisioning simulated (in-process) identity enclave for ${did}...`);
|
|
69
69
|
const { publicKey, privateKey } = await generateKeyPair('ed25519');
|
|
70
70
|
const pubPEM = publicKey.export({ type: 'spki', format: 'pem' });
|
|
71
71
|
|
|
@@ -82,7 +82,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
82
82
|
const record = this.enclaveStore.get(did);
|
|
83
83
|
if (!record) throw new Error(`Enclave record not found for ${did}`);
|
|
84
84
|
|
|
85
|
-
console.log(`[ZTAI-HSM]
|
|
85
|
+
console.log(`[ZTAI-HSM-SIM] Signing via simulated in-process enclave (NOT a hardware HSM/TPM) [DID: ${did}]`);
|
|
86
86
|
|
|
87
87
|
// Simulate enclave "wrapping" or "sealing" logic
|
|
88
88
|
const signature = crypto.sign(null, Buffer.from(data), record.privateKey);
|
|
@@ -93,7 +93,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async rotate(did) {
|
|
96
|
-
console.log(`[ZTAI-HSM] Rotating enclave keys for ${did}...`);
|
|
96
|
+
console.log(`[ZTAI-HSM-SIM] Rotating simulated enclave keys for ${did}...`);
|
|
97
97
|
return this.generate(did);
|
|
98
98
|
}
|
|
99
99
|
|
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)
|
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();
|
package/bin/memory/vector-hub.js
CHANGED
|
@@ -56,6 +56,26 @@ class VectorHub {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Idempotently add a column to an existing table (lightweight migration).
|
|
61
|
+
* SQLite has no "ADD COLUMN IF NOT EXISTS", so we run the ALTER and swallow
|
|
62
|
+
* only the "duplicate column name" error — which simply means the column is
|
|
63
|
+
* already present (the table was created with it, or a prior run added it).
|
|
64
|
+
* Any other error is re-thrown so genuine schema problems surface loudly.
|
|
65
|
+
* @param {string} table
|
|
66
|
+
* @param {string} column
|
|
67
|
+
* @param {string} typeDecl - e.g. 'TEXT', 'INTEGER DEFAULT 0'
|
|
68
|
+
*/
|
|
69
|
+
_addColumnIfMissing(table, column, typeDecl) {
|
|
70
|
+
try {
|
|
71
|
+
this._db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${typeDecl}`);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (!/duplicate column name/i.test(err && err.message)) {
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
59
79
|
/**
|
|
60
80
|
* Initialize the WASM SQLite database and create tables + indexes.
|
|
61
81
|
*/
|
|
@@ -124,11 +144,23 @@ class VectorHub {
|
|
|
124
144
|
id TEXT PRIMARY KEY,
|
|
125
145
|
request_id TEXT NOT NULL,
|
|
126
146
|
status TEXT NOT NULL,
|
|
147
|
+
did TEXT,
|
|
148
|
+
signed_message TEXT,
|
|
127
149
|
attestation_payload TEXT,
|
|
128
150
|
timestamp TEXT NOT NULL
|
|
129
151
|
)
|
|
130
152
|
`);
|
|
131
153
|
|
|
154
|
+
// UC-22 (audit finding #2): orbital attestations must carry the signer DID
|
|
155
|
+
// and the EXACT canonical message that was signed so verify() can re-check
|
|
156
|
+
// the cryptographic signature instead of trusting status='APPROVED' alone.
|
|
157
|
+
// CREATE TABLE IF NOT EXISTS won't add columns to a database created before
|
|
158
|
+
// this fix, so back-fill them with guarded ALTER TABLE statements. SQLite
|
|
159
|
+
// throws "duplicate column name" when the column already exists — that case
|
|
160
|
+
// is the success path (already migrated), so it is swallowed.
|
|
161
|
+
this._addColumnIfMissing('attestations', 'did', 'TEXT');
|
|
162
|
+
this._addColumnIfMissing('attestations', 'signed_message', 'TEXT');
|
|
163
|
+
|
|
132
164
|
this._db.run(`
|
|
133
165
|
CREATE TABLE IF NOT EXISTS mesh_config (
|
|
134
166
|
key TEXT PRIMARY KEY,
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
const SEVERITY_ORDER = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
|
|
8
8
|
|
|
9
|
+
// A severity spread of this many levels (or more) within a single location-group
|
|
10
|
+
// is treated as a contradiction (e.g. CRITICAL=3 vs LOW=0 → gap 3).
|
|
11
|
+
const CONTRADICTION_GAP_THRESHOLD = 2;
|
|
12
|
+
|
|
9
13
|
function synthesizeFindings(reviews) {
|
|
10
14
|
const allFindings = [];
|
|
11
15
|
const modelSpecific = {};
|
|
@@ -18,8 +22,9 @@ function synthesizeFindings(reviews) {
|
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
// Detect consensus
|
|
25
|
+
// Detect consensus and contradictions from the same location-groups.
|
|
22
26
|
const consensus = [];
|
|
27
|
+
const contradictions = [];
|
|
23
28
|
const processed = new Set();
|
|
24
29
|
|
|
25
30
|
for (let i = 0; i < allFindings.length; i++) {
|
|
@@ -31,7 +36,7 @@ function synthesizeFindings(reviews) {
|
|
|
31
36
|
for (let j = i + 1; j < allFindings.length; j++) {
|
|
32
37
|
if (processed.has(j)) continue;
|
|
33
38
|
const f2 = allFindings[j];
|
|
34
|
-
|
|
39
|
+
|
|
35
40
|
if (isSameFinding(f1, f2)) {
|
|
36
41
|
group.push(f2);
|
|
37
42
|
processed.add(j);
|
|
@@ -45,13 +50,12 @@ function synthesizeFindings(reviews) {
|
|
|
45
50
|
severity: getHighestSeverity(group.map(f => f.severity)),
|
|
46
51
|
models: group.map(f => f.model),
|
|
47
52
|
});
|
|
53
|
+
|
|
54
|
+
const contradiction = detectContradiction(f1.location, group);
|
|
55
|
+
if (contradiction) contradictions.push(contradiction);
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
// Detect contradictions (large severity gap on same finding)
|
|
52
|
-
const contradictions = [];
|
|
53
|
-
// (In a real implementation, we'd more deeply analyze conflicting logic)
|
|
54
|
-
|
|
55
59
|
return {
|
|
56
60
|
consensus,
|
|
57
61
|
model_specific: modelSpecific,
|
|
@@ -92,6 +96,31 @@ function normalizeLocation(loc) {
|
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
function severityRank(severity) {
|
|
100
|
+
const idx = SEVERITY_ORDER.indexOf(severity);
|
|
101
|
+
return idx < 0 ? 0 : idx;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// A location-group is contradictory when its reviews disagree on severity by
|
|
105
|
+
// CONTRADICTION_GAP_THRESHOLD levels or more (e.g. CRITICAL vs LOW). Reuses the
|
|
106
|
+
// already-computed location-group rather than re-deriving it.
|
|
107
|
+
function detectContradiction(location, group) {
|
|
108
|
+
const ranks = group.map(f => severityRank(f.severity));
|
|
109
|
+
const maxRank = Math.max(...ranks);
|
|
110
|
+
const minRank = Math.min(...ranks);
|
|
111
|
+
|
|
112
|
+
if (maxRank - minRank < CONTRADICTION_GAP_THRESHOLD) return null;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
location,
|
|
116
|
+
severities: group.map(f => f.severity),
|
|
117
|
+
models: group.map(f => f.model),
|
|
118
|
+
description: `Severity disagreement at ${location}: ` +
|
|
119
|
+
`${SEVERITY_ORDER[minRank]} vs ${SEVERITY_ORDER[maxRank]} ` +
|
|
120
|
+
`(${maxRank - minRank}-level gap across ${group.length} reviews)`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
95
124
|
function getHighestSeverity(severities) {
|
|
96
125
|
let highest = 0;
|
|
97
126
|
for (const s of severities) {
|