agentic-flow 1.7.3 → 1.7.4
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/.claude/agents/test-neural.md +0 -5
- package/.claude/answer.md +1 -0
- package/.claude/settings.json +19 -20
- package/CHANGELOG.md +0 -117
- package/README.md +17 -81
- package/dist/agentdb/benchmarks/comprehensive-benchmark.js +664 -0
- package/dist/agentdb/benchmarks/frontier-benchmark.js +419 -0
- package/dist/agentdb/benchmarks/reflexion-benchmark.js +370 -0
- package/dist/agentdb/cli/agentdb-cli.js +717 -0
- package/dist/agentdb/controllers/CausalMemoryGraph.js +322 -0
- package/dist/agentdb/controllers/CausalRecall.js +281 -0
- package/dist/agentdb/controllers/EmbeddingService.js +118 -0
- package/dist/agentdb/controllers/ExplainableRecall.js +387 -0
- package/dist/agentdb/controllers/NightlyLearner.js +382 -0
- package/dist/agentdb/controllers/ReflexionMemory.js +239 -0
- package/dist/agentdb/controllers/SkillLibrary.js +276 -0
- package/dist/agentdb/controllers/frontier-index.js +9 -0
- package/dist/agentdb/controllers/index.js +8 -0
- package/dist/agentdb/index.js +32 -0
- package/dist/agentdb/optimizations/BatchOperations.js +198 -0
- package/dist/agentdb/optimizations/QueryOptimizer.js +225 -0
- package/dist/agentdb/optimizations/index.js +7 -0
- package/dist/agentdb/tests/frontier-features.test.js +665 -0
- package/dist/cli-proxy.js +2 -33
- package/dist/mcp/standalone-stdio.js +200 -4
- package/dist/memory/SharedMemoryPool.js +211 -0
- package/dist/memory/index.js +6 -0
- package/dist/reasoningbank/AdvancedMemory.js +239 -0
- package/dist/reasoningbank/HybridBackend.js +305 -0
- package/dist/reasoningbank/index-new.js +87 -0
- package/dist/reasoningbank/index.js +23 -44
- package/dist/utils/cli.js +0 -22
- package/docs/AGENTDB_TESTING.md +411 -0
- package/docs/v1.7.1-QUICK-START.md +399 -0
- package/package.json +4 -4
- package/scripts/run-validation.sh +165 -0
- package/scripts/test-agentdb.sh +153 -0
- package/.claude/skills/agentdb-memory-patterns/SKILL.md +0 -166
- package/.claude/skills/agentdb-vector-search/SKILL.md +0 -126
- package/.claude/skills/agentic-flow/agentdb-memory-patterns/SKILL.md +0 -166
- package/.claude/skills/agentic-flow/agentdb-vector-search/SKILL.md +0 -126
- package/.claude/skills/agentic-flow/reasoningbank-intelligence/SKILL.md +0 -201
- package/.claude/skills/agentic-flow/swarm-orchestration/SKILL.md +0 -179
- package/.claude/skills/reasoningbank-intelligence/SKILL.md +0 -201
- package/.claude/skills/skill-builder/README.md +0 -308
- package/.claude/skills/skill-builder/SKILL.md +0 -910
- package/.claude/skills/skill-builder/docs/SPECIFICATION.md +0 -358
- package/.claude/skills/skill-builder/resources/schemas/skill-frontmatter.schema.json +0 -41
- package/.claude/skills/skill-builder/resources/templates/full-skill.template +0 -118
- package/.claude/skills/skill-builder/resources/templates/minimal-skill.template +0 -38
- package/.claude/skills/skill-builder/scripts/generate-skill.sh +0 -334
- package/.claude/skills/skill-builder/scripts/validate-skill.sh +0 -198
- package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
- package/docs/AGENTDB_INTEGRATION.md +0 -379
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingService - Text Embedding Generation
|
|
3
|
+
*
|
|
4
|
+
* Handles text-to-vector embedding generation using various models.
|
|
5
|
+
* Supports both local (transformers.js) and remote (OpenAI, etc.) embeddings.
|
|
6
|
+
*/
|
|
7
|
+
export class EmbeddingService {
|
|
8
|
+
config;
|
|
9
|
+
pipeline; // transformers.js pipeline
|
|
10
|
+
cache;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.cache = new Map();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the embedding service
|
|
17
|
+
*/
|
|
18
|
+
async initialize() {
|
|
19
|
+
if (this.config.provider === 'transformers') {
|
|
20
|
+
// Use transformers.js for local embeddings
|
|
21
|
+
try {
|
|
22
|
+
const { pipeline } = await import('@xenova/transformers');
|
|
23
|
+
this.pipeline = await pipeline('feature-extraction', this.config.model);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn('Transformers.js not available, falling back to mock embeddings');
|
|
27
|
+
this.pipeline = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate embedding for text
|
|
33
|
+
*/
|
|
34
|
+
async embed(text) {
|
|
35
|
+
// Check cache
|
|
36
|
+
const cacheKey = `${this.config.model}:${text}`;
|
|
37
|
+
if (this.cache.has(cacheKey)) {
|
|
38
|
+
return this.cache.get(cacheKey);
|
|
39
|
+
}
|
|
40
|
+
let embedding;
|
|
41
|
+
if (this.config.provider === 'transformers' && this.pipeline) {
|
|
42
|
+
// Use transformers.js
|
|
43
|
+
const output = await this.pipeline(text, { pooling: 'mean', normalize: true });
|
|
44
|
+
embedding = new Float32Array(output.data);
|
|
45
|
+
}
|
|
46
|
+
else if (this.config.provider === 'openai' && this.config.apiKey) {
|
|
47
|
+
// Use OpenAI API
|
|
48
|
+
embedding = await this.embedOpenAI(text);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Mock embedding for testing
|
|
52
|
+
embedding = this.mockEmbedding(text);
|
|
53
|
+
}
|
|
54
|
+
// Cache result
|
|
55
|
+
if (this.cache.size > 10000) {
|
|
56
|
+
// Simple LRU: clear half the cache
|
|
57
|
+
const keysToDelete = Array.from(this.cache.keys()).slice(0, 5000);
|
|
58
|
+
keysToDelete.forEach(k => this.cache.delete(k));
|
|
59
|
+
}
|
|
60
|
+
this.cache.set(cacheKey, embedding);
|
|
61
|
+
return embedding;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Batch embed multiple texts
|
|
65
|
+
*/
|
|
66
|
+
async embedBatch(texts) {
|
|
67
|
+
return Promise.all(texts.map(text => this.embed(text)));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Clear embedding cache
|
|
71
|
+
*/
|
|
72
|
+
clearCache() {
|
|
73
|
+
this.cache.clear();
|
|
74
|
+
}
|
|
75
|
+
// ========================================================================
|
|
76
|
+
// Private Methods
|
|
77
|
+
// ========================================================================
|
|
78
|
+
async embedOpenAI(text) {
|
|
79
|
+
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
83
|
+
'Content-Type': 'application/json'
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
model: this.config.model,
|
|
87
|
+
input: text
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
return new Float32Array(data.data[0].embedding);
|
|
92
|
+
}
|
|
93
|
+
mockEmbedding(text) {
|
|
94
|
+
// Simple deterministic mock embedding for testing
|
|
95
|
+
const embedding = new Float32Array(this.config.dimension);
|
|
96
|
+
// Use simple hash-based generation
|
|
97
|
+
let hash = 0;
|
|
98
|
+
for (let i = 0; i < text.length; i++) {
|
|
99
|
+
hash = ((hash << 5) - hash) + text.charCodeAt(i);
|
|
100
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
101
|
+
}
|
|
102
|
+
// Fill embedding with pseudo-random values based on hash
|
|
103
|
+
for (let i = 0; i < this.config.dimension; i++) {
|
|
104
|
+
const seed = hash + i * 31;
|
|
105
|
+
embedding[i] = Math.sin(seed) * Math.cos(seed * 0.5);
|
|
106
|
+
}
|
|
107
|
+
// Normalize
|
|
108
|
+
let norm = 0;
|
|
109
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
110
|
+
norm += embedding[i] * embedding[i];
|
|
111
|
+
}
|
|
112
|
+
norm = Math.sqrt(norm);
|
|
113
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
114
|
+
embedding[i] /= norm;
|
|
115
|
+
}
|
|
116
|
+
return embedding;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExplainableRecall - Provenance and Justification for Memory Retrieval
|
|
3
|
+
*
|
|
4
|
+
* Every retrieval returns:
|
|
5
|
+
* - Minimal hitting set of facts that justify the answer
|
|
6
|
+
* - Merkle proof chain for provenance
|
|
7
|
+
* - Policy compliance certificates
|
|
8
|
+
*
|
|
9
|
+
* Based on:
|
|
10
|
+
* - Minimal hitting set algorithms
|
|
11
|
+
* - Merkle tree provenance
|
|
12
|
+
* - Explainable AI techniques
|
|
13
|
+
*/
|
|
14
|
+
import * as crypto from 'crypto';
|
|
15
|
+
export class ExplainableRecall {
|
|
16
|
+
db;
|
|
17
|
+
constructor(db) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a recall certificate for a retrieval operation
|
|
22
|
+
*/
|
|
23
|
+
createCertificate(params) {
|
|
24
|
+
const { queryId, queryText, chunks, requirements, accessLevel = 'internal' } = params;
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
// 1. Compute minimal hitting set
|
|
27
|
+
const minimalWhy = this.computeMinimalHittingSet(chunks, requirements);
|
|
28
|
+
// 2. Calculate metrics
|
|
29
|
+
const redundancyRatio = chunks.length / minimalWhy.length;
|
|
30
|
+
const completenessScore = this.calculateCompleteness(minimalWhy, requirements);
|
|
31
|
+
// 3. Build provenance chain
|
|
32
|
+
const sourceHashes = chunks.map(chunk => this.getOrCreateProvenance(chunk.type, parseInt(chunk.id)));
|
|
33
|
+
const merkleTree = this.buildMerkleTree(sourceHashes);
|
|
34
|
+
const merkleRoot = merkleTree.root;
|
|
35
|
+
// 4. Generate chunk metadata first (needed for certificate ID)
|
|
36
|
+
const chunkIds = chunks.map(c => c.id);
|
|
37
|
+
const chunkTypes = chunks.map(c => c.type);
|
|
38
|
+
// 5. Create certificate ID
|
|
39
|
+
const certificateId = this.generateCertificateId(queryId, chunkIds);
|
|
40
|
+
// 6. Generate proof chain for each chunk
|
|
41
|
+
const proofChain = chunks.map((chunk, idx) => this.getMerkleProof(merkleTree, idx)).flat();
|
|
42
|
+
// 7. Store certificate
|
|
43
|
+
this.db.prepare(`
|
|
44
|
+
INSERT INTO recall_certificates (
|
|
45
|
+
id, query_id, query_text, chunk_ids, chunk_types,
|
|
46
|
+
minimal_why, redundancy_ratio, completeness_score,
|
|
47
|
+
merkle_root, source_hashes, proof_chain,
|
|
48
|
+
access_level, latency_ms
|
|
49
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
50
|
+
`).run(certificateId, queryId, queryText, JSON.stringify(chunkIds), JSON.stringify(chunkTypes), JSON.stringify(minimalWhy), redundancyRatio, completenessScore, merkleRoot, JSON.stringify(sourceHashes), JSON.stringify(proofChain), accessLevel, Date.now() - startTime);
|
|
51
|
+
// 7. Store justification paths
|
|
52
|
+
this.storeJustificationPaths(certificateId, chunks, minimalWhy, requirements);
|
|
53
|
+
const certificate = {
|
|
54
|
+
id: certificateId,
|
|
55
|
+
queryId,
|
|
56
|
+
queryText,
|
|
57
|
+
chunkIds,
|
|
58
|
+
chunkTypes,
|
|
59
|
+
minimalWhy,
|
|
60
|
+
redundancyRatio,
|
|
61
|
+
completenessScore,
|
|
62
|
+
merkleRoot,
|
|
63
|
+
sourceHashes,
|
|
64
|
+
proofChain,
|
|
65
|
+
accessLevel: accessLevel,
|
|
66
|
+
latencyMs: Date.now() - startTime
|
|
67
|
+
};
|
|
68
|
+
return certificate;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verify a recall certificate
|
|
72
|
+
*/
|
|
73
|
+
verifyCertificate(certificateId) {
|
|
74
|
+
const cert = this.db.prepare('SELECT * FROM recall_certificates WHERE id = ?').get(certificateId);
|
|
75
|
+
if (!cert) {
|
|
76
|
+
return { valid: false, issues: ['Certificate not found'] };
|
|
77
|
+
}
|
|
78
|
+
const issues = [];
|
|
79
|
+
// 1. Verify Merkle root
|
|
80
|
+
const sourceHashes = JSON.parse(cert.source_hashes);
|
|
81
|
+
const merkleTree = this.buildMerkleTree(sourceHashes);
|
|
82
|
+
if (merkleTree.root !== cert.merkle_root) {
|
|
83
|
+
issues.push('Merkle root mismatch');
|
|
84
|
+
}
|
|
85
|
+
// 2. Verify chunk hashes still match
|
|
86
|
+
const chunkIds = JSON.parse(cert.chunk_ids);
|
|
87
|
+
const chunkTypes = JSON.parse(cert.chunk_types);
|
|
88
|
+
for (let i = 0; i < chunkIds.length; i++) {
|
|
89
|
+
const currentHash = this.getContentHash(chunkTypes[i], parseInt(chunkIds[i]));
|
|
90
|
+
if (currentHash !== sourceHashes[i]) {
|
|
91
|
+
issues.push(`Chunk ${chunkIds[i]} hash changed`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 3. Verify completeness
|
|
95
|
+
const minimalWhy = JSON.parse(cert.minimal_why);
|
|
96
|
+
if (minimalWhy.length === 0) {
|
|
97
|
+
issues.push('Empty justification set');
|
|
98
|
+
}
|
|
99
|
+
// 4. Verify redundancy ratio
|
|
100
|
+
if (cert.redundancy_ratio < 1.0) {
|
|
101
|
+
issues.push('Invalid redundancy ratio');
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
valid: issues.length === 0,
|
|
105
|
+
issues
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get justification for why a chunk was included
|
|
110
|
+
*/
|
|
111
|
+
getJustification(certificateId, chunkId) {
|
|
112
|
+
const row = this.db.prepare(`
|
|
113
|
+
SELECT * FROM justification_paths
|
|
114
|
+
WHERE certificate_id = ? AND chunk_id = ?
|
|
115
|
+
`).get(certificateId, chunkId);
|
|
116
|
+
if (!row)
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
chunkId: row.chunk_id,
|
|
120
|
+
chunkType: row.chunk_type,
|
|
121
|
+
reason: row.reason,
|
|
122
|
+
necessityScore: row.necessity_score,
|
|
123
|
+
pathElements: JSON.parse(row.path_elements)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get provenance lineage for a source
|
|
128
|
+
*/
|
|
129
|
+
getProvenanceLineage(contentHash) {
|
|
130
|
+
const lineage = [];
|
|
131
|
+
let currentHash = contentHash;
|
|
132
|
+
while (currentHash) {
|
|
133
|
+
const source = this.db.prepare(`
|
|
134
|
+
SELECT * FROM provenance_sources WHERE content_hash = ?
|
|
135
|
+
`).get(currentHash);
|
|
136
|
+
if (!source)
|
|
137
|
+
break;
|
|
138
|
+
lineage.push({
|
|
139
|
+
id: source.id,
|
|
140
|
+
sourceType: source.source_type,
|
|
141
|
+
sourceId: source.source_id,
|
|
142
|
+
contentHash: source.content_hash,
|
|
143
|
+
parentHash: source.parent_hash,
|
|
144
|
+
derivedFrom: source.derived_from ? JSON.parse(source.derived_from) : undefined,
|
|
145
|
+
creator: source.creator,
|
|
146
|
+
metadata: source.metadata ? JSON.parse(source.metadata) : undefined
|
|
147
|
+
});
|
|
148
|
+
currentHash = source.parent_hash;
|
|
149
|
+
}
|
|
150
|
+
return lineage;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Audit certificate access
|
|
154
|
+
*/
|
|
155
|
+
auditCertificate(certificateId) {
|
|
156
|
+
const certRow = this.db.prepare('SELECT * FROM recall_certificates WHERE id = ?').get(certificateId);
|
|
157
|
+
if (!certRow) {
|
|
158
|
+
throw new Error(`Certificate ${certificateId} not found`);
|
|
159
|
+
}
|
|
160
|
+
const certificate = {
|
|
161
|
+
id: certRow.id,
|
|
162
|
+
queryId: certRow.query_id,
|
|
163
|
+
queryText: certRow.query_text,
|
|
164
|
+
chunkIds: JSON.parse(certRow.chunk_ids),
|
|
165
|
+
chunkTypes: JSON.parse(certRow.chunk_types),
|
|
166
|
+
minimalWhy: JSON.parse(certRow.minimal_why),
|
|
167
|
+
redundancyRatio: certRow.redundancy_ratio,
|
|
168
|
+
completenessScore: certRow.completeness_score,
|
|
169
|
+
merkleRoot: certRow.merkle_root,
|
|
170
|
+
sourceHashes: JSON.parse(certRow.source_hashes),
|
|
171
|
+
proofChain: JSON.parse(certRow.proof_chain),
|
|
172
|
+
policyProof: certRow.policy_proof,
|
|
173
|
+
policyVersion: certRow.policy_version,
|
|
174
|
+
accessLevel: certRow.access_level,
|
|
175
|
+
latencyMs: certRow.latency_ms
|
|
176
|
+
};
|
|
177
|
+
// Get justifications
|
|
178
|
+
const justRows = this.db.prepare(`
|
|
179
|
+
SELECT * FROM justification_paths WHERE certificate_id = ?
|
|
180
|
+
`).all(certificateId);
|
|
181
|
+
const justifications = justRows.map(row => ({
|
|
182
|
+
chunkId: row.chunk_id,
|
|
183
|
+
chunkType: row.chunk_type,
|
|
184
|
+
reason: row.reason,
|
|
185
|
+
necessityScore: row.necessity_score,
|
|
186
|
+
pathElements: JSON.parse(row.path_elements)
|
|
187
|
+
}));
|
|
188
|
+
// Get provenance for each source
|
|
189
|
+
const provenance = new Map();
|
|
190
|
+
for (const hash of certificate.sourceHashes) {
|
|
191
|
+
provenance.set(hash, this.getProvenanceLineage(hash));
|
|
192
|
+
}
|
|
193
|
+
// Calculate quality metrics
|
|
194
|
+
const avgNecessity = justifications.reduce((sum, j) => sum + j.necessityScore, 0) / justifications.length;
|
|
195
|
+
return {
|
|
196
|
+
certificate,
|
|
197
|
+
justifications,
|
|
198
|
+
provenance,
|
|
199
|
+
quality: {
|
|
200
|
+
completeness: certificate.completenessScore,
|
|
201
|
+
redundancy: certificate.redundancyRatio,
|
|
202
|
+
avgNecessity
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// ========================================================================
|
|
207
|
+
// Private Helper Methods
|
|
208
|
+
// ========================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Compute minimal hitting set using greedy algorithm
|
|
211
|
+
* A hitting set contains at least one element from each requirement
|
|
212
|
+
*/
|
|
213
|
+
computeMinimalHittingSet(chunks, requirements) {
|
|
214
|
+
if (requirements.length === 0) {
|
|
215
|
+
return chunks.slice(0, Math.min(3, chunks.length)).map(c => c.id);
|
|
216
|
+
}
|
|
217
|
+
const uncovered = new Set(requirements);
|
|
218
|
+
const selected = [];
|
|
219
|
+
// Greedy: select chunk that covers most uncovered requirements
|
|
220
|
+
while (uncovered.size > 0 && chunks.length > 0) {
|
|
221
|
+
let bestChunk = null;
|
|
222
|
+
let bestCoverage = 0;
|
|
223
|
+
for (const chunk of chunks) {
|
|
224
|
+
const coverage = Array.from(uncovered).filter(req => chunk.content.toLowerCase().includes(req.toLowerCase())).length;
|
|
225
|
+
if (coverage > bestCoverage) {
|
|
226
|
+
bestCoverage = coverage;
|
|
227
|
+
bestChunk = chunk;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!bestChunk)
|
|
231
|
+
break;
|
|
232
|
+
selected.push(bestChunk.id);
|
|
233
|
+
// Remove covered requirements
|
|
234
|
+
for (const req of Array.from(uncovered)) {
|
|
235
|
+
if (bestChunk.content.toLowerCase().includes(req.toLowerCase())) {
|
|
236
|
+
uncovered.delete(req);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Remove selected chunk
|
|
240
|
+
chunks = chunks.filter(c => c.id !== bestChunk.id);
|
|
241
|
+
}
|
|
242
|
+
return selected;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Calculate completeness score
|
|
246
|
+
*/
|
|
247
|
+
calculateCompleteness(minimalWhy, requirements) {
|
|
248
|
+
if (requirements.length === 0)
|
|
249
|
+
return 1.0;
|
|
250
|
+
const chunks = minimalWhy.map(id => {
|
|
251
|
+
// Get chunk content
|
|
252
|
+
const episode = this.db.prepare('SELECT output FROM episodes WHERE id = ?').get(parseInt(id));
|
|
253
|
+
return episode ? episode.output : '';
|
|
254
|
+
});
|
|
255
|
+
const satisfied = requirements.filter(req => chunks.some(content => content && content.toLowerCase().includes(req.toLowerCase())));
|
|
256
|
+
return satisfied.length / requirements.length;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get or create provenance record
|
|
260
|
+
*/
|
|
261
|
+
getOrCreateProvenance(sourceType, sourceId) {
|
|
262
|
+
// Check if provenance exists
|
|
263
|
+
const existing = this.db.prepare(`
|
|
264
|
+
SELECT content_hash FROM provenance_sources
|
|
265
|
+
WHERE source_type = ? AND source_id = ?
|
|
266
|
+
`).get(sourceType, sourceId);
|
|
267
|
+
if (existing) {
|
|
268
|
+
return existing.content_hash;
|
|
269
|
+
}
|
|
270
|
+
// Create new provenance
|
|
271
|
+
const contentHash = this.getContentHash(sourceType, sourceId);
|
|
272
|
+
this.db.prepare(`
|
|
273
|
+
INSERT INTO provenance_sources (source_type, source_id, content_hash, creator)
|
|
274
|
+
VALUES (?, ?, ?, ?)
|
|
275
|
+
`).run(sourceType, sourceId, contentHash, 'system');
|
|
276
|
+
return contentHash;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get content hash for a memory
|
|
280
|
+
*/
|
|
281
|
+
getContentHash(sourceType, sourceId) {
|
|
282
|
+
let content = '';
|
|
283
|
+
switch (sourceType) {
|
|
284
|
+
case 'episode':
|
|
285
|
+
const episode = this.db.prepare('SELECT task, output FROM episodes WHERE id = ?').get(sourceId);
|
|
286
|
+
content = episode ? `${episode.task}:${episode.output}` : '';
|
|
287
|
+
break;
|
|
288
|
+
case 'skill':
|
|
289
|
+
const skill = this.db.prepare('SELECT name, code FROM skills WHERE id = ?').get(sourceId);
|
|
290
|
+
content = skill ? `${skill.name}:${skill.code}` : '';
|
|
291
|
+
break;
|
|
292
|
+
case 'note':
|
|
293
|
+
const note = this.db.prepare('SELECT text FROM notes WHERE id = ?').get(sourceId);
|
|
294
|
+
content = note ? note.text : '';
|
|
295
|
+
break;
|
|
296
|
+
case 'fact':
|
|
297
|
+
const fact = this.db.prepare('SELECT subject, predicate, object FROM facts WHERE id = ?').get(sourceId);
|
|
298
|
+
content = fact ? `${fact.subject}:${fact.predicate}:${fact.object}` : '';
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Build Merkle tree from hashes
|
|
305
|
+
*/
|
|
306
|
+
buildMerkleTree(hashes) {
|
|
307
|
+
if (hashes.length === 0) {
|
|
308
|
+
return { root: '', tree: [[]] };
|
|
309
|
+
}
|
|
310
|
+
const tree = [hashes];
|
|
311
|
+
while (tree[tree.length - 1].length > 1) {
|
|
312
|
+
const level = tree[tree.length - 1];
|
|
313
|
+
const nextLevel = [];
|
|
314
|
+
for (let i = 0; i < level.length; i += 2) {
|
|
315
|
+
if (i + 1 < level.length) {
|
|
316
|
+
const combined = level[i] + level[i + 1];
|
|
317
|
+
nextLevel.push(crypto.createHash('sha256').update(combined).digest('hex'));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
nextLevel.push(level[i]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
tree.push(nextLevel);
|
|
324
|
+
}
|
|
325
|
+
return { root: tree[tree.length - 1][0], tree };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get Merkle proof for a leaf
|
|
329
|
+
*/
|
|
330
|
+
getMerkleProof(merkleTree, leafIndex) {
|
|
331
|
+
const proof = [];
|
|
332
|
+
let index = leafIndex;
|
|
333
|
+
for (let level = 0; level < merkleTree.tree.length - 1; level++) {
|
|
334
|
+
const currentLevel = merkleTree.tree[level];
|
|
335
|
+
const isLeftNode = index % 2 === 0;
|
|
336
|
+
const siblingIndex = isLeftNode ? index + 1 : index - 1;
|
|
337
|
+
if (siblingIndex < currentLevel.length) {
|
|
338
|
+
proof.push({
|
|
339
|
+
hash: currentLevel[siblingIndex],
|
|
340
|
+
position: isLeftNode ? 'right' : 'left'
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
index = Math.floor(index / 2);
|
|
344
|
+
}
|
|
345
|
+
return proof;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Generate certificate ID
|
|
349
|
+
*/
|
|
350
|
+
generateCertificateId(queryId, chunkIds) {
|
|
351
|
+
const data = `${queryId}:${chunkIds.join(',')}:${Date.now()}`;
|
|
352
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Store justification paths
|
|
356
|
+
*/
|
|
357
|
+
storeJustificationPaths(certificateId, chunks, minimalWhy, requirements) {
|
|
358
|
+
const stmt = this.db.prepare(`
|
|
359
|
+
INSERT INTO justification_paths (
|
|
360
|
+
certificate_id, chunk_id, chunk_type, reason, necessity_score, path_elements
|
|
361
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
362
|
+
`);
|
|
363
|
+
for (const chunk of chunks) {
|
|
364
|
+
const isNecessary = minimalWhy.includes(chunk.id);
|
|
365
|
+
const reason = this.determineReason(chunk, requirements);
|
|
366
|
+
const necessityScore = isNecessary ? chunk.relevance : chunk.relevance * 0.5;
|
|
367
|
+
const pathElements = [
|
|
368
|
+
`Retrieved for query`,
|
|
369
|
+
isNecessary ? `Essential for justification` : `Supporting evidence`,
|
|
370
|
+
`Relevance: ${(chunk.relevance * 100).toFixed(1)}%`
|
|
371
|
+
];
|
|
372
|
+
stmt.run(certificateId, chunk.id, chunk.type, reason, necessityScore, JSON.stringify(pathElements));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Determine reason for inclusion
|
|
377
|
+
*/
|
|
378
|
+
determineReason(chunk, requirements) {
|
|
379
|
+
if (chunk.relevance > 0.9)
|
|
380
|
+
return 'semantic_match';
|
|
381
|
+
if (chunk.relevance > 0.7)
|
|
382
|
+
return 'causal_link';
|
|
383
|
+
if (chunk.relevance > 0.5)
|
|
384
|
+
return 'prerequisite';
|
|
385
|
+
return 'constraint';
|
|
386
|
+
}
|
|
387
|
+
}
|