@hardkas/query 0.2.1-alpha → 0.2.2-alpha.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/dist/index.d.ts +156 -35
- package/dist/index.js +545 -407
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,147 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/backend-fs.ts
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
+
var FilesystemQueryBackend = class {
|
|
5
|
+
rootDir;
|
|
6
|
+
constructor(rootDir) {
|
|
7
|
+
this.rootDir = rootDir;
|
|
8
|
+
}
|
|
9
|
+
isReady() {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
kind() {
|
|
13
|
+
return "filesystem";
|
|
14
|
+
}
|
|
15
|
+
async findArtifacts(filters) {
|
|
16
|
+
const files = await this.scanFiles(this.rootDir);
|
|
17
|
+
const docs = [];
|
|
18
|
+
for (const f of files) {
|
|
19
|
+
const raw = await this.readJson(f);
|
|
20
|
+
if (!raw || !raw.schema) continue;
|
|
21
|
+
if (filters?.schema && raw.schema !== filters.schema) continue;
|
|
22
|
+
if (filters?.mode && raw.mode !== filters.mode) continue;
|
|
23
|
+
if (filters?.networkId && raw.networkId !== filters.networkId) continue;
|
|
24
|
+
docs.push({
|
|
25
|
+
contentHash: raw.contentHash || "",
|
|
26
|
+
schema: raw.schema,
|
|
27
|
+
version: raw.version || "unknown",
|
|
28
|
+
kind: raw.kind || raw.schema,
|
|
29
|
+
mode: raw.mode,
|
|
30
|
+
networkId: raw.networkId,
|
|
31
|
+
createdAt: raw.createdAt || null,
|
|
32
|
+
txId: raw.txId || null,
|
|
33
|
+
artifactId: raw.artifactId || raw.contentHash || "",
|
|
34
|
+
path: f,
|
|
35
|
+
payload: raw
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return docs;
|
|
39
|
+
}
|
|
40
|
+
async getArtifact(idOrHash) {
|
|
41
|
+
const artifacts = await this.findArtifacts();
|
|
42
|
+
return artifacts.find(
|
|
43
|
+
(a) => a.contentHash === idOrHash || a.payload.lineage?.artifactId === idOrHash || a.payload.artifactId === idOrHash || a.payload.txId === idOrHash
|
|
44
|
+
) || null;
|
|
45
|
+
}
|
|
46
|
+
async getEvents(filters) {
|
|
47
|
+
const eventsPath = path.join(this.rootDir, ".hardkas", "events.jsonl");
|
|
48
|
+
const docs = [];
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(eventsPath, "utf-8");
|
|
51
|
+
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(line);
|
|
55
|
+
if (filters?.kind && parsed.kind !== filters.kind) continue;
|
|
56
|
+
if (filters?.txId && parsed.txId !== filters.txId) continue;
|
|
57
|
+
docs.push({
|
|
58
|
+
eventId: parsed.eventId,
|
|
59
|
+
kind: parsed.kind,
|
|
60
|
+
domain: parsed.domain,
|
|
61
|
+
timestamp: parsed.timestamp || null,
|
|
62
|
+
workflowId: parsed.workflowId,
|
|
63
|
+
correlationId: parsed.correlationId,
|
|
64
|
+
causationId: parsed.causationId || null,
|
|
65
|
+
txId: parsed.txId || null,
|
|
66
|
+
artifactId: parsed.artifactId || null,
|
|
67
|
+
networkId: parsed.networkId,
|
|
68
|
+
payload: parsed.payload
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
return docs;
|
|
76
|
+
}
|
|
77
|
+
async getLineageEdges(filters) {
|
|
78
|
+
const artifacts = await this.findArtifacts();
|
|
79
|
+
const edges = [];
|
|
80
|
+
for (const a of artifacts) {
|
|
81
|
+
if (a.payload.lineage?.parentArtifactId) {
|
|
82
|
+
edges.push({
|
|
83
|
+
parentArtifactId: a.payload.lineage.parentArtifactId,
|
|
84
|
+
childArtifactId: a.artifactId,
|
|
85
|
+
lineageId: a.payload.lineage.lineageId || "unknown",
|
|
86
|
+
rule: "parent",
|
|
87
|
+
createdAt: a.createdAt
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return edges.filter((e) => {
|
|
92
|
+
if (filters?.parentHash && e.parentArtifactId !== filters.parentHash) return false;
|
|
93
|
+
if (filters?.childHash && e.childArtifactId !== filters.childHash) return false;
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async getStoreStatus() {
|
|
98
|
+
return "fresh";
|
|
99
|
+
}
|
|
100
|
+
async doctor() {
|
|
101
|
+
return { ok: true, backend: "filesystem" };
|
|
102
|
+
}
|
|
103
|
+
async rebuild() {
|
|
104
|
+
}
|
|
105
|
+
async findReceipts(filters) {
|
|
106
|
+
return this.findArtifacts({ schema: "hardkas.txReceipt", ...filters });
|
|
107
|
+
}
|
|
108
|
+
async findTraces(filters) {
|
|
109
|
+
const artifacts = await this.findArtifacts({ schema: "hardkas.txTrace" });
|
|
110
|
+
if (filters?.txId) {
|
|
111
|
+
return artifacts.filter((a) => a.payload.txId === filters.txId);
|
|
112
|
+
}
|
|
113
|
+
return artifacts;
|
|
114
|
+
}
|
|
115
|
+
async scanFiles(dir) {
|
|
116
|
+
const results = [];
|
|
117
|
+
try {
|
|
118
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const fullPath = path.join(dir, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
123
|
+
results.push(...await this.scanFiles(fullPath));
|
|
124
|
+
} else if (entry.name.endsWith(".json") && !entry.name.endsWith(".enc.json") && entry.name !== "events.jsonl") {
|
|
125
|
+
results.push(fullPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
return results;
|
|
131
|
+
}
|
|
132
|
+
async readJson(file) {
|
|
133
|
+
try {
|
|
134
|
+
const content = await fs.readFile(file, "utf-8");
|
|
135
|
+
return JSON.parse(content);
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/adapters/artifact-adapter.ts
|
|
143
|
+
import fs2 from "fs/promises";
|
|
144
|
+
import path2 from "path";
|
|
4
145
|
import {
|
|
5
146
|
calculateContentHash,
|
|
6
147
|
verifyArtifactIntegrity,
|
|
@@ -123,144 +264,145 @@ var VALID_TRANSITIONS = {
|
|
|
123
264
|
"hardkas.txPlan": ["hardkas.signedTx"],
|
|
124
265
|
"hardkas.signedTx": ["hardkas.txReceipt"]
|
|
125
266
|
};
|
|
267
|
+
function createExplainBlock(options) {
|
|
268
|
+
return {
|
|
269
|
+
backend: options.backend,
|
|
270
|
+
executionPlan: options.executionPlan,
|
|
271
|
+
indexesUsed: options.indexesUsed || [],
|
|
272
|
+
filtersApplied: options.filtersApplied || [],
|
|
273
|
+
rowsRead: options.rowsRead,
|
|
274
|
+
scannedFiles: options.scannedFiles,
|
|
275
|
+
freshness: options.freshness,
|
|
276
|
+
warnings: options.warnings || []
|
|
277
|
+
};
|
|
278
|
+
}
|
|
126
279
|
function explainIntegrity(artifact, integrity) {
|
|
127
|
-
const
|
|
280
|
+
const causalChain = [];
|
|
281
|
+
const evidence = [];
|
|
128
282
|
let order = 1;
|
|
129
|
-
|
|
283
|
+
if (artifact.contentHash) {
|
|
284
|
+
evidence.push({ type: "contentHash", value: artifact.contentHash });
|
|
285
|
+
}
|
|
286
|
+
evidence.push({ type: "filePath", value: artifact.filePath });
|
|
287
|
+
causalChain.push({
|
|
130
288
|
order: order++,
|
|
131
289
|
assertion: integrity.schemaValid ? `Schema "${artifact.schema}" is a recognized HardKAS artifact schema` : `Schema "${artifact.schema}" is not recognized`,
|
|
132
290
|
evidence: `artifact.schema = "${artifact.schema}"`,
|
|
133
291
|
rule: "ARTIFACT_SCHEMAS constant (artifacts/constants.ts)"
|
|
134
292
|
});
|
|
135
293
|
if (artifact.contentHash) {
|
|
136
|
-
|
|
294
|
+
causalChain.push({
|
|
137
295
|
order: order++,
|
|
138
|
-
assertion: integrity.hashMatch ? "Content hash matches recomputed hash" : "Content hash does NOT match recomputed hash
|
|
139
|
-
evidence: `
|
|
296
|
+
assertion: integrity.hashMatch ? "Content hash matches recomputed hash" : "Content hash does NOT match recomputed hash",
|
|
297
|
+
evidence: `hash(payload) === "${artifact.contentHash}"`,
|
|
140
298
|
rule: "canonicalStringify + SHA-256 (artifacts/canonical.ts)"
|
|
141
299
|
});
|
|
142
|
-
} else {
|
|
143
|
-
steps.push({
|
|
144
|
-
order: order++,
|
|
145
|
-
assertion: "Artifact has no contentHash field (pre-verification artifact)",
|
|
146
|
-
evidence: "artifact.contentHash is undefined",
|
|
147
|
-
rule: "contentHash is optional but recommended"
|
|
148
|
-
});
|
|
149
300
|
}
|
|
150
301
|
if (integrity.errors.length > 0) {
|
|
151
302
|
for (const err of integrity.errors) {
|
|
152
|
-
|
|
303
|
+
causalChain.push({
|
|
153
304
|
order: order++,
|
|
154
|
-
assertion: `
|
|
155
|
-
evidence:
|
|
156
|
-
rule: "
|
|
305
|
+
assertion: `Semantic violation detected: ${err}`,
|
|
306
|
+
evidence: "Verification engine failure",
|
|
307
|
+
rule: "verifyArtifactSemantics (artifacts/verify.ts)"
|
|
157
308
|
});
|
|
158
309
|
}
|
|
159
310
|
}
|
|
160
311
|
return {
|
|
161
|
-
question: `Why is artifact "${artifact.schema}"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
312
|
+
question: `Why is artifact "${artifact.schema}" ${integrity.ok ? "valid" : "invalid"}?`,
|
|
313
|
+
answer: integrity.ok ? "All deterministic checks (schema, hash, semantics) passed successfully." : `Verification failed: ${integrity.errors.join("; ")}`,
|
|
314
|
+
evidence,
|
|
315
|
+
causalChain,
|
|
316
|
+
model: "integrity-verifier",
|
|
317
|
+
confidence: "definitive"
|
|
167
318
|
};
|
|
168
319
|
}
|
|
169
320
|
function explainTransition(transition) {
|
|
170
|
-
const
|
|
321
|
+
const causalChain = [];
|
|
322
|
+
const evidence = [
|
|
323
|
+
{ type: "contentHash", value: transition.from.contentHash },
|
|
324
|
+
{ type: "contentHash", value: transition.to.contentHash }
|
|
325
|
+
];
|
|
171
326
|
let order = 1;
|
|
172
327
|
const allowed = VALID_TRANSITIONS[transition.from.schema] ?? [];
|
|
173
|
-
|
|
174
|
-
order: order++,
|
|
175
|
-
assertion: allowed.length > 0 ? `Schema "${transition.from.schema}" allows transitions to: ${allowed.join(", ")}` : `Schema "${transition.from.schema}" has no defined transitions`,
|
|
176
|
-
evidence: `VALID_TRANSITIONS["${transition.from.schema}"] = [${allowed.map((s) => `"${s}"`).join(", ")}]`,
|
|
177
|
-
rule: "Lineage transition table (artifacts/lineage.ts)"
|
|
178
|
-
});
|
|
179
|
-
steps.push({
|
|
328
|
+
causalChain.push({
|
|
180
329
|
order: order++,
|
|
181
|
-
assertion: transition.valid ? `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is
|
|
182
|
-
evidence: `
|
|
183
|
-
rule: "Lineage transition table
|
|
330
|
+
assertion: transition.valid ? `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is allowed` : `Transition "${transition.from.schema}" \u2192 "${transition.to.schema}" is NOT allowed`,
|
|
331
|
+
evidence: `allowed_from_${transition.from.schema} = [${allowed.join(", ")}]`,
|
|
332
|
+
rule: "Lineage transition table"
|
|
184
333
|
});
|
|
185
|
-
const
|
|
186
|
-
|
|
334
|
+
const contextMatch = transition.from.networkId === transition.to.networkId && transition.from.mode === transition.to.mode;
|
|
335
|
+
causalChain.push({
|
|
187
336
|
order: order++,
|
|
188
|
-
assertion:
|
|
189
|
-
evidence: `
|
|
190
|
-
rule: "
|
|
337
|
+
assertion: contextMatch ? "Execution context (network, mode) is consistent" : "EXECUTION CONTEXT MISMATCH detected",
|
|
338
|
+
evidence: `from: ${transition.from.networkId}/${transition.from.mode}, to: ${transition.to.networkId}/${transition.to.mode}`,
|
|
339
|
+
rule: "Context isolation policy"
|
|
191
340
|
});
|
|
192
|
-
const modeMatch = transition.from.mode === transition.to.mode;
|
|
193
|
-
steps.push({
|
|
194
|
-
order: order++,
|
|
195
|
-
assertion: modeMatch ? `Mode is consistent: both "${transition.from.mode}"` : `MODE CONTAMINATION: "${transition.from.mode}" \u2192 "${transition.to.mode}"`,
|
|
196
|
-
evidence: `parent.mode = "${transition.from.mode}", child.mode = "${transition.to.mode}"`,
|
|
197
|
-
rule: "MODE_CONTAMINATION check (artifacts/lineage.ts)"
|
|
198
|
-
});
|
|
199
|
-
const lineageMatch = transition.from.lineageId === transition.to.lineageId;
|
|
200
|
-
steps.push({
|
|
201
|
-
order: order++,
|
|
202
|
-
assertion: lineageMatch ? "Lineage ID is continuous" : "LINEAGE ID MISMATCH \u2014 artifacts belong to different lineage chains",
|
|
203
|
-
evidence: `parent.lineageId = "${transition.from.lineageId}", child.lineageId = "${transition.to.lineageId}"`,
|
|
204
|
-
rule: "LINEAGE_ID_MISMATCH check (artifacts/lineage.ts)"
|
|
205
|
-
});
|
|
206
|
-
const allValid = transition.valid && networkMatch && modeMatch && lineageMatch;
|
|
207
341
|
return {
|
|
208
|
-
question: `Why
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
342
|
+
question: `Why transition ${transition.from.schema} \u2192 ${transition.to.schema}?`,
|
|
343
|
+
answer: transition.valid && contextMatch ? "Causal chain is consistent with HardKAS state transition rules." : "Workflow violation: invalid schema transition or context contamination.",
|
|
344
|
+
evidence,
|
|
345
|
+
causalChain,
|
|
346
|
+
model: "causal-lineage",
|
|
347
|
+
confidence: "definitive"
|
|
214
348
|
};
|
|
215
349
|
}
|
|
216
350
|
function explainOrphan(node, missingParentId) {
|
|
217
351
|
return {
|
|
218
|
-
question: `Why is artifact "${node.
|
|
219
|
-
|
|
220
|
-
|
|
352
|
+
question: `Why is artifact "${node.artifactId.slice(0, 8)}" an orphan?`,
|
|
353
|
+
answer: "The parent artifact referenced in the lineage metadata is missing from the indexed store.",
|
|
354
|
+
evidence: [
|
|
355
|
+
{ type: "artifactId", value: node.artifactId },
|
|
356
|
+
{ type: "artifactId", value: missingParentId }
|
|
357
|
+
],
|
|
358
|
+
causalChain: [
|
|
221
359
|
{
|
|
222
360
|
order: 1,
|
|
223
|
-
assertion:
|
|
224
|
-
evidence: `
|
|
225
|
-
rule: "Lineage
|
|
361
|
+
assertion: "Artifact defines a parent dependency",
|
|
362
|
+
evidence: `parentArtifactId = "${missingParentId}"`,
|
|
363
|
+
rule: "Lineage metadata requirement"
|
|
226
364
|
},
|
|
227
365
|
{
|
|
228
366
|
order: 2,
|
|
229
|
-
assertion: "
|
|
230
|
-
evidence: "
|
|
231
|
-
rule: "
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
order: 3,
|
|
235
|
-
assertion: "Artifact is classified as an orphan",
|
|
236
|
-
evidence: "Missing parent means the lineage chain is broken",
|
|
237
|
-
rule: "Orphan detection: parentArtifactId exists but not resolvable"
|
|
367
|
+
assertion: "Parent artifact lookup failed",
|
|
368
|
+
evidence: "Index scan for artifactId returned 0 results",
|
|
369
|
+
rule: "Store integrity policy"
|
|
238
370
|
}
|
|
239
371
|
],
|
|
240
|
-
model: "
|
|
241
|
-
confidence: "definitive"
|
|
242
|
-
references: [node.contentHash, missingParentId]
|
|
372
|
+
model: "orphan-analysis",
|
|
373
|
+
confidence: "definitive"
|
|
243
374
|
};
|
|
244
375
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
376
|
+
function formatExplainBlock(block) {
|
|
377
|
+
const lines = [];
|
|
378
|
+
lines.push(` [Explain: Technical Diagnostics]`);
|
|
379
|
+
lines.push(` Backend: ${block.backend}`);
|
|
380
|
+
lines.push(` Freshness: ${block.freshness}`);
|
|
381
|
+
lines.push(` Rows Read: ${block.rowsRead}`);
|
|
382
|
+
lines.push(` Files Scan: ${block.scannedFiles}`);
|
|
383
|
+
if (block.executionPlan.length > 0) {
|
|
384
|
+
lines.push(` Plan: ${block.executionPlan.join(" \u2192 ")}`);
|
|
385
|
+
}
|
|
386
|
+
if (block.warnings.length > 0) {
|
|
387
|
+
lines.push(` Warnings:`);
|
|
388
|
+
for (const w of block.warnings) lines.push(` \u26A0 ${w}`);
|
|
389
|
+
}
|
|
390
|
+
return lines.join("\n");
|
|
247
391
|
}
|
|
248
|
-
function
|
|
392
|
+
function formatWhyBlock(block) {
|
|
249
393
|
const lines = [];
|
|
250
|
-
lines.push(`
|
|
394
|
+
lines.push(` [Why: Causal Analysis]`);
|
|
395
|
+
lines.push(` Q: ${block.question}`);
|
|
396
|
+
lines.push(` A: ${block.answer}`);
|
|
251
397
|
lines.push("");
|
|
252
|
-
for (const step of
|
|
253
|
-
lines.push(`
|
|
254
|
-
lines.push(`
|
|
255
|
-
if (step.rule) {
|
|
256
|
-
lines.push(` Rule: ${step.rule}`);
|
|
257
|
-
}
|
|
398
|
+
for (const step of block.causalChain) {
|
|
399
|
+
lines.push(` ${step.order}. ${step.assertion}`);
|
|
400
|
+
lines.push(` Evidence: ${step.evidence}`);
|
|
401
|
+
if (step.rule) lines.push(` Rule: ${step.rule}`);
|
|
258
402
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (chain.references.length > 0) {
|
|
263
|
-
lines.push(`References: ${chain.references.map((r) => r.slice(0, 16) + "...").join(", ")}`);
|
|
403
|
+
if (block.evidence.length > 0) {
|
|
404
|
+
lines.push("");
|
|
405
|
+
lines.push(` Evidence Refs: ${block.evidence.map((e) => `${e.type}:${e.value.slice(0, 12)}...`).join(", ")}`);
|
|
264
406
|
}
|
|
265
407
|
return lines.join("\n");
|
|
266
408
|
}
|
|
@@ -270,8 +412,10 @@ var KNOWN_SCHEMAS = new Set(Object.values(ARTIFACT_SCHEMAS));
|
|
|
270
412
|
var ArtifactQueryAdapter = class {
|
|
271
413
|
domain = "artifacts";
|
|
272
414
|
rootDir;
|
|
273
|
-
|
|
415
|
+
backend;
|
|
416
|
+
constructor(rootDir, backend) {
|
|
274
417
|
this.rootDir = rootDir;
|
|
418
|
+
this.backend = backend;
|
|
275
419
|
}
|
|
276
420
|
supportedOps() {
|
|
277
421
|
return ["list", "inspect", "diff", "verify"];
|
|
@@ -298,12 +442,25 @@ var ArtifactQueryAdapter = class {
|
|
|
298
442
|
// -------------------------------------------------------------------------
|
|
299
443
|
async executeList(request) {
|
|
300
444
|
const start = Date.now();
|
|
301
|
-
const
|
|
445
|
+
const docs = await this.backend.findArtifacts();
|
|
302
446
|
const items = [];
|
|
303
|
-
for (const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
447
|
+
for (const doc of docs) {
|
|
448
|
+
const item = {
|
|
449
|
+
filePath: doc.path,
|
|
450
|
+
schema: doc.schema,
|
|
451
|
+
version: doc.version,
|
|
452
|
+
networkId: doc.networkId,
|
|
453
|
+
mode: doc.mode,
|
|
454
|
+
createdAt: doc.createdAt,
|
|
455
|
+
contentHash: doc.contentHash,
|
|
456
|
+
payload: doc.payload,
|
|
457
|
+
// Optional mapping for common fields
|
|
458
|
+
status: doc.payload.status,
|
|
459
|
+
from: doc.payload.from,
|
|
460
|
+
to: doc.payload.to,
|
|
461
|
+
amountSompi: doc.payload.amountSompi,
|
|
462
|
+
lineage: doc.payload.lineage
|
|
463
|
+
};
|
|
307
464
|
if (evaluateFilters(item, request.filters)) {
|
|
308
465
|
items.push(item);
|
|
309
466
|
}
|
|
@@ -311,9 +468,9 @@ var ArtifactQueryAdapter = class {
|
|
|
311
468
|
const sorted = this.sortItems(items, request.sort);
|
|
312
469
|
const total = sorted.length;
|
|
313
470
|
const paged = sorted.slice(request.offset, request.offset + request.limit);
|
|
314
|
-
let
|
|
471
|
+
let why;
|
|
315
472
|
if (request.explain) {
|
|
316
|
-
|
|
473
|
+
why = paged.map((item) => explainIntegrity(item, {
|
|
317
474
|
ok: true,
|
|
318
475
|
hashMatch: true,
|
|
319
476
|
schemaValid: KNOWN_SCHEMAS.has(item.schema),
|
|
@@ -328,11 +485,11 @@ var ArtifactQueryAdapter = class {
|
|
|
328
485
|
truncated: total > request.offset + request.limit,
|
|
329
486
|
deterministic: true,
|
|
330
487
|
queryHash: computeQueryHash(paged),
|
|
331
|
-
|
|
488
|
+
why,
|
|
332
489
|
annotations: {
|
|
333
490
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
491
|
executionMs: Date.now() - start,
|
|
335
|
-
filesScanned:
|
|
492
|
+
filesScanned: docs.length
|
|
336
493
|
}
|
|
337
494
|
};
|
|
338
495
|
}
|
|
@@ -383,9 +540,9 @@ var ArtifactQueryAdapter = class {
|
|
|
383
540
|
staleness,
|
|
384
541
|
lineageStatus
|
|
385
542
|
};
|
|
386
|
-
let
|
|
543
|
+
let why;
|
|
387
544
|
if (request.explain) {
|
|
388
|
-
|
|
545
|
+
why = [explainIntegrity(item, inspectResult.integrity)];
|
|
389
546
|
}
|
|
390
547
|
return {
|
|
391
548
|
domain: "artifacts",
|
|
@@ -395,7 +552,7 @@ var ArtifactQueryAdapter = class {
|
|
|
395
552
|
truncated: false,
|
|
396
553
|
deterministic: true,
|
|
397
554
|
queryHash: computeQueryHash([inspectResult]),
|
|
398
|
-
|
|
555
|
+
why,
|
|
399
556
|
annotations: {
|
|
400
557
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
401
558
|
executionMs: Date.now() - start,
|
|
@@ -472,12 +629,12 @@ var ArtifactQueryAdapter = class {
|
|
|
472
629
|
async walkDir(dir, out) {
|
|
473
630
|
let entries;
|
|
474
631
|
try {
|
|
475
|
-
entries = await
|
|
632
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
476
633
|
} catch {
|
|
477
634
|
return;
|
|
478
635
|
}
|
|
479
636
|
for (const entry of entries) {
|
|
480
|
-
const full =
|
|
637
|
+
const full = path2.join(dir, entry.name);
|
|
481
638
|
if (entry.isDirectory()) {
|
|
482
639
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
|
|
483
640
|
await this.walkDir(full, out);
|
|
@@ -488,7 +645,7 @@ var ArtifactQueryAdapter = class {
|
|
|
488
645
|
}
|
|
489
646
|
async readJsonSafe(filePath) {
|
|
490
647
|
try {
|
|
491
|
-
const content = await
|
|
648
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
492
649
|
return JSON.parse(content);
|
|
493
650
|
} catch {
|
|
494
651
|
return null;
|
|
@@ -496,7 +653,7 @@ var ArtifactQueryAdapter = class {
|
|
|
496
653
|
}
|
|
497
654
|
async resolveTarget(target) {
|
|
498
655
|
if (target.includes("/") || target.includes("\\") || target.endsWith(".json")) {
|
|
499
|
-
return
|
|
656
|
+
return path2.resolve(target);
|
|
500
657
|
}
|
|
501
658
|
const files = await this.scanArtifactFiles();
|
|
502
659
|
for (const f of files) {
|
|
@@ -517,7 +674,7 @@ var ArtifactQueryAdapter = class {
|
|
|
517
674
|
});
|
|
518
675
|
} else {
|
|
519
676
|
sorted.sort((a, b) => {
|
|
520
|
-
const cmp = b.createdAt.localeCompare(a.createdAt);
|
|
677
|
+
const cmp = (b.createdAt ?? "").localeCompare(a.createdAt ?? "");
|
|
521
678
|
return cmp !== 0 ? cmp : a.schema.localeCompare(b.schema);
|
|
522
679
|
});
|
|
523
680
|
}
|
|
@@ -533,6 +690,7 @@ function toArtifactQueryItem(raw, filePath) {
|
|
|
533
690
|
mode: raw.mode || "unknown",
|
|
534
691
|
createdAt: raw.createdAt || "",
|
|
535
692
|
contentHash: raw.contentHash,
|
|
693
|
+
payload: raw,
|
|
536
694
|
from: raw.from,
|
|
537
695
|
to: raw.to,
|
|
538
696
|
amountSompi: raw.amountSompi,
|
|
@@ -554,8 +712,8 @@ function classifyStaleness(hours) {
|
|
|
554
712
|
}
|
|
555
713
|
|
|
556
714
|
// src/adapters/lineage-adapter.ts
|
|
557
|
-
import
|
|
558
|
-
import
|
|
715
|
+
import fs3 from "fs/promises";
|
|
716
|
+
import path3 from "path";
|
|
559
717
|
var VALID_TRANSITIONS2 = {
|
|
560
718
|
"hardkas.snapshot": ["hardkas.txPlan"],
|
|
561
719
|
"hardkas.txPlan": ["hardkas.signedTx"],
|
|
@@ -564,8 +722,10 @@ var VALID_TRANSITIONS2 = {
|
|
|
564
722
|
var LineageQueryAdapter = class {
|
|
565
723
|
domain = "lineage";
|
|
566
724
|
rootDir;
|
|
567
|
-
|
|
725
|
+
backend;
|
|
726
|
+
constructor(rootDir, backend) {
|
|
568
727
|
this.rootDir = rootDir;
|
|
728
|
+
this.backend = backend;
|
|
569
729
|
}
|
|
570
730
|
supportedOps() {
|
|
571
731
|
return ["chain", "transitions", "orphans"];
|
|
@@ -611,9 +771,9 @@ var LineageQueryAdapter = class {
|
|
|
611
771
|
transitions,
|
|
612
772
|
complete
|
|
613
773
|
};
|
|
614
|
-
let
|
|
774
|
+
let why;
|
|
615
775
|
if (request.explain) {
|
|
616
|
-
|
|
776
|
+
why = transitions.map((t) => explainTransition(t));
|
|
617
777
|
}
|
|
618
778
|
return {
|
|
619
779
|
domain: "lineage",
|
|
@@ -623,7 +783,7 @@ var LineageQueryAdapter = class {
|
|
|
623
783
|
truncated: false,
|
|
624
784
|
deterministic: true,
|
|
625
785
|
queryHash: computeQueryHash([result]),
|
|
626
|
-
|
|
786
|
+
why,
|
|
627
787
|
annotations: {
|
|
628
788
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
629
789
|
executionMs: Date.now() - start,
|
|
@@ -660,9 +820,9 @@ var LineageQueryAdapter = class {
|
|
|
660
820
|
return a.from.contentHash.localeCompare(b.from.contentHash);
|
|
661
821
|
});
|
|
662
822
|
const paged = transitions.slice(request.offset, request.offset + request.limit);
|
|
663
|
-
let
|
|
823
|
+
let why;
|
|
664
824
|
if (request.explain) {
|
|
665
|
-
|
|
825
|
+
why = paged.map((t) => explainTransition(t));
|
|
666
826
|
}
|
|
667
827
|
return {
|
|
668
828
|
domain: "lineage",
|
|
@@ -672,7 +832,7 @@ var LineageQueryAdapter = class {
|
|
|
672
832
|
truncated: transitions.length > request.offset + request.limit,
|
|
673
833
|
deterministic: true,
|
|
674
834
|
queryHash: computeQueryHash(paged),
|
|
675
|
-
|
|
835
|
+
why,
|
|
676
836
|
annotations: {
|
|
677
837
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
678
838
|
executionMs: Date.now() - start,
|
|
@@ -700,9 +860,9 @@ var LineageQueryAdapter = class {
|
|
|
700
860
|
}
|
|
701
861
|
orphans.sort((a, b) => a.node.contentHash.localeCompare(b.node.contentHash));
|
|
702
862
|
const paged = orphans.slice(request.offset, request.offset + request.limit);
|
|
703
|
-
let
|
|
863
|
+
let why;
|
|
704
864
|
if (request.explain) {
|
|
705
|
-
|
|
865
|
+
why = paged.map((o) => explainOrphan(o.node, o.missingParentId));
|
|
706
866
|
}
|
|
707
867
|
return {
|
|
708
868
|
domain: "lineage",
|
|
@@ -712,7 +872,7 @@ var LineageQueryAdapter = class {
|
|
|
712
872
|
truncated: orphans.length > request.offset + request.limit,
|
|
713
873
|
deterministic: true,
|
|
714
874
|
queryHash: computeQueryHash(paged),
|
|
715
|
-
|
|
875
|
+
why,
|
|
716
876
|
annotations: {
|
|
717
877
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
718
878
|
executionMs: Date.now() - start,
|
|
@@ -728,22 +888,22 @@ var LineageQueryAdapter = class {
|
|
|
728
888
|
const byArtifactId = /* @__PURE__ */ new Map();
|
|
729
889
|
const byContentHash = /* @__PURE__ */ new Map();
|
|
730
890
|
const children = /* @__PURE__ */ new Map();
|
|
731
|
-
const
|
|
732
|
-
for (const
|
|
733
|
-
const raw =
|
|
891
|
+
const docs = await this.backend.findArtifacts();
|
|
892
|
+
for (const doc of docs) {
|
|
893
|
+
const raw = doc.payload;
|
|
734
894
|
if (!raw?.schema || !raw.lineage) continue;
|
|
735
895
|
const node = {
|
|
736
|
-
contentHash:
|
|
737
|
-
schema:
|
|
896
|
+
contentHash: doc.contentHash,
|
|
897
|
+
schema: doc.schema,
|
|
738
898
|
artifactId: raw.lineage.artifactId || "",
|
|
739
899
|
parentArtifactId: raw.lineage.parentArtifactId,
|
|
740
900
|
rootArtifactId: raw.lineage.rootArtifactId || "",
|
|
741
901
|
lineageId: raw.lineage.lineageId || "",
|
|
742
902
|
sequence: raw.lineage.sequence,
|
|
743
|
-
filePath,
|
|
744
|
-
networkId:
|
|
745
|
-
mode:
|
|
746
|
-
createdAt:
|
|
903
|
+
filePath: doc.path,
|
|
904
|
+
networkId: doc.networkId,
|
|
905
|
+
mode: doc.mode,
|
|
906
|
+
createdAt: doc.createdAt || ""
|
|
747
907
|
};
|
|
748
908
|
nodes.push(node);
|
|
749
909
|
if (node.artifactId) byArtifactId.set(node.artifactId, node);
|
|
@@ -754,7 +914,7 @@ var LineageQueryAdapter = class {
|
|
|
754
914
|
children.set(node.parentArtifactId, existing);
|
|
755
915
|
}
|
|
756
916
|
}
|
|
757
|
-
return { nodes, byArtifactId, byContentHash, children, totalFiles:
|
|
917
|
+
return { nodes, byArtifactId, byContentHash, children, totalFiles: docs.length };
|
|
758
918
|
}
|
|
759
919
|
// -------------------------------------------------------------------------
|
|
760
920
|
// Graph Traversal
|
|
@@ -812,12 +972,12 @@ var LineageQueryAdapter = class {
|
|
|
812
972
|
async walkDir(dir, out) {
|
|
813
973
|
let entries;
|
|
814
974
|
try {
|
|
815
|
-
entries = await
|
|
975
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
816
976
|
} catch {
|
|
817
977
|
return;
|
|
818
978
|
}
|
|
819
979
|
for (const entry of entries) {
|
|
820
|
-
const full =
|
|
980
|
+
const full = path3.join(dir, entry.name);
|
|
821
981
|
if (entry.isDirectory()) {
|
|
822
982
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "keystores") continue;
|
|
823
983
|
await this.walkDir(full, out);
|
|
@@ -828,7 +988,7 @@ var LineageQueryAdapter = class {
|
|
|
828
988
|
}
|
|
829
989
|
async readJsonSafe(filePath) {
|
|
830
990
|
try {
|
|
831
|
-
const content = await
|
|
991
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
832
992
|
return JSON.parse(content);
|
|
833
993
|
} catch {
|
|
834
994
|
return null;
|
|
@@ -837,14 +997,15 @@ var LineageQueryAdapter = class {
|
|
|
837
997
|
};
|
|
838
998
|
|
|
839
999
|
// src/adapters/replay-adapter.ts
|
|
840
|
-
import
|
|
841
|
-
import path3 from "path";
|
|
1000
|
+
import fs4 from "fs/promises";
|
|
842
1001
|
import { calculateContentHash as calculateContentHash2 } from "@hardkas/artifacts";
|
|
843
1002
|
var ReplayQueryAdapter = class {
|
|
844
1003
|
domain = "replay";
|
|
845
1004
|
rootDir;
|
|
846
|
-
|
|
1005
|
+
backend;
|
|
1006
|
+
constructor(rootDir, backend) {
|
|
847
1007
|
this.rootDir = rootDir;
|
|
1008
|
+
this.backend = backend;
|
|
848
1009
|
}
|
|
849
1010
|
supportedOps() {
|
|
850
1011
|
return ["list", "summary", "divergences", "invariants"];
|
|
@@ -871,13 +1032,15 @@ var ReplayQueryAdapter = class {
|
|
|
871
1032
|
// -------------------------------------------------------------------------
|
|
872
1033
|
async executeList(request) {
|
|
873
1034
|
const start = Date.now();
|
|
874
|
-
const receipts = await this.
|
|
875
|
-
|
|
1035
|
+
const receipts = await this.backend.findReceipts({
|
|
1036
|
+
status: request.filters.find((f) => f.field === "status")?.value
|
|
1037
|
+
});
|
|
1038
|
+
const traces = await this.backend.findTraces();
|
|
876
1039
|
const traceMap = new Map(traces.map((t) => [t.txId, t]));
|
|
877
1040
|
const items = [];
|
|
878
1041
|
for (const r of receipts) {
|
|
879
1042
|
const trace = traceMap.get(r.txId);
|
|
880
|
-
const item = toSummary(r, trace);
|
|
1043
|
+
const item = toSummary(r.payload, trace?.payload);
|
|
881
1044
|
if (evaluateFilters(item, request.filters)) {
|
|
882
1045
|
items.push(item);
|
|
883
1046
|
}
|
|
@@ -910,10 +1073,11 @@ var ReplayQueryAdapter = class {
|
|
|
910
1073
|
const start = Date.now();
|
|
911
1074
|
const txId = request.params["txId"];
|
|
912
1075
|
if (!txId) throw new Error("summary requires params.txId");
|
|
913
|
-
const receipt = await this.
|
|
914
|
-
if (!receipt) throw new Error(`Receipt not found for txId: ${txId}`);
|
|
915
|
-
const
|
|
916
|
-
const
|
|
1076
|
+
const receipt = await this.backend.getArtifact(txId);
|
|
1077
|
+
if (!receipt || receipt.schema !== "hardkas.txReceipt") throw new Error(`Receipt not found for txId: ${txId}`);
|
|
1078
|
+
const traces = await this.backend.findTraces({ txId });
|
|
1079
|
+
const trace = traces[0];
|
|
1080
|
+
const item = toSummary(receipt.payload, trace?.payload);
|
|
917
1081
|
return {
|
|
918
1082
|
domain: "replay",
|
|
919
1083
|
op: "summary",
|
|
@@ -934,9 +1098,10 @@ var ReplayQueryAdapter = class {
|
|
|
934
1098
|
// -------------------------------------------------------------------------
|
|
935
1099
|
async executeDivergences(request) {
|
|
936
1100
|
const start = Date.now();
|
|
937
|
-
const receipts = await this.
|
|
1101
|
+
const receipts = await this.backend.findReceipts();
|
|
938
1102
|
const divergences = [];
|
|
939
|
-
for (const
|
|
1103
|
+
for (const doc of receipts) {
|
|
1104
|
+
const receipt = doc.payload;
|
|
940
1105
|
if (receipt.contentHash) {
|
|
941
1106
|
const recomputed = computeContentHashSafe(receipt);
|
|
942
1107
|
if (recomputed !== receipt.contentHash) {
|
|
@@ -994,9 +1159,9 @@ var ReplayQueryAdapter = class {
|
|
|
994
1159
|
}
|
|
995
1160
|
divergences.sort((a, b) => a.txId.localeCompare(b.txId));
|
|
996
1161
|
const paged = divergences.slice(request.offset, request.offset + request.limit);
|
|
997
|
-
let
|
|
1162
|
+
let why;
|
|
998
1163
|
if (request.explain) {
|
|
999
|
-
|
|
1164
|
+
why = paged.map((d) => explainDivergence(d));
|
|
1000
1165
|
}
|
|
1001
1166
|
return {
|
|
1002
1167
|
domain: "replay",
|
|
@@ -1006,7 +1171,7 @@ var ReplayQueryAdapter = class {
|
|
|
1006
1171
|
truncated: divergences.length > request.offset + request.limit,
|
|
1007
1172
|
deterministic: true,
|
|
1008
1173
|
queryHash: computeQueryHash(paged),
|
|
1009
|
-
|
|
1174
|
+
why,
|
|
1010
1175
|
annotations: {
|
|
1011
1176
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1012
1177
|
executionMs: Date.now() - start,
|
|
@@ -1021,8 +1186,9 @@ var ReplayQueryAdapter = class {
|
|
|
1021
1186
|
const start = Date.now();
|
|
1022
1187
|
const txId = request.params["txId"];
|
|
1023
1188
|
if (!txId) throw new Error("invariants requires params.txId");
|
|
1024
|
-
const
|
|
1025
|
-
if (!
|
|
1189
|
+
const doc = await this.backend.getArtifact(txId);
|
|
1190
|
+
if (!doc || doc.schema !== "hardkas.txReceipt") throw new Error(`Receipt not found for txId: ${txId}`);
|
|
1191
|
+
const receipt = doc.payload;
|
|
1026
1192
|
const issues = [];
|
|
1027
1193
|
const planIntegrity = receipt.contentHash ? computeContentHashSafe(receipt) === receipt.contentHash : true;
|
|
1028
1194
|
if (!planIntegrity) {
|
|
@@ -1065,9 +1231,9 @@ var ReplayQueryAdapter = class {
|
|
|
1065
1231
|
utxoConservation,
|
|
1066
1232
|
issues
|
|
1067
1233
|
};
|
|
1068
|
-
let
|
|
1234
|
+
let why;
|
|
1069
1235
|
if (request.explain) {
|
|
1070
|
-
|
|
1236
|
+
why = [explainInvariants(result)];
|
|
1071
1237
|
}
|
|
1072
1238
|
return {
|
|
1073
1239
|
domain: "replay",
|
|
@@ -1077,7 +1243,7 @@ var ReplayQueryAdapter = class {
|
|
|
1077
1243
|
truncated: false,
|
|
1078
1244
|
deterministic: true,
|
|
1079
1245
|
queryHash: computeQueryHash([result]),
|
|
1080
|
-
|
|
1246
|
+
why,
|
|
1081
1247
|
annotations: {
|
|
1082
1248
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1083
1249
|
executionMs: Date.now() - start,
|
|
@@ -1085,43 +1251,9 @@ var ReplayQueryAdapter = class {
|
|
|
1085
1251
|
}
|
|
1086
1252
|
};
|
|
1087
1253
|
}
|
|
1088
|
-
// -------------------------------------------------------------------------
|
|
1089
|
-
// Filesystem
|
|
1090
|
-
// -------------------------------------------------------------------------
|
|
1091
|
-
async loadAllReceipts() {
|
|
1092
|
-
const dir = path3.join(this.rootDir, ".hardkas", "receipts");
|
|
1093
|
-
return this.loadJsonDir(dir);
|
|
1094
|
-
}
|
|
1095
|
-
async loadAllTraces() {
|
|
1096
|
-
const dir = path3.join(this.rootDir, ".hardkas", "traces");
|
|
1097
|
-
return this.loadJsonDir(dir);
|
|
1098
|
-
}
|
|
1099
|
-
async loadReceipt(txId) {
|
|
1100
|
-
const filePath = path3.join(this.rootDir, ".hardkas", "receipts", `${txId}.json`);
|
|
1101
|
-
return this.readJsonSafe(filePath);
|
|
1102
|
-
}
|
|
1103
|
-
async loadTrace(txId) {
|
|
1104
|
-
const filePath = path3.join(this.rootDir, ".hardkas", "traces", `${txId}.trace.json`);
|
|
1105
|
-
return this.readJsonSafe(filePath);
|
|
1106
|
-
}
|
|
1107
|
-
async loadJsonDir(dir) {
|
|
1108
|
-
const results = [];
|
|
1109
|
-
let entries;
|
|
1110
|
-
try {
|
|
1111
|
-
entries = await fs3.readdir(dir);
|
|
1112
|
-
} catch {
|
|
1113
|
-
return results;
|
|
1114
|
-
}
|
|
1115
|
-
for (const file of entries.sort()) {
|
|
1116
|
-
if (!file.endsWith(".json")) continue;
|
|
1117
|
-
const raw = await this.readJsonSafe(path3.join(dir, file));
|
|
1118
|
-
if (raw) results.push(raw);
|
|
1119
|
-
}
|
|
1120
|
-
return results;
|
|
1121
|
-
}
|
|
1122
1254
|
async readJsonSafe(filePath) {
|
|
1123
1255
|
try {
|
|
1124
|
-
const content = await
|
|
1256
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
1125
1257
|
return JSON.parse(content);
|
|
1126
1258
|
} catch {
|
|
1127
1259
|
return null;
|
|
@@ -1164,43 +1296,55 @@ var DIVERGENCE_RULES = {
|
|
|
1164
1296
|
};
|
|
1165
1297
|
function explainDivergence(d) {
|
|
1166
1298
|
return {
|
|
1167
|
-
question: `Why is tx ${d.txId.slice(0, 16)}...
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
{
|
|
1299
|
+
question: `Why is tx ${d.txId.slice(0, 16)}... divergent?`,
|
|
1300
|
+
answer: `Field "${d.field}" shows unexpected non-deterministic behavior (${d.kind}).`,
|
|
1301
|
+
evidence: [{ type: "txId", value: d.txId }],
|
|
1302
|
+
causalChain: [
|
|
1303
|
+
{
|
|
1304
|
+
order: 1,
|
|
1305
|
+
assertion: `Value mismatch in "${d.field}"`,
|
|
1306
|
+
evidence: `Expected: ${d.expected.slice(0, 40)}, Actual: ${d.actual.slice(0, 40)}`,
|
|
1307
|
+
rule: DIVERGENCE_RULES[d.kind]
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
order: 2,
|
|
1311
|
+
assertion: "Divergence detected in replay comparison",
|
|
1312
|
+
evidence: "Verification engine mismatch",
|
|
1313
|
+
rule: "Invariant validation policy"
|
|
1314
|
+
}
|
|
1172
1315
|
],
|
|
1173
|
-
model: "replay-
|
|
1174
|
-
confidence: "definitive"
|
|
1175
|
-
references: [d.txId]
|
|
1316
|
+
model: "replay-analysis",
|
|
1317
|
+
confidence: "definitive"
|
|
1176
1318
|
};
|
|
1177
1319
|
}
|
|
1178
1320
|
function explainInvariants(result) {
|
|
1179
|
-
const
|
|
1321
|
+
const causalChain = [];
|
|
1180
1322
|
let order = 1;
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1323
|
+
causalChain.push({ order: order++, assertion: result.planIntegrity ? "Plan integrity is OK" : "Plan integrity FAILED", evidence: `planIntegrity=${result.planIntegrity}`, rule: "SHA-256 canonical consistency" });
|
|
1324
|
+
causalChain.push({ order: order++, assertion: result.receiptReproducible ? "Receipt is reproducible" : "Receipt is NOT reproducible", evidence: `receiptReproducible=${result.receiptReproducible}`, rule: "Replay evidence requirements" });
|
|
1325
|
+
causalChain.push({ order: order++, assertion: result.stateTransitionValid ? "State transition is valid" : "State transition INVALID", evidence: `stateTransitionValid=${result.stateTransitionValid}`, rule: "Status/State alignment" });
|
|
1326
|
+
causalChain.push({ order: order++, assertion: result.utxoConservation ? "UTXO conservation holds" : "UTXO conservation VIOLATED", evidence: `utxoConservation=${result.utxoConservation}`, rule: "Value conservation policy" });
|
|
1185
1327
|
const allOk = result.planIntegrity && result.receiptReproducible && result.stateTransitionValid && result.utxoConservation;
|
|
1186
1328
|
return {
|
|
1187
1329
|
question: `Are replay invariants satisfied for tx ${result.txId.slice(0, 16)}...?`,
|
|
1188
|
-
|
|
1189
|
-
|
|
1330
|
+
answer: allOk ? "All replay invariants satisfied." : `Violations found: ${result.issues.join("; ")}`,
|
|
1331
|
+
evidence: [{ type: "txId", value: result.txId }],
|
|
1332
|
+
causalChain,
|
|
1190
1333
|
model: "replay-invariants",
|
|
1191
|
-
confidence: "definitive"
|
|
1192
|
-
references: [result.txId]
|
|
1334
|
+
confidence: "definitive"
|
|
1193
1335
|
};
|
|
1194
1336
|
}
|
|
1195
1337
|
|
|
1196
1338
|
// src/adapters/dag-adapter.ts
|
|
1197
|
-
import
|
|
1339
|
+
import fs5 from "fs/promises";
|
|
1198
1340
|
import path4 from "path";
|
|
1199
1341
|
var DagQueryAdapter = class {
|
|
1200
1342
|
domain = "dag";
|
|
1201
1343
|
rootDir;
|
|
1202
|
-
|
|
1344
|
+
backend;
|
|
1345
|
+
constructor(rootDir, backend) {
|
|
1203
1346
|
this.rootDir = rootDir;
|
|
1347
|
+
this.backend = backend;
|
|
1204
1348
|
}
|
|
1205
1349
|
supportedOps() {
|
|
1206
1350
|
return ["conflicts", "displaced", "history", "sink-path", "anomalies"];
|
|
@@ -1240,9 +1384,9 @@ var DagQueryAdapter = class {
|
|
|
1240
1384
|
}));
|
|
1241
1385
|
items.sort((a, b) => a.outpoint.localeCompare(b.outpoint));
|
|
1242
1386
|
const paged = items.slice(request.offset, request.offset + request.limit);
|
|
1243
|
-
let
|
|
1387
|
+
let why;
|
|
1244
1388
|
if (request.explain) {
|
|
1245
|
-
|
|
1389
|
+
why = paged.map((c) => explainConflict(c, dag));
|
|
1246
1390
|
}
|
|
1247
1391
|
return {
|
|
1248
1392
|
domain: "dag",
|
|
@@ -1252,7 +1396,7 @@ var DagQueryAdapter = class {
|
|
|
1252
1396
|
truncated: items.length > request.offset + request.limit,
|
|
1253
1397
|
deterministic: true,
|
|
1254
1398
|
queryHash: computeQueryHash(paged),
|
|
1255
|
-
|
|
1399
|
+
why,
|
|
1256
1400
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1257
1401
|
};
|
|
1258
1402
|
}
|
|
@@ -1276,9 +1420,9 @@ var DagQueryAdapter = class {
|
|
|
1276
1420
|
});
|
|
1277
1421
|
items.sort((a, b) => a.txId.localeCompare(b.txId));
|
|
1278
1422
|
const paged = items.slice(request.offset, request.offset + request.limit);
|
|
1279
|
-
let
|
|
1423
|
+
let why;
|
|
1280
1424
|
if (request.explain) {
|
|
1281
|
-
|
|
1425
|
+
why = paged.map((d) => explainDisplacement(d, dag));
|
|
1282
1426
|
}
|
|
1283
1427
|
return {
|
|
1284
1428
|
domain: "dag",
|
|
@@ -1288,7 +1432,7 @@ var DagQueryAdapter = class {
|
|
|
1288
1432
|
truncated: items.length > request.offset + request.limit,
|
|
1289
1433
|
deterministic: true,
|
|
1290
1434
|
queryHash: computeQueryHash(paged),
|
|
1291
|
-
|
|
1435
|
+
why,
|
|
1292
1436
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1293
1437
|
};
|
|
1294
1438
|
}
|
|
@@ -1328,9 +1472,9 @@ var DagQueryAdapter = class {
|
|
|
1328
1472
|
}
|
|
1329
1473
|
}
|
|
1330
1474
|
entries.sort((a, b) => a.daaScore.localeCompare(b.daaScore));
|
|
1331
|
-
let
|
|
1475
|
+
let why;
|
|
1332
1476
|
if (request.explain) {
|
|
1333
|
-
|
|
1477
|
+
why = entries.map((e) => explainTxHistory(e, dag));
|
|
1334
1478
|
}
|
|
1335
1479
|
return {
|
|
1336
1480
|
domain: "dag",
|
|
@@ -1340,7 +1484,7 @@ var DagQueryAdapter = class {
|
|
|
1340
1484
|
truncated: false,
|
|
1341
1485
|
deterministic: true,
|
|
1342
1486
|
queryHash: computeQueryHash(entries),
|
|
1343
|
-
|
|
1487
|
+
why,
|
|
1344
1488
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1345
1489
|
};
|
|
1346
1490
|
}
|
|
@@ -1428,9 +1572,9 @@ var DagQueryAdapter = class {
|
|
|
1428
1572
|
}
|
|
1429
1573
|
anomalies.sort((a, b) => a.kind.localeCompare(b.kind));
|
|
1430
1574
|
const paged = anomalies.slice(request.offset, request.offset + request.limit);
|
|
1431
|
-
let
|
|
1575
|
+
let why;
|
|
1432
1576
|
if (request.explain) {
|
|
1433
|
-
|
|
1577
|
+
why = paged.map((a) => explainAnomaly(a, dag));
|
|
1434
1578
|
}
|
|
1435
1579
|
return {
|
|
1436
1580
|
domain: "dag",
|
|
@@ -1440,7 +1584,7 @@ var DagQueryAdapter = class {
|
|
|
1440
1584
|
truncated: anomalies.length > request.offset + request.limit,
|
|
1441
1585
|
deterministic: true,
|
|
1442
1586
|
queryHash: computeQueryHash(paged),
|
|
1443
|
-
|
|
1587
|
+
why,
|
|
1444
1588
|
annotations: { executedAt: (/* @__PURE__ */ new Date()).toISOString(), executionMs: Date.now() - start }
|
|
1445
1589
|
};
|
|
1446
1590
|
}
|
|
@@ -1450,7 +1594,7 @@ var DagQueryAdapter = class {
|
|
|
1450
1594
|
async loadDag() {
|
|
1451
1595
|
const statePath = path4.join(this.rootDir, ".hardkas", "state.json");
|
|
1452
1596
|
try {
|
|
1453
|
-
const content = await
|
|
1597
|
+
const content = await fs5.readFile(statePath, "utf-8");
|
|
1454
1598
|
const state = JSON.parse(content);
|
|
1455
1599
|
return state.dag ?? null;
|
|
1456
1600
|
} catch {
|
|
@@ -1471,7 +1615,7 @@ function emptyDagResult(op, start) {
|
|
|
1471
1615
|
};
|
|
1472
1616
|
}
|
|
1473
1617
|
function explainConflict(conflict, dag) {
|
|
1474
|
-
const
|
|
1618
|
+
const causalChain = [];
|
|
1475
1619
|
let order = 1;
|
|
1476
1620
|
let winnerBlockId;
|
|
1477
1621
|
let winnerDaaScore = "0";
|
|
@@ -1484,92 +1628,87 @@ function explainConflict(conflict, dag) {
|
|
|
1484
1628
|
break;
|
|
1485
1629
|
}
|
|
1486
1630
|
}
|
|
1487
|
-
|
|
1631
|
+
causalChain.push({
|
|
1488
1632
|
order: order++,
|
|
1489
|
-
assertion: `Outpoint ${conflict.outpoint}
|
|
1633
|
+
assertion: `Outpoint ${conflict.outpoint} spent by multiple txs`,
|
|
1490
1634
|
evidence: `Winner: ${conflict.winnerTxId}, Losers: ${conflict.loserTxIds.join(", ")}`,
|
|
1491
|
-
rule: "UTXO double-spend detection
|
|
1635
|
+
rule: "UTXO double-spend detection"
|
|
1492
1636
|
});
|
|
1493
1637
|
if (winnerBlockId) {
|
|
1494
|
-
|
|
1638
|
+
causalChain.push({
|
|
1495
1639
|
order: order++,
|
|
1496
|
-
assertion: `Winner
|
|
1497
|
-
evidence: `
|
|
1498
|
-
rule: "
|
|
1499
|
-
});
|
|
1500
|
-
steps.push({
|
|
1501
|
-
order: order++,
|
|
1502
|
-
assertion: winnerInSinkPath ? `Block ${winnerBlockId} IS in the sink path \u2014 has priority` : `Block ${winnerBlockId} is NOT in the sink path`,
|
|
1503
|
-
evidence: `selectedPathToSink.includes("${winnerBlockId}") = ${winnerInSinkPath}`,
|
|
1504
|
-
rule: "Sink-ancestry priority (dag.ts:resolveConflictsDeterministically)"
|
|
1640
|
+
assertion: `Winner in block ${winnerBlockId} (daaScore: ${winnerDaaScore})`,
|
|
1641
|
+
evidence: `In sink path: ${winnerInSinkPath}`,
|
|
1642
|
+
rule: "Sink-ancestry priority"
|
|
1505
1643
|
});
|
|
1506
1644
|
}
|
|
1507
|
-
steps.push({
|
|
1508
|
-
order: order++,
|
|
1509
|
-
assertion: `Conflict resolved: ${conflict.winnerTxId.slice(0, 16)}... accepted, ${conflict.loserTxIds.length} tx(s) displaced`,
|
|
1510
|
-
evidence: `Resolution based on: 1) sink-path priority, 2) daaScore ordering, 3) blockId tie-break`,
|
|
1511
|
-
rule: "Deterministic conflict resolution (dag.ts:187-248)"
|
|
1512
|
-
});
|
|
1513
1645
|
return {
|
|
1514
|
-
question: `Why
|
|
1515
|
-
|
|
1516
|
-
|
|
1646
|
+
question: `Why winner ${conflict.winnerTxId.slice(0, 8)} on outpoint ${conflict.outpoint}?`,
|
|
1647
|
+
answer: `Winner has ${winnerInSinkPath ? "sink-path priority" : "block ordering priority"}.`,
|
|
1648
|
+
evidence: [
|
|
1649
|
+
{ type: "txId", value: conflict.winnerTxId },
|
|
1650
|
+
...conflict.loserTxIds.map((id) => ({ type: "txId", value: id }))
|
|
1651
|
+
],
|
|
1652
|
+
causalChain,
|
|
1517
1653
|
model: "deterministic-light-model",
|
|
1518
|
-
confidence: "definitive"
|
|
1519
|
-
references: [conflict.winnerTxId, ...conflict.loserTxIds]
|
|
1654
|
+
confidence: "definitive"
|
|
1520
1655
|
};
|
|
1521
1656
|
}
|
|
1522
1657
|
function explainDisplacement(d, _dag) {
|
|
1523
1658
|
return {
|
|
1524
|
-
question: `Why
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
{ order:
|
|
1659
|
+
question: `Why displaced tx ${d.txId.slice(0, 8)}?`,
|
|
1660
|
+
answer: d.reason,
|
|
1661
|
+
evidence: [{ type: "txId", value: d.txId }],
|
|
1662
|
+
causalChain: [
|
|
1663
|
+
{ order: 1, assertion: "Tx in displaced set", evidence: "dag.displacedTxIds includes txId", rule: "DAG reorganization" },
|
|
1664
|
+
{ order: 2, assertion: "Status: displaced", evidence: d.reason }
|
|
1529
1665
|
],
|
|
1530
1666
|
model: "deterministic-light-model",
|
|
1531
|
-
confidence: "definitive"
|
|
1532
|
-
references: [d.txId]
|
|
1667
|
+
confidence: "definitive"
|
|
1533
1668
|
};
|
|
1534
1669
|
}
|
|
1535
1670
|
function explainTxHistory(entry, _dag) {
|
|
1536
1671
|
const status = entry.accepted ? "ACCEPTED" : entry.displaced ? "DISPLACED" : "UNKNOWN";
|
|
1537
1672
|
return {
|
|
1538
|
-
question: `
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
{
|
|
1542
|
-
{
|
|
1543
|
-
|
|
1544
|
-
|
|
1673
|
+
question: `Causal history of tx ${entry.txId.slice(0, 8)}?`,
|
|
1674
|
+
answer: `Status is ${status} in block ${entry.blockId}.`,
|
|
1675
|
+
evidence: [
|
|
1676
|
+
{ type: "txId", value: entry.txId },
|
|
1677
|
+
{ type: "blockId", value: entry.blockId }
|
|
1678
|
+
],
|
|
1679
|
+
causalChain: [
|
|
1680
|
+
{ order: 1, assertion: `In block ${entry.blockId}`, evidence: `daaScore=${entry.daaScore}` },
|
|
1681
|
+
{ order: 2, assertion: entry.inSinkPath ? "In selected sink path" : "Not in sink path", evidence: `selectedPathToSink.includes("${entry.blockId}")` }
|
|
1545
1682
|
],
|
|
1546
1683
|
model: "deterministic-light-model",
|
|
1547
|
-
confidence: "definitive"
|
|
1548
|
-
references: [entry.txId]
|
|
1684
|
+
confidence: "definitive"
|
|
1549
1685
|
};
|
|
1550
1686
|
}
|
|
1551
1687
|
function explainAnomaly(a, _dag) {
|
|
1688
|
+
const evidence = [];
|
|
1689
|
+
if (a.txId) evidence.push({ type: "txId", value: a.txId });
|
|
1690
|
+
if (a.blockId) evidence.push({ type: "blockId", value: a.blockId });
|
|
1552
1691
|
return {
|
|
1553
|
-
question: `Why
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1692
|
+
question: `Why anomaly: ${a.kind}?`,
|
|
1693
|
+
answer: a.description,
|
|
1694
|
+
evidence,
|
|
1695
|
+
causalChain: [
|
|
1696
|
+
{ order: 1, assertion: a.description, evidence: `Anomaly: ${a.kind}`, rule: "DAG invariant check" }
|
|
1557
1697
|
],
|
|
1558
1698
|
model: "deterministic-light-model",
|
|
1559
|
-
confidence: "definitive"
|
|
1560
|
-
references: [a.txId ?? a.blockId ?? ""]
|
|
1699
|
+
confidence: "definitive"
|
|
1561
1700
|
};
|
|
1562
1701
|
}
|
|
1563
1702
|
|
|
1564
1703
|
// src/adapters/events-adapter.ts
|
|
1565
|
-
import fs5 from "fs/promises";
|
|
1566
1704
|
import path5 from "path";
|
|
1567
|
-
import { validateEventEnvelope } from "@hardkas/core";
|
|
1568
1705
|
var EventsQueryAdapter = class {
|
|
1569
1706
|
domain = "events";
|
|
1570
1707
|
rootDir;
|
|
1571
|
-
|
|
1708
|
+
backend;
|
|
1709
|
+
constructor(rootDir, backend) {
|
|
1572
1710
|
this.rootDir = rootDir;
|
|
1711
|
+
this.backend = backend;
|
|
1573
1712
|
}
|
|
1574
1713
|
supportedOps() {
|
|
1575
1714
|
return ["list", "summary"];
|
|
@@ -1588,7 +1727,7 @@ var EventsQueryAdapter = class {
|
|
|
1588
1727
|
async executeList(request) {
|
|
1589
1728
|
const start = Date.now();
|
|
1590
1729
|
const events = await this.loadEvents();
|
|
1591
|
-
const backendUsed =
|
|
1730
|
+
const backendUsed = this.backend.kind();
|
|
1592
1731
|
const eventsPath = path5.join(this.rootDir, ".hardkas", "events.jsonl");
|
|
1593
1732
|
const filtered = [];
|
|
1594
1733
|
const txFilter = request.params["tx"] || request.params["txId"];
|
|
@@ -1619,67 +1758,52 @@ var EventsQueryAdapter = class {
|
|
|
1619
1758
|
annotations: {
|
|
1620
1759
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1621
1760
|
executionMs: Date.now() - start,
|
|
1622
|
-
filesScanned: 1
|
|
1761
|
+
filesScanned: backendUsed === "sqlite" ? 0 : 1
|
|
1623
1762
|
},
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}]
|
|
1637
|
-
} : {}
|
|
1763
|
+
why: request.explain ? [{
|
|
1764
|
+
question: "How were events loaded and linked?",
|
|
1765
|
+
answer: `Loaded ${total} events matching filters. Causal links (correlation/causation) available in payload.`,
|
|
1766
|
+
evidence: [],
|
|
1767
|
+
causalChain: [
|
|
1768
|
+
{ order: 1, assertion: `Source: ${backendUsed}`, evidence: "Event stream processed" },
|
|
1769
|
+
{ order: 2, assertion: `Filters: ${effectiveFilters.length} applied`, evidence: effectiveFilters.map((f) => `${f.field} ${f.op} ${f.value}`).join(", ") || "none" },
|
|
1770
|
+
{ order: 3, assertion: "Ordering: timestamp ASC", evidence: "Deterministic stream sorting" }
|
|
1771
|
+
],
|
|
1772
|
+
model: "events-causality",
|
|
1773
|
+
confidence: "definitive"
|
|
1774
|
+
}] : void 0
|
|
1638
1775
|
};
|
|
1639
1776
|
}
|
|
1640
1777
|
async loadEvents() {
|
|
1641
|
-
const
|
|
1642
|
-
let content;
|
|
1643
|
-
try {
|
|
1644
|
-
content = await fs5.readFile(eventsPath, "utf-8");
|
|
1645
|
-
} catch {
|
|
1646
|
-
return [];
|
|
1647
|
-
}
|
|
1648
|
-
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
1778
|
+
const docs = await this.backend.getEvents();
|
|
1649
1779
|
const events = [];
|
|
1650
|
-
for (const
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
networkId: parsed.networkId,
|
|
1665
|
-
payload: parsed.payload
|
|
1666
|
-
});
|
|
1667
|
-
} catch {
|
|
1668
|
-
}
|
|
1780
|
+
for (const doc of docs) {
|
|
1781
|
+
events.push({
|
|
1782
|
+
eventId: doc.eventId,
|
|
1783
|
+
kind: doc.kind,
|
|
1784
|
+
domain: doc.domain,
|
|
1785
|
+
timestamp: doc.timestamp || "",
|
|
1786
|
+
workflowId: doc.workflowId,
|
|
1787
|
+
correlationId: doc.correlationId,
|
|
1788
|
+
causationId: doc.causationId || void 0,
|
|
1789
|
+
txId: doc.txId || void 0,
|
|
1790
|
+
artifactId: doc.artifactId || void 0,
|
|
1791
|
+
networkId: doc.networkId,
|
|
1792
|
+
payload: doc.payload
|
|
1793
|
+
});
|
|
1669
1794
|
}
|
|
1670
1795
|
return events;
|
|
1671
1796
|
}
|
|
1672
1797
|
};
|
|
1673
1798
|
|
|
1674
1799
|
// src/adapters/tx-adapter.ts
|
|
1675
|
-
import fs6 from "fs/promises";
|
|
1676
|
-
import path6 from "path";
|
|
1677
|
-
import { validateEventEnvelope as validateEventEnvelope2 } from "@hardkas/core";
|
|
1678
1800
|
var TxQueryAdapter = class {
|
|
1679
1801
|
domain = "tx";
|
|
1680
1802
|
rootDir;
|
|
1681
|
-
|
|
1803
|
+
backend;
|
|
1804
|
+
constructor(rootDir, backend) {
|
|
1682
1805
|
this.rootDir = rootDir;
|
|
1806
|
+
this.backend = backend;
|
|
1683
1807
|
}
|
|
1684
1808
|
supportedOps() {
|
|
1685
1809
|
return ["aggregate"];
|
|
@@ -1716,20 +1840,19 @@ var TxQueryAdapter = class {
|
|
|
1716
1840
|
warnings,
|
|
1717
1841
|
complete
|
|
1718
1842
|
};
|
|
1719
|
-
let
|
|
1843
|
+
let why;
|
|
1720
1844
|
if (request.explain) {
|
|
1721
|
-
|
|
1722
|
-
question: `
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
{ order:
|
|
1727
|
-
{ order:
|
|
1728
|
-
{ order:
|
|
1845
|
+
why = [{
|
|
1846
|
+
question: `Causal aggregation for transaction ${txId}?`,
|
|
1847
|
+
answer: complete ? `Found ${artifacts.length} artifact(s) and ${events.length} event(s). Workflow is consistent.` : `Aggregation incomplete: ${warnings.join(". ")}.`,
|
|
1848
|
+
evidence: [{ type: "txId", value: txId }],
|
|
1849
|
+
causalChain: [
|
|
1850
|
+
{ order: 1, assertion: `Artifacts linked: ${artifacts.length}`, evidence: artifacts.map((a) => a.role).join(", ") },
|
|
1851
|
+
{ order: 2, assertion: `Events linked: ${events.length}`, evidence: "Events found in stream" },
|
|
1852
|
+
{ order: 3, assertion: `Completeness check: ${complete}`, evidence: warnings.join("; ") || "all required roles found" }
|
|
1729
1853
|
],
|
|
1730
|
-
model: "tx-
|
|
1731
|
-
confidence: "definitive"
|
|
1732
|
-
references: [txId]
|
|
1854
|
+
model: "tx-causality",
|
|
1855
|
+
confidence: "definitive"
|
|
1733
1856
|
}];
|
|
1734
1857
|
}
|
|
1735
1858
|
return {
|
|
@@ -1740,7 +1863,7 @@ var TxQueryAdapter = class {
|
|
|
1740
1863
|
truncated: false,
|
|
1741
1864
|
deterministic: true,
|
|
1742
1865
|
queryHash: computeQueryHash([result]),
|
|
1743
|
-
|
|
1866
|
+
why,
|
|
1744
1867
|
annotations: {
|
|
1745
1868
|
executedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1746
1869
|
executionMs: Date.now() - start
|
|
@@ -1748,92 +1871,82 @@ var TxQueryAdapter = class {
|
|
|
1748
1871
|
};
|
|
1749
1872
|
}
|
|
1750
1873
|
async findArtifactsByTxId(txId) {
|
|
1874
|
+
const docs = await this.backend.findArtifacts();
|
|
1751
1875
|
const results = [];
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
schema,
|
|
1768
|
-
contentHash: parsed.contentHash,
|
|
1769
|
-
role
|
|
1770
|
-
});
|
|
1771
|
-
} catch {
|
|
1772
|
-
}
|
|
1876
|
+
for (const doc of docs) {
|
|
1877
|
+
const parsed = doc.payload;
|
|
1878
|
+
const matchesTx = parsed.txId === txId || parsed.transaction?.id === txId || parsed.lineage?.artifactId === txId;
|
|
1879
|
+
if (!matchesTx) continue;
|
|
1880
|
+
const schema = String(doc.schema);
|
|
1881
|
+
let role = "unknown";
|
|
1882
|
+
if (schema.includes("txPlan")) role = "plan";
|
|
1883
|
+
else if (schema.includes("signedTx")) role = "signed";
|
|
1884
|
+
else if (schema.includes("txReceipt")) role = "receipt";
|
|
1885
|
+
results.push({
|
|
1886
|
+
filePath: doc.path,
|
|
1887
|
+
schema,
|
|
1888
|
+
contentHash: doc.contentHash,
|
|
1889
|
+
role
|
|
1890
|
+
});
|
|
1773
1891
|
}
|
|
1774
1892
|
return results.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
1775
1893
|
}
|
|
1776
1894
|
async findEventsByTxId(txId) {
|
|
1777
|
-
const
|
|
1778
|
-
let content;
|
|
1779
|
-
try {
|
|
1780
|
-
content = await fs6.readFile(eventsPath, "utf-8");
|
|
1781
|
-
} catch {
|
|
1782
|
-
return [];
|
|
1783
|
-
}
|
|
1784
|
-
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
1895
|
+
const docs = await this.backend.getEvents({ txId });
|
|
1785
1896
|
const results = [];
|
|
1786
|
-
for (const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
eventId: parsed.eventId,
|
|
1793
|
-
kind: parsed.kind,
|
|
1794
|
-
timestamp: parsed.timestamp || ""
|
|
1795
|
-
});
|
|
1796
|
-
} catch {
|
|
1797
|
-
}
|
|
1897
|
+
for (const doc of docs) {
|
|
1898
|
+
results.push({
|
|
1899
|
+
eventId: doc.eventId,
|
|
1900
|
+
kind: doc.kind,
|
|
1901
|
+
timestamp: doc.timestamp || ""
|
|
1902
|
+
});
|
|
1798
1903
|
}
|
|
1799
1904
|
return results;
|
|
1800
1905
|
}
|
|
1801
|
-
async scanJsonFiles() {
|
|
1802
|
-
const files = [];
|
|
1803
|
-
const hardkasDir = path6.join(this.rootDir, ".hardkas");
|
|
1804
|
-
await this.walkDir(hardkasDir, files);
|
|
1805
|
-
return files.sort();
|
|
1806
|
-
}
|
|
1807
|
-
async walkDir(dir, out) {
|
|
1808
|
-
let entries;
|
|
1809
|
-
try {
|
|
1810
|
-
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
1811
|
-
} catch {
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
for (const entry of entries) {
|
|
1815
|
-
const full = path6.join(dir, entry.name);
|
|
1816
|
-
if (entry.isDirectory()) {
|
|
1817
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
1818
|
-
await this.walkDir(full, out);
|
|
1819
|
-
} else if (entry.name.endsWith(".json") && !entry.name.endsWith(".enc.json") && entry.name !== "events.jsonl") {
|
|
1820
|
-
out.push(full);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
1906
|
};
|
|
1825
1907
|
|
|
1826
1908
|
// src/engine.ts
|
|
1827
|
-
|
|
1909
|
+
import fs6 from "fs";
|
|
1910
|
+
import path6 from "path";
|
|
1911
|
+
var QueryEngine = class _QueryEngine {
|
|
1828
1912
|
adapters;
|
|
1913
|
+
backend;
|
|
1914
|
+
/**
|
|
1915
|
+
* Primary entry point for creating a QueryEngine with auto-discovery.
|
|
1916
|
+
*/
|
|
1917
|
+
static async create(options) {
|
|
1918
|
+
let backend = options.backend;
|
|
1919
|
+
if (!backend) {
|
|
1920
|
+
const dbPath = path6.join(options.artifactDir, ".hardkas", "store.db");
|
|
1921
|
+
if (fs6.existsSync(dbPath)) {
|
|
1922
|
+
try {
|
|
1923
|
+
const { HardkasStore, SqliteQueryBackend, HardkasIndexer } = await import("@hardkas/query-store");
|
|
1924
|
+
const store = new HardkasStore({ dbPath });
|
|
1925
|
+
store.connect();
|
|
1926
|
+
const indexer = new HardkasIndexer(store.getDatabase());
|
|
1927
|
+
await indexer.sync();
|
|
1928
|
+
backend = new SqliteQueryBackend(store);
|
|
1929
|
+
} catch (e) {
|
|
1930
|
+
backend = new FilesystemQueryBackend(options.artifactDir);
|
|
1931
|
+
}
|
|
1932
|
+
} else {
|
|
1933
|
+
backend = new FilesystemQueryBackend(options.artifactDir);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return new _QueryEngine({
|
|
1937
|
+
artifactDir: options.artifactDir,
|
|
1938
|
+
backend
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1829
1941
|
constructor(options) {
|
|
1942
|
+
this.backend = options.backend || new FilesystemQueryBackend(options.artifactDir);
|
|
1830
1943
|
this.adapters = /* @__PURE__ */ new Map();
|
|
1831
|
-
this.adapters.set("artifacts", new ArtifactQueryAdapter(options.artifactDir));
|
|
1832
|
-
this.adapters.set("lineage", new LineageQueryAdapter(options.artifactDir));
|
|
1833
|
-
this.adapters.set("replay", new ReplayQueryAdapter(options.artifactDir));
|
|
1834
|
-
this.adapters.set("dag", new DagQueryAdapter(options.artifactDir));
|
|
1835
|
-
this.adapters.set("events", new EventsQueryAdapter(options.artifactDir));
|
|
1836
|
-
this.adapters.set("tx", new TxQueryAdapter(options.artifactDir));
|
|
1944
|
+
this.adapters.set("artifacts", new ArtifactQueryAdapter(options.artifactDir, this.backend));
|
|
1945
|
+
this.adapters.set("lineage", new LineageQueryAdapter(options.artifactDir, this.backend));
|
|
1946
|
+
this.adapters.set("replay", new ReplayQueryAdapter(options.artifactDir, this.backend));
|
|
1947
|
+
this.adapters.set("dag", new DagQueryAdapter(options.artifactDir, this.backend));
|
|
1948
|
+
this.adapters.set("events", new EventsQueryAdapter(options.artifactDir, this.backend));
|
|
1949
|
+
this.adapters.set("tx", new TxQueryAdapter(options.artifactDir, this.backend));
|
|
1837
1950
|
}
|
|
1838
1951
|
/**
|
|
1839
1952
|
* Execute a query request against the appropriate adapter.
|
|
@@ -1848,7 +1961,31 @@ var QueryEngine = class {
|
|
|
1848
1961
|
`Operation "${request.op}" is not supported by the "${request.domain}" adapter. Supported: ${adapter.supportedOps().join(", ")}`
|
|
1849
1962
|
);
|
|
1850
1963
|
}
|
|
1851
|
-
|
|
1964
|
+
const result = await adapter.execute(request);
|
|
1965
|
+
const freshness = await this.backend.getStoreStatus();
|
|
1966
|
+
const backendUsed = this.backend.kind();
|
|
1967
|
+
let explain = void 0;
|
|
1968
|
+
if (request.explain) {
|
|
1969
|
+
explain = {
|
|
1970
|
+
backend: backendUsed,
|
|
1971
|
+
executionPlan: ["Discovery", "Filter", "Sort", "Paginate"],
|
|
1972
|
+
indexesUsed: backendUsed === "sqlite" ? ["PRIMARY", "idx_artifacts_schema"] : [],
|
|
1973
|
+
filtersApplied: request.filters.map((f) => `${f.field} ${f.op} ${f.value}`),
|
|
1974
|
+
rowsRead: result.items.length,
|
|
1975
|
+
scannedFiles: result.annotations.filesScanned || 0,
|
|
1976
|
+
freshness,
|
|
1977
|
+
warnings: freshness === "stale" ? ["Index is STALE. mtime mismatch detected. Run 'hardkas query store rebuild'."] : []
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
return {
|
|
1981
|
+
...result,
|
|
1982
|
+
explain,
|
|
1983
|
+
annotations: {
|
|
1984
|
+
...result.annotations,
|
|
1985
|
+
backendUsed,
|
|
1986
|
+
freshness
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1852
1989
|
}
|
|
1853
1990
|
/**
|
|
1854
1991
|
* List available domains and their operations.
|
|
@@ -1886,14 +2023,15 @@ export {
|
|
|
1886
2023
|
ReplayQueryAdapter,
|
|
1887
2024
|
TxQueryAdapter,
|
|
1888
2025
|
computeQueryHash,
|
|
2026
|
+
createExplainBlock,
|
|
1889
2027
|
createQueryRequest,
|
|
1890
2028
|
evaluateFilter,
|
|
1891
2029
|
evaluateFilters,
|
|
1892
2030
|
explainIntegrity,
|
|
1893
2031
|
explainOrphan,
|
|
1894
2032
|
explainTransition,
|
|
1895
|
-
|
|
1896
|
-
|
|
2033
|
+
formatExplainBlock,
|
|
2034
|
+
formatWhyBlock,
|
|
1897
2035
|
resolveFieldPath,
|
|
1898
2036
|
serializeQueryResult
|
|
1899
2037
|
};
|